[{"content":"Terraform is an IaC tool. IaC stands for ‘Infrastructure as Code’: we write our infrastructure as declarative code, then use terraform apply to deploy it. With the same configuration, you will always get exactly the same infrastructure every time (Nix OS users rejoice).\nWhy use Terraform Traditional infrastructure management mostly relies on manual work and the dashboards provided by various cloud vendors, which brings the following pain points:\nPain point Explanation Hard to reproduce Configuring things by clicking around in a dashboard makes it easy to miss or misconfigure something, and hard to reproduce later Environment drift Manual changes gradually cause production and test environments to diverge, so in extreme cases testing is fine but production falls over Hard to scale Adding a new environment requires repeating lots of manual steps, which is time-consuming and error-prone Hard to audit There is no change history, so when things go wrong it is harder to pass the buck Hard to collaborate Infrastructure ends up controlled by a small number of ‘people who know’, and anyone who wants to change something has to go through them, which is inefficient To solve these problems, the concept of ‘Infrastructure as Code’ 1 was introduced, and Terraform is one of the best-known solutions.\nTake a realistic use case: suppose you buy a new GCP account with $300 trial credit every year. It is cheap, but every year you have to go back into the GCP console and recreate your machines. With Terraform, if you want to deploy the same setup again, you just replace the API token after switching accounts, then run terraform apply. In a few minutes, you can recreate exactly the same machines, VPCs, S3, firewall rules, and so on as in your previous account.\nAnother example is managing and migrating Cloudflare DNS and Tunnel. You only need to copy the old Tunnel’s Ingress Rule to the new Tunnel’s Ingress Rule. Even if you later migrate to other providers such as AliDNS or Route 53, you can still copy the data across as-is 2.\nThis becomes especially useful when working with other people. Combined with Git, every change leaves a trace, merge conflicts are far less worrying, PRs can automatically generate previews of changes, and if something goes wrong you can roll back to the previous version immediately. None of this is really possible with the traditional approach of people directly operating a provider’s dashboard by hand.\nInstalling Terraform Terraform is written in Go, and the compiled output is naturally a single executable file, so installation is very straightforward. On Windows, you can install it directly with Winget:\nwinget install Hashicorp.TerraformIf you do not want to use Winget, you can also use Scoop or another package manager, or place the precompiled binary into $PATH to complete the installation.\nIf you are on Linux, just use the appropriate package manager. If you install it by placing the binary into $PATH, remember to use sudo chmod +x terraform to make the file executable.\nTwo basic Terraform concepts Provider(s) As mentioned earlier, Terraform is an Infrastructure as Code tool. As a tool, it is not tied to any specific platform. Instead, it connects to different platforms through Provider(s). To see what Provider(s) are available, you can browse Terraform’s Registry.\nState management Terraform stores the state information from each infrastructure change operation in a state file. By default, this is saved as the terraform.tfstate file in the current working directory 3, though you can also configure a different backend such as S3 or Postgres. Every time you run terraform apply, Terraform compares the state declared in the current configuration files with the existing state file, calculates the differences, works out the correct order of operations, and then tells the Provider to apply those changes.\nTerraform’s resource import problem Importing existing resources has long been one of Terraform’s most criticised pain points. Hashi Corp. seems to have stuck to a stubborn and frankly silly idea for years: all your infrastructure should have been created with Terraform from the very beginning, so there is no such thing as a resource import problem.\nTime Version Progress Problem 2014–2022 v0.x–v1.4 Only terraform import, one resource at a time, and it did not generate any configuration After importing, you still had to hand-write the HCL 4 2023.06 v1.5 Introduced the import block and the -generate-config-out parameter, so it could generate configuration But you still had to write them one by one, and still had to provide the existing resource IDs yourself 2024.01 v1.7 The import block gained support for for_each Batch import at last, but you still had to obtain the IDs yourself Second half of 2024 v1.12 Introduced the terraform query and list blocks, finally enabling automatic resource discovery But this feature has to be implemented by each Provider, and many Providers simply have not caught up The community has been complaining about this for years, yet the question ‘I already have a pile of existing resources — how do I import them into Terraform?’ was never taken seriously. As an Infrastructure as Code tool, Terraform’s design philosophy is declarative configuration and idempotence 5. Hashi Corp. firmly believes that ‘the state declared in code is the only source of truth, so resources should be created from scratch with Terraform’. But in reality, most companies already have a large amount of legacy infrastructure. Having resources first and code later is the norm. Hashi Corp. acknowledged this contradiction very late; the terraform query in v1.12 was really the first time the official tooling took the issue seriously — though as for Provider ecosystem support\u0026hellip; well, it has been rough.\nTerraform file structure Terraform’s file structure is very simple. When it runs, the main program blindly reads all .tf files in the working directory. As long as the required information is present, you can name the files whatever you like. For example, my file structure looks like this:\n❯ tree -a -I .git . ├── .editorconfig ├── .github │ ├── dependabot.yml │ └── workflows │ ├── terraform-apply.yml │ ├── terraform-plan.yml │ └── your-fork.yml ├── .gitignore ├── .terraform.lock.hcl ├── cf_dns_zones.tf ├── cf_tunnel.tf ├── dns_example_com.tf ├── dns_example_net.tf ├── dns_example_top.tf ├── dns_example_cn.tf ├── main.tf # Basic configuration (terraform block) ├── moved.tf # Moved resources ├── provider.tf # Configuration for each Provider ├── README.md ├── rename_resources.ps1 └── variables.tf # Custom variablesmain.tf is used to store the basic configuration:\nterraform { required_providers { cloudflare = { source = \"cloudflare/cloudflare\" version = \"~\u003e 5\" } tencentcloud = { source = \"tencentcloudstack/tencentcloud\" version = \"\u003e= 1.81.43\" } } }provider.tf is used to store the configuration for each Provider:\nprovider \"cloudflare\" {} provider \"tencentcloud\" {}It is left empty here because we can pass credentials through environment variables instead of writing them directly into the configuration file. For example, the Cloudflare Provider accepts CLOUDFLARE_API_TOKEN as an alternative to the api_token variable.\nvariables.tf is used to declare custom variables:\nvariable \"cloudflare_zone_example_com\" { description = \"Cloudflare zone ID for example.com\" type = string } variable \"cloudflare_zone_example_top\" { description = \"Cloudflare zone ID for example.top\" type = string }These variables can be passed in through environment variables prefixed with TF_VAR_. Terraform will also automatically read variables from the terraform.tfvars file. The main purpose of variables is to be referenced from other configuration files, for example:\nresource \"cloudflare_dns_record\" \"example_cname\" { content = \"${cloudflare_zero_trust_tunnel_cloudflared.Production_Tunnel.id}.cfargotunnel.com\" name = \"example.example.com\" proxied = true tags = [] ttl = 1 type = \"CNAME\" zone_id = var.cloudflare_zone_id_example_com # the cloudflare_zone_example_com variable is referenced here settings = { flatten_cname = false } }With these basic settings in place, we can run terraform init to initialise the Terraform environment and lock dependency versions. After that, the rest is just declaring our resources.\nImporting existing resources into Terraform As mentioned earlier, Terraform uses state management. This is great, but it also creates a problem during initialisation: by default, Terraform’s state file is obviously empty.\nAt this point, what we need to do is import the state of the existing resources from the cloud provider into Terraform, so that Terraform can take over and manage our infrastructure seamlessly. As you have probably noticed from the earlier rant, resource import in Terraform is a sore point. Taking DNS records hosted on Cloudflare as an example, if the Provider supports it, you can use terraform query, introduced in Terraform 1.12. In most cases, though, Providers have not implemented this new feature, and Cloudflare is one of those that does not support it.\nFortunately, even if Hashi Corp. has not taken the problem seriously, companies that use Terraform heavily to manage infrastructure have come up with their own solutions. Cloudflare maintains an import tool called cf-terraforming, which saves a lot of manual effort. First, install cf-terraforming. This tool is written in Go, so you either need a Go environment to install it, or you can place the official precompiled binary into $PATH and make it executable.\ngo install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latestThe tool is fairly straightforward to use. First, you need the following environment variables:\n# If you use an API Token export CLOUDFLARE_API_TOKEN='Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j' # If you use an API Key export CLOUDFLARE_EMAIL='user@example.com' export CLOUDFLARE_API_KEY='1150bed3f45247b99f7db9696fffa17cbx9' # Specify the zone ID of the domain to import; this is not needed for account-level resources (such as Cloudflare Tunnel) export CLOUDFLARE_ZONE_ID='81b06ss3228f488fh84e5e993c2dc17' 💡 Tip The commands here assume you are using Bash. If your shell is not compatible with Bash syntax, you will need to adjust them. For example, in PowerShell on Windows, the syntax for setting an environment variable is:\n$env:CLOUDFLARE_API_TOKEN='Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j' Usually, you only need to set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID. When creating the API token in the console, remember to grant it the necessary permissions. In this case, we are only importing DNS records, so giving it permission to edit zone DNS is enough.\nRight, all the preparation is done. Now we can start generating the configuration files.\nFirst, import the domain configuration in the account, namely cloudflare_zone:\ncf-terraforming generate \\ --key $CLOUDFLARE_API_KEY \\ --resource-type \"cloudflare_zone\" \u003e zone.tfThis step generates a file called zone.tf in the current directory, containing content in the following format:\nresource \"cloudflare_zone\" \"REDACTED\" { name = \"REDACTED\" paused = false type = \"full\" vanity_name_servers = [] account = { id = \"REDACTED\" name = \"REDACTED\" } } resource \"cloudflare_zone\" \"REDACTED\" { name = \"REDACTED\" paused = false type = \"full\" vanity_name_servers = [] account = { id = \"REDACTED\" name = \"REDACTED\" } }At this point, the domain resources have been imported, but their internal configuration has not. Next, import the DNS records under the domain:\ncf-terraforming generate \\ --zone $CLOUDFLARE_ZONE_ID \\ --key $CLOUDFLARE_API_KEY \\ --resource-type \"cloudflare_dns_record\" \u003e\u003e dns.tfThis step generates a configuration file called dns.tf in the current directory, containing content in the following format:\nresource \"cloudflare_dns_record\" \"terraform_managed_resource_5deb14xxxxxb629bf123xxxxxxxc8f_0\" { content = \"67.24.33.108\" name = \"example.example.com\" proxied = true tags = [] ttl = 1 type = \"A\" zone_id = \"81c7f2de8dfxxxxxx52629xxxxxxfc\" settings = {} } resource \"cloudflare_dns_record\" \"terraform_managed_resource_89xxxxx0bf9cxxxxxx9a_1\" { content = \"35.27.108.33\" name = \"terraform.example.com\" proxied = true tags = [] ttl = 1 type = \"A\" zone_id = \"8xxxxxx7644e428526xxxxxx\" settings = {} }If you need to import multiple domains, just set the CLOUDFLARE_ZONE_ID environment variable separately each time and rerun the command.\nThe generated configuration file here can be used directly — it is the Terraform configuration file we need later. However, at this point we have only generated the configuration file; Terraform’s state is still empty. If you run terraform apply now, Terraform will blindly treat all the declarations we just imported as new resources and throw a pile of ‘Alredy Exists’ errors. So next, we need to import the generated configuration into Terraform’s terraform.tfstate state.\nTerraform introduced the import block in version 1.5, which is much more modern than typing import commands one line at a time. The process is to generate an .tf file containing import blocks. The next time you run terraform apply, Terraform will automatically perform the import for you.\nGenerate the import blocks for cloudflare_zone:\ncf-terraforming import \\ --resource-type \"cloudflare_zone\" \\ --modern-import-block \\ --key $CLOUDFLARE_API_KEY \\ --zone $CLOUDFLARE_ZONE_ID \u003e\u003e import.tfGenerate the import blocks for cloudflare_dns_record:\ncf-terraforming import \\ --resource-type \"cloudflare_dns_record\" \\ --modern-import-block \\ --key $CLOUDFLARE_API_KEY \\ --zone $CLOUDFLARE_ZONE_ID \u003e\u003e import.tfThis step generates import.tf in the current directory. It contains the import information needed to tell Terraform which cloud-provider resource ID corresponds to each resource block generated in the previous step. This ID is the code the cloud provider uses internally to identify a resource. You would not normally see it in the control panel; you only get it by requesting it through the API. Terraform needs this ID during import to confirm that the local definition matches the cloud resource, ensuring strict idempotence.\nRight, now let us run terraform plan:\n$ terraform plan cloudflare_dns_record.minio_a: Refreshing state... [id=xxxxxxxxxxx53] cloudflare_zero_trust_tunnel_cloudflared_config.raspberrypi: Refreshing state... ...... Terraform will perform the following actions: # cloudflare_dns_record.terraform_managed_resource_0 will be imported resource \"cloudflare_dns_record\" \"terraform_managed_resource_REDACTED_0\" { content = \"67.24.33.108\" created_on = \"2026-04-08T10:18:12Z\" id = \"5deb14c21xxxxxxx20f1c8f\" meta = jsonencode({}) modified_on = \"2026-04-08T10:18:12Z\" name = \"example.example.com\" proxiable = true proxied = true settings = {} tags = [] ttl = 1 type = \"A\" zone_id = \"REDACTED\" } # cloudflare_dns_record.terraform_managed_resource_1 will be imported resource \"cloudflare_dns_record\" \"terraform_managed_resource_89c149exxxxxxxxxxxba13xxxxxa_1\" { content = \"35.27.108.33\" created_on = \"2026-04-08T10:17:54Z\" id = \"89cxxxxxxxxxxxxxxxxxx09a\" meta = jsonencode({}) modified_on = \"2026-04-08T10:17:54Z\" name = \"terraform.example.com\" proxiable = true proxied = true settings = {} tags = [] ttl = 1 type = \"A\" zone_id = \"81xxxxxxxxxxxxxxxxxxxxxfc\" } Plan: 2 to import, 0 to add, 0 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run \"terraform apply\" now.If the numbers for Add, Change, and Destroy are all 0, then the import has gone correctly. Just run terraform apply --auto-approve, and the resources will be imported.\n$ terraform apply --auto-approve cloudflare_dns_record.push_a: Refreshing state... [id=REDACTED] Terraform will perform the following actions: # cloudflare_dns_record.terraform_managed_resource_REDACTED_0 will be imported resource \"cloudflare_dns_record\" \"terraform_managed_resource_REDACTED_0\" { content = \"67.24.33.108\" created_on = \"2026-04-08T10:18:12Z\" id = \"REDACTED\" meta = jsonencode({}) modified_on = \"2026-04-08T10:18:12Z\" name = \"example.example.com\" proxiable = true proxied = true settings = {} tags = [] ttl = 1 type = \"A\" zone_id = \"REDACTED\" } # cloudflare_dns_record.terraform_managed_resource_REDACTED_1 will be imported resource \"cloudflare_dns_record\" \"terraform_managed_resource_REDACTED_1\" { content = \"35.27.108.33\" created_on = \"2026-04-08T10:17:54Z\" id = \"REDACTED\" meta = jsonencode({}) modified_on = \"2026-04-08T10:17:54Z\" name = \"terraform.example.com\" proxiable = true proxied = true settings = {} tags = [] ttl = 1 type = \"A\" zone_id = \"REDACTED\" } Plan: 2 to import, 0 to add, 0 to change, 0 to destroy. cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Importing... [id=REDACTED/REDACTED] cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Import complete [id=REDACTED/REDACTED] cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Importing... [id=REDACTED/REDACTED] cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Import complete [id=REDACTED/REDACTED] Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.State storage and continuous integration One of IaC’s core strengths is that it makes Git-based collaboration and CI easy, but before that there is another problem to solve: where exactly should terraform.tfstate live? Nobody wants to painstakingly import state for each environment only to lose it every time they switch.\nTerraform currently supports the following state backends:\nlocal remote azurerm consul cos gcs http Kubernetes oci oss pg s3 If you do not have any special requirements, you can choose s3 as I do. Cloudflare R2 has a free tier, after all, so you might as well use it.\nterraform { backend \"s3\" { bucket = \"terraform\" key = \"terraform.tfstate\" region = \"auto\" endpoints = { s3 = \"https://REDACTED.r2.cloudflarestorage.com\" } # R2 does not need this AWS validation skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true skip_requesting_account_id = true use_path_style = true } }For the S3 backend, it is recommended to store credentials in the two environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:\nexport AWS_ACCESS_KEY_ID='REDACTED' export AWS_SECRET_ACCESS_KEY='REDACTED'Once configured, run terraform init -migrate-state and the state will be stored successfully in the cloud. After that, no matter where you edit the configuration or run terraform apply, you will not need to worry about Terraform state getting out of sync.\nNext comes the GitHub CI configuration. It is actually very simple: on each git push, just trigger terraform init, terraform fmt, and terraform apply. Here is my .github/workflows/apply.yml:\nname: \"Terraform Apply\" on: push: branches: - main env: TF_IN_AUTOMATION: \"true\" CLOUDFLARE_API_TOKEN: \"${{ secrets.CLOUDFLARE_API_TOKEN }}\" AWS_ACCESS_KEY_ID: \"${{ secrets.AWS_ACCESS_KEY_ID }}\" AWS_SECRET_ACCESS_KEY: \"${{ secrets.AWS_SECRET_ACCESS_KEY }}\" TF_VAR_cloudflare_zone_id_example_com: \"${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}\" TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }} jobs: terraform: name: \"Terraform Apply\" runs-on: ubuntu-latest permissions: contents: read concurrency: group: terraform-apply cancel-in-progress: false steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Terraform uses: hashicorp/setup-terraform@v4 - name: Terraform Init run: terraform init -input=false - name: Terraform Apply run: terraform apply -input=false -auto-approveFor PRs, CI should automatically attach the output of terraform plan to each PR:\nname: Terraform Plan on: pull_request: paths: - \"**/*.tf\" - \".github/workflows/terraform-plan.yml\" permissions: contents: read pull-requests: write env: TF_IN_AUTOMATION: \"true\" CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} TF_VAR_cloudflare_zone_id_example_com: \"${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}\" TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }} jobs: plan: name: Terraform Plan runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: hashicorp/setup-terraform@v4 - name: Terraform fmt id: fmt run: terraform fmt -check -recursive continue-on-error: true - name: Terraform Init id: init run: terraform init -input=false - name: Terraform Validate id: validate run: terraform validate -no-color - name: Terraform Plan id: plan run: terraform plan -input=false -no-color continue-on-error: true - name: Post Plan to PR uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const botComment = comments.find(c =\u003e c.user.type === 'Bot' \u0026\u0026 c.body.includes('') ); const planOutput = `${{ steps.plan.outputs.stdout }}`.substring(0, 65000); const body = ` #### Terraform Plan | Step | Result | | -------- | --------------------------------- | | fmt | \\`${{ steps.fmt.outcome }}\\` | | init | \\`${{ steps.init.outcome }}\\` | | validate | \\`${{ steps.validate.outcome }}\\` | | plan | \\`${{ steps.plan.outcome }}\\` | Expand Plan details \\`\\`\\`terraform ${planOutput} \\`\\`\\` `; if (botComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: botComment.id, body }); } else { await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body }); } - name: Fail if plan failed if: steps.plan.outcome == 'failure' run: exit 1 Ongoing workflow At this point, Terraform’s initial ‘takeover’ is complete, and after that you move into day-to-day maintenance. At this stage there are really only three things to do:\nCreate new resources Modify existing resources Delete resources that are no longer needed There are only two common operations: terraform plan and terraform apply. If you are working alone, you can usually just commit small changes directly. If you are working in a team, though, each change should follow the principle of using a PR whenever possible rather than committing directly.\nCreating infrastructure Suppose you want to add a new DNS record, create a new Tunnel, or create a new object storage bucket. The process looks like this:\nflowchart TD C1[Create a new branch such as feat/add-minio-record] --\u003e C2[Add a new resource block] C2 --\u003e C3[terraform fmt + validate] C3 --\u003e C4[terraform plan] C4 --\u003e C5{Only the expected new resources?} C5 -- No --\u003e C6[Fix the configuration and rerun plan] C6 --\u003e C4 C5 -- Yes --\u003e C7[Submit PR] C7 --\u003e C8[After merge, CI apply]The ideal plan output is:\nX to add 0 to change 0 to destroy If you only meant to add something but to destroy appears, do not rush in. Usually it means a bad reference, a wrong variable, or that you accidentally changed a resource address. Check carefully to see what went wrong.\nModifying and deleting infrastructure The process for modifying resources is similar to creating them, but there is one extra step: evaluate whether the change will trigger a rebuild.\nThat is because many Provider fields are ForceNew. You think you are only changing one field, and Terraform replies: ‘Right then, delete and recreate it.’ In a DNS scenario like this, that is not a huge issue, but for something like a cloud instance, deleting and recreating it can obviously cause real damage.\nIt is best to follow this order:\nflowchart TD M1[Modify .tf] --\u003e M2[terraform plan] M2 --\u003e M3{replace/destroy appears?} M3 -- No --\u003e M7[Confirm the scope of impact] M7 --\u003e M8[terraform apply] M3 -- Yes --\u003e M4[Pause and review the change] M4 --\u003e M5[Add lifecycle protection if needed] M5 --\u003e M6[Schedule a maintenance window] M6 --\u003e M8For production environments, being a bit slower when creating or modifying things is not a big problem. What matters most is correctness. Go a bit slower; do not make mistakes. IaC is not a speed contest — it is about predictability.\nIf you are deleting resources instead (for example, retiring a DNS record or cleaning up an abandoned Tunnel), follow the process below 6:\nflowchart TD D1[Confirm the resource is no longer needed; check dependencies in services, monitoring, and scripts] --\u003e D2[Delete the resource block or adjust count/for_each] D2 --\u003e D3[terraform plan] D3 --\u003e D4{Does to destroy match expectations?} D4 -- No --\u003e D5[Revert the change and continue checking dependencies] D5 --\u003e D1 D4 -- Yes --\u003e D6[Prepare a rollback plan and choose a low-traffic window] D6 --\u003e D7[PR approved] D7 --\u003e D8[terraform apply] D8 --\u003e D9[Availability check after deletion]Suggestions for day-to-day collaboration Put credentials in environment variables or CI secrets; do not write them into .tf or the repository Enable protection policies for critical resources to prevent accidental deletion Split directories by resource type Run terraform plan regularly to check for and correct infrastructure drift 7, so you do not end up with manual dashboard changes by mistake Although this workflow may look a bit cumbersome, every change is recorded, auditable, and reversible — and most importantly, reproducible. That is where IaC delivers its real value.\nReferences Using Terraform to manage DNS records on CloudFlare - Candinya Automate Terraform with GitHub Actions Query configuration files list block reference cloudflare/cf-terraforming Import Cloudflare resources Cloudflare Provider - Terraform Registry Infrastructure as Code refers to a method of defining and deploying the required infrastructure using machine-readable configuration files.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nEach provider uses different field names and formats in its configuration files, but these can be converted fairly easily with a script.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nOne especially important thing to note is that the state file may contain sensitive information stored in plain text, such as database passwords and API keys, so you must never commit the .tfstate file to a public code repository.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHashiCorp Configuration Language, a declarative configuration language developed by HashiCorp, designed to balance machine readability with human readability.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nIdempotence means that when a computer system or interface receives the same request multiple times, the effect is the same as if it had been executed once. No matter how many times it runs, the system’s final state remains consistent.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nIf you only want Terraform to stop managing a resource, rather than actually destroying it in the cloud, you should use the terraform state rm command instead of deleting the resource block from the code and then running apply, otherwise the real resource in the cloud will be destroyed as well.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nInfrastructure drift refers to a situation where infrastructure is modified in reality through non-IaC means such as clicking around in a console, causing the actual state to differ from the state declared in code.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2026-04-09T07:13:35Z","image":"/2026/04/iac-with-terraform/cover_hu_a8e83f97ed61569c.webp","permalink":"/en/2026/04/iac-with-terraform/","title":"First impressions of Terraform IaC"},{"content":"Work, work, until the day we die.\nTutoring others during the winter break (see here), I barely had a moment to catch my breath. The Lunar New Year was technically a holiday, but in reality, it was just a different form of work—endless socialising and the exhaustion of travelling. No sooner had I settled back into my flat than I was throwing myself headfirst into teaching again. To be honest, tutoring is far from easy. When chatting with my mates who are also teaching assistants at the same agency, we all joke that getting through a lesson is an absolute nightmare: the students can\u0026rsquo;t remember what they\u0026rsquo;ve tried to rote-learn, their basic arithmetic is completely absent, key concepts are constantly muddled, and trying to hold a normal, flowing conversation is virtually impossible. To be fair, you can\u0026rsquo;t really blame them; if anyone\u0026rsquo;s at fault, it\u0026rsquo;s the educational environment they\u0026rsquo;ve been subjected to. Every family has its own underlying struggles. For various reasons, most of these students ended up taking alternative vocational pathways rather than facing the rigorous gauntlet of the standard university entrance exams. Some barely even managed to scrape through secondary school. Ultimately, they just want to secure a job, which is why they are willing to pay for our one-on-one sessions.\nInitially, I worked as a teaching assistant at an agency, but I eventually decided to go freelance. This not only bumped up my earnings but also saved me a fair bit of lesson preparation time. I\u0026rsquo;d teach every day from 7:30 AM to 11:00 AM for 400 RMB. An hourly rate crossing the 100 RMB mark might seem quite decent, but when you factor in the students\u0026rsquo; extremely poor foundational knowledge, I can frankly say I earn every single penny with a clear conscience. Honestly, it is incredibly hard-earned cash.\nGetting back to the point, the new term has started, and I have to plunge straight back into my own academic commitments without missing a beat. It genuinely feels like I am perpetually busy. What I call \u0026ldquo;resting\u0026rdquo; is essentially just swapping to a slightly less taxing activity. If I\u0026rsquo;m exhausted from teaching, I\u0026rsquo;ll go home and unwind with a bit of Vibe Coding; if staring at the screen drives me up the wall, I\u0026rsquo;ll switch to tidying up the flat and doing the chores. When it comes to everyday drive and our finite human energy, I can acutely feel the gulf between different types of people. Some are naturally gifted, bursting with boundless energy. They churn out project after project, publish paper after paper, and maintain incredibly high productivity. It seems the only thing holding them back is the number of hours in a day, rather than any lack of stamina or motivation. Meanwhile, most of us are mere mortals like myself. Even if we desire that kind of lifestyle, our spirits are willing but our flesh is weak. Yet, seeing the sheer output of that first group inevitably brews anxiety, leaving us with no choice but to push our limits and treat ourselves as \u0026ldquo;practical perpetual motion machines\u0026rdquo; 1.\nThe pressure-cooker educational system we’ve been put through since childhood has genuinely warped us. There\u0026rsquo;s a famous quote by a well-known intensive tuition teacher here: \u0026ldquo;How on earth can you sleep at your age?\u0026rdquo; Though originally meant to relentlessly tell off students nodding off in his lectures, it has subtly and profoundly conditioned our entire generation, driving us into a state that\u0026rsquo;s almost pathological. In life, there is no ultimate \u0026lsquo;finish line\u0026rsquo;. In primary school, you’re scrambling to secure a spot at a top secondary school; then you’re fighting to pass your GCSE-equivalents; later, you’re battling through the brutal university entrance exams. Once at university, the rat race continues as you desperately try to secure a master\u0026rsquo;s spot, a PhD, a stable civil service role, or simply a respectable corporate job. And even after you\u0026rsquo;ve finished your education and entered the workforce, you\u0026rsquo;re faced with endless promotions, performance metrics, and the daily grind of paying the bills. Work, work, until the day we die. Hoping to cross that final finish line and rest on your laurels forever? I\u0026rsquo;m afraid that’s simply impossible.\nDespite knowing all this perfectly well, I still feel a dreadful sense of emptiness whenever I actually stop to rest. And so, I remain perpetually busy, endlessly working. I give my body the bare minimum of sleep it requires and use physical exercise to keep my hormones in check. My idea of a \u0026ldquo;break\u0026rdquo; is just switching to a lighter task that I enjoy. Having been so deeply conditioned by a high-stakes education system and relentless meritocracy, I suspect this way of living won\u0026rsquo;t be changing for a very long time.\nLooking on the bright side, however, this recent stint of hard graft has meant I\u0026rsquo;ve earned enough to cover my own living expenses for the entire term. I\u0026rsquo;ve even got enough left over to buy my mum a new mobile phone for her birthday. Suddenly, being perpetually busy doesn\u0026rsquo;t seem quite so terrible after all.\nI am borrowing the engineering distinction between ideal and practical current sources here to draw a parallel between an \u0026ldquo;ideal perpetual motion machine\u0026rdquo; and a practical one. The former is exactly what it says on the tin, whilst the latter refers to us ordinary folk, pushing our flesh-and-blood bodies to the absolute limit just to approximate it.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2026-03-13T23:14:26+08:00","permalink":"/en/2026/03/busy-for-eternity/","title":"Perpetually Busy"},{"content":"Recently, I’ve been doing one-on-one coaching at a private training agency for students preparing for the second round of the State Grid Corporation of China (SGCC) recruitment exams. I teach for three and a half hours every evening, and they pay me 200 RMB (approx. £22).\nThe fact that the pay is abysmal isn\u0026rsquo;t even the biggest issue. The real problem lies in how they charge the students. They offer two main packages: the \u0026ldquo;Excellence Plan,\u0026rdquo; which costs a staggering 120,000 RMB (£13,000) but includes unlimited one-on-one sessions; and the \u0026ldquo;Contract Class,\u0026rdquo; which costs 70,000 RMB (£7,500), but with a catch—students must pay an additional 450 RMB (£50) for every one-on-one session.\nYesterday, I arranged a private session with one of the students in the afternoon. My plan was to fly under the agency\u0026rsquo;s radar; the student would pay me 300 RMB directly. It seemed like a win-win: I’d make an extra hundred, and they’d save 150. However, the student didn\u0026rsquo;t quite catch on. When they asked the coordinator for leave, they explicitly mentioned they were \u0026ldquo;getting extra tutoring from me.\u0026rdquo; The agency immediately \u0026ldquo;benevolently\u0026rdquo; stepped in to help me collect the 450 RMB fee from the parents.\nIt didn\u0026rsquo;t stop there. When it came time to calculate my earnings, the agency classified this as an \u0026ldquo;extra lesson.\u0026rdquo; They claimed I had \u0026ldquo;misused\u0026rdquo; my afternoon lesson-preparation time to teach a student not enrolled in the \u0026ldquo;Excellence Plan\u0026rdquo;. For those three and a half hours of work, they only gave me 120 RMB.\nTo put it simply:\nStudent pays 450 → Agency pockets 330 → I receive 120\nNaturally, I’m annoyed that the 300 RMB I should have earned was whittled down to 120. But beyond my personal loss, what truly unsettles me is the predatory nature of this business. Most of these students are vocational college graduates from humble backgrounds. In China’s job market, they already face significant discrimination, and they are doing everything in their power to climb the social ladder. They have staked everything—tens, or even hundreds of thousands of RMB, potentially their family’s entire life savings—on these courses to get a stable job at the State Grid. They believe they are buying a \u0026ldquo;cure\u0026rdquo; to change their fate, unaware that the medicine is being served with a side of their own lifeblood. Meanwhile, as the one actually doing the hard work of teaching, I receive a smaller cut than the agency that simply sits back and collects the rent.\nIn Chinese literature, we call this \u0026ldquo;eating buns soaked in human blood\u0026rdquo;—profiting off the desperation and suffering of others.\nOn this single transaction, the agency made a net profit of 330 RMB for doing absolutely nothing. It is, quite frankly, unacceptable. I have already advised the student to be more \u0026ldquo;flexible\u0026rdquo; when asking for leave in the future. I’ve also had a word with their coordinator, making it clear she should look the other way regarding our private arrangements. Given that she’s also a working-class employee with performance targets tied to student results, I doubt she’ll be foolish enough to jeopardize her own KPIs.\nThat said, this agency had better watch its step. The joke I made during \u0026ldquo;lesson prep\u0026rdquo;—about the teaching assistants forming a temporary union and going on strike for better pay—might just become a self-fulfilling prophecy one day.\nI can’t help but wonder who the real \u0026ldquo;ruthless capitalists\u0026rdquo; are meant to be. This agency seems to have mastered the art of the blood-soaked business quite effectively.\n","date":"2026-02-11T17:06:57+08:00","permalink":"/en/2026/02/buns-soaked-in-human-blood/","title":"Buns Soaked in Human Blood"},{"content":"What a busy year it has been. Before I knew it, we reached the end of the year again. There was indeed a lot going on—plenty of rushing about—but I’ve managed to achieve some significant milestones. I take up my pen once again to write this year-end summary as a testament to the year that was.\nThe Grid: The Final Destination for Electrical Engineers Running Ragged for a Job After a full year of suspension, I returned to my studies on schedule to begin my final year of university, officially becoming a \u0026ldquo;fresh graduate\u0026rdquo;. Consequently, job hunting naturally moved to the top of my agenda. As an Electrical Engineering student from a university with historical ties to the former Ministry of Electric Power, finding a job isn\u0026rsquo;t exactly impossible. However, saying it\u0026rsquo;s easy wouldn\u0026rsquo;t be accurate either. given the current climate: slowing economic growth, an uncertain international situation, reduced hiring demand, and the \u0026ldquo;rat race\u0026rdquo; becoming increasingly intense everywhere.\nIn September, I attended a graduate job fair, handed out a few CVs in person, and applied for four or five roles online. I only received interview invitations from two companies. I experienced primarily that the prestige of being from a \u0026ldquo;former Power Ministry affiliated university\u0026rdquo; is really only recognised within the power system itself1. The employment market is just the way it is right now. Apart from the power system, decent-paying jobs are not easily found by students from \u0026ldquo;standard universities\u0026rdquo; (non-elite institutions) like ours. But in comparison, at least they still accept our CVs; for some other majors, companies won\u0026rsquo;t even look at them.\nWritten Exams for the Two Grid Giants Working for the Power Grid represents the most relevant and stable career path for my major. The largest employers in our field—State Grid (SGCC) and China Southern Power Grid (CSG)—both require candidates to pass a written exam to qualify for an interview. The exam covers eight subjects in total: Circuit Theory, Electrical Machines, Power System Analysis, Power System Protection, Power Electronics, High Voltage Engineering, Electrical Equipment \u0026amp; Main Systems (called \u0026ldquo;Electrical Part of Power Plants\u0026rdquo; at our uni), and the Administrative Aptitude Test (essentially a civil service competency test).\nThe first seven are technical subjects, while the Aptitude Test covers a bizarre range of topics, including corporate culture, which you simply have to memorise. Current affairs and politics(Yes, this is also a subject) are mandatory, but what do they test? Extremely recent events—editorials published in Qiushi (the Party\u0026rsquo;s2 theoretical journal) just days ago appeared directly in the exam a few days later. I was honestly speechless.\nThis year\u0026rsquo;s exam was strange, particularly regarding difficulty. Historically, there are always a few calculation questions, and Circuit Theory usually tests flexible problem-solving ability. Based on this intelligence, I had mastered numerous calculation types—electrical computations, numerical settings, equivalent transformations—and felt quite confident. The result? The first batch of State Grid exams didn\u0026rsquo;t feature a single calculation question. The Aptitude Test was simple enough for primary school children, and the technical part tested pure concepts. If it weren\u0026rsquo;t for the CSG exam still requiring practical application, my study efforts would have been practically wasted. Unsurprisingly, classmates who were strong at calculations and did well in mock tests essentially bombed this exam. Conversely, those with less impressive academic records who struggled with maths managed to get decent scores by rote learning. At the end of the day, it\u0026rsquo;s just a corporate recruitment test; outsiders can never guess how they\u0026rsquo;ll set the questions. I did what I had to do, and the final hiring outcome was excellent, which is all that matters.\nInterviews, Interviews, and More Interviews In December, I rushed to four interviews. For the CSG Guangxi Power Grid Ltd., the only interview location was Guilin, forcing me to skive off classes travel there. This resulted in my final trip of the year. Visiting Guilin twice in two years felt quite nice.\nThe State Grid interview made me a bit nervous. To prevent interview questions from leaking to subsequent candidates3, they employed fully closed-loop management. The process was long—mostly spent waiting in a holding room. All electronic devices were sealed away starting at 1:30 PM. After an hour-long psychometric test, the long wait began. They screened the film The Battle at Lake Changjin to keep us entertained, and provided tea and snacks. Dressed in suits and sitting upright, silently reciting my one-minute self-introduction until I knew it backwards, nervously waiting while watching the movie—it\u0026rsquo;s a flavour of anxiety you only understand if you\u0026rsquo;ve been there. Looking back, it was actually quite interesting.\nWith the experience (and Offer) from the State Grid interview under my belt, the subsequent Southern Power Grid (CSG) interview was far less nerve-wracking. The CSG format differs from the State Grid. State Grid uses a semi-structured, double-blind interview: the first candidate draws a set of questions from sealed envelopes, and subsequent candidates answer the same ones, perhaps with a few follow-up queries. The interviewers cannot see your CV and can only score you based on the ID number you drew; you are not allowed to state your name or background. CSG, however, is interviewer-led. They ask various questions based on your CV, including technical and supplementary questions. The difficulty depends entirely on the interviewer\u0026rsquo;s mood. For instance, during my Hainan Power Grid interview, the technical interviewer drilled down relentlessly into my internship experience with incredibly detailed technical questions. In contrast, the technical questions at the Guangxi Power Grid interview were much friendlier, involving basic switching operations and lightning protection facilities.\nThe Bittersweet Grid Training Most people taking the Grid exams sign up with a training institute, and I was no exception. As early as last November, I methodically enrolled in a national cram school. They had two campuses; I chose the one further out in the suburbs since the rent was cheaper.\nThe training schedule was intense and the workload heavy. The summer session involved 41 consecutive days of classes with only three rest days in between. Mobile phones had to be handed in before class every day. Fortunately, iPads were allowed for note-taking purposes, which gave me a rare opportunity to slack off occasionally.\nActually, during that period, I found a rare chance to focus entirely on one thing. Apart from classes, I didn\u0026rsquo;t have to worry about anything else. So, while my body was tired, my \u0026ldquo;mind\u0026rdquo; wasn\u0026rsquo;t actually that weary. Now that I\u0026rsquo;m back at university, having to worry about big and small matters alike, my body isn\u0026rsquo;t as tired, but my \u0026ldquo;mind\u0026rdquo; is far more exhausted than before. Coupled with a recent cold and cough, sleeping 12 hours a day hasn\u0026rsquo;t brought much improvement. I can only wait until everything is sorted, then take leave, go home, and get some proper rest.\nRetracing Steps Yongzhou I returned to Yongzhou early this year to visit relatives. My grandmother is nearly 90 years old but still quite hale and hearty; visiting often is simply the right thing to do. She has looked after me since I was a child, and my gratitude is beyond words. I often feel at a loss regarding how to repay her, so visiting Yongzhou at least once a year is my way of fulfilling some duty as a grandson.\nAs for Yongzhou itself, it’s still the same; nothing has changed. After all, this isn\u0026rsquo;t an era of economic explosion, so how could a small city like this change much? Stagnation isn\u0026rsquo;t necessarily a tragedy, and change isn\u0026rsquo;t always a blessing. Like after the Cultural Revolution in the last century when everything changed, but everything also became unrecognisable. The commercial complex that was bustling when I was a child now basically only has clothing shops open on the ground floor. The cinema and old arcade closed long ago; the floors converted into dining areas are now dim and dreary. Any new restaurants, whether chains or independent ventures, inevitably fail after the initial hype washes away.\nHong Kong My trip to Hong Kong this time was just for fun, with no special objective other than mooching off the hotel room during my mum\u0026rsquo;s business trip. Of course, I opened a Bank of China account4 while I was there. The whole itinerary was rather unremarkable. The only unique part was staying in a flat in Wan Chai for a few days, where I could buy groceries and cook for myself (or eat \u0026ldquo;two-dish rice\u0026rdquo; takeouts), experiencing a slice of working-class life.\nGuilin The main reason for going to Guilin this time was the interview for the Guangxi branch of the Southern Power Grid. Naturally, I also took the opportunity to eat Chunji Roast Goose and Haitian Rice Noodle Rolls. The only downside was that it rained during the two days of the interview, leaving me with little inclination to walk around or sightsee. However, I ate my fill of rice rolls and roast goose—tasted great, would come again.\nOld for New My previous computer was an i5 12450H + RTX 3060 Laptop configuration. I’d used it for over two years, and just a few months ago, I bought two sticks of Crucial5 memory to upgrade it to 32GB. Frankly, there was no issue continuing to use it.\nHowever, memory chip prices were still at a low point at that time, and government trade-in subsidies further reduced the cost of new devices. Combined with the recent launch of the 50-series graphics cards, the price of new laptops was incredibly attractive. Unfortunately, my pockets were shallow, and I didn\u0026rsquo;t have the budget for the latest generation CPU + latest generation GPU combo. I had to settle for the second-best option: an N-1 generation processor paired with the latest generation graphics card6. I\u0026rsquo;ve been using it for nearly half a year now, and the daily experience is excellent, though the howling fan noise really kills the mood. Overall, I consider the laptop a case where the flaws don\u0026rsquo;t obscure the virtues. With storage prices skyrocketing and government subsidies tapering off, I doubt we\u0026rsquo;ll see laptops this cheap again anytime soon.\nMy Cat My cat turned one year old this year (probably)7. Amidst the celebrations, for a male cat, growing from a kitten to an adult means his testicles start doing their job, leading to scenes like this:\nWhile I was away in Hong Kong, this stinky cat actually climbed onto my bed to pee and poop ~~ (probably because my dad didn\u0026rsquo;t scoop the litter)~~. Unforgivable. He had to get the snip!\nThe cat has brought a lot of joy to my life. Aside from the destruction, he is very cute. The sofa at home has been reduced to shreds after a year of claw service. You have to pay a price; that\u0026rsquo;s just how keeping a cat is. Scratch marks, cat hair everywhere during shedding season, and occasional acts of mischief are all part of the package. Since we accept the emotional value the cat brings, we must correspondingly accept these little flaws. I almost view him as family. From that perspective, you instantly forgive these shortcomings. Thinking back, perhaps I really do love him—so much so that I\u0026rsquo;ve started to love even his flaws. Because without them, he wouldn\u0026rsquo;t be a complete cat.\nStill Lifting, Still on Meds Pumping iron, especially leg day, is truly exhilarating. Feeling that sheer release after your muscles exert power is genuinely addictive. During the time I wasn\u0026rsquo;t at the Grid training, I consistently went to the gym, not just enjoying that feeling of release but also building muscle. honestly, not exercising feels terrible.\nI am still taking medication every day. I really don\u0026rsquo;t want to suffer an emotional breakdown amidst my relentless schedule. My original plan to gradually taper off the medication after a year has been indefinitely postponed. Why? Put simply, the pressure from various sources has been significant lately. While staying on meds forever isn\u0026rsquo;t a solution, and I must slowly taper off once things settle, I am far from living a stable, certain life at this point nearing graduation.\nThis period is continuously filled with possibilities and opportunities, but also pressure. The money spent on medicine is a small price to pay, but the \u0026ldquo;shield\u0026rdquo; it provides for emotional stability plays a crucial role.\nNo Time for Gaming As you can tell from my Steam Year in Review, I really didn\u0026rsquo;t have much time for games this year. \u0026ldquo;Stealing moments of leisure from a busy life\u0026rdquo; was the theme. At the end of the year, I downloaded Delta Force. sometimes, after Grid training finished at 10 PM, I\u0026rsquo;d go home and play until midnight. I\u0026rsquo;ve clocked 75 hours so far, and it feels pretty good.\nMy osu! playing essentially dropped off in the second half of the year, though my PP still grew from 3,439pp last year to 4,701pp this year. This game really requires perseverance; if you stop for a while and lose your muscle memory, rehabilitation takes time. Thinking about finishing class at 10 PM, completely drained, and then trying to play a game requiring such intense concentration and reaction speed—well, \u0026ldquo;I simply cannot do it\u0026rdquo;. Ideally, mid-year, my feel for the mouse was at its peak—I could snap to anything. I thought, \u0026ldquo;No one can stop me on my road to 5 digit rank\u0026rdquo;8, yet here I am, still hovering on the edge of 5 digits. Quite ironic, really.\nMy Online Presence In 2025, traffic to my blog rose steadily, with 48.3K UV and 95.4K PV for the year. Traffic primarily came from Google (approx. 15.8K visitors) and Bing (approx. 15.2K visitors).\nMy personal override rules on GitHub garnered 194 Stars, and I gained some new Followers.\nOne of my articles was featured on Hacker News this year, resulting in significant traffic for a few days. Additionally, the translated English and Japanese versions of the blog have attracted quite a few readers, making up a not-insignificant portion of my visitors. So, a phenomenon occurred where, despite my busyness causing a lower update frequency than previous years, traffic actually doubled. I ultimately have to make concessions to life; pressure from living costs, studies, and employment inevitably impacts my willingness and energy to update the blog. On this point, I ask for my readers\u0026rsquo; understanding.\nA New Year Approaches: Wishes for Myself and Everyone The New Year is coming. My biggest wish this year is, of course, to resolve the lingering issues from my suspension and graduate smoothly. Once I truly step into the workforce, I hope to focus more on the technical aspects required by the power system, dealing more with equipment and less with unnecessary, or even harmful, office politics. Hard work is fine as long as it\u0026rsquo;s safe, grounded, and meaningful: safe production, less fuss, more tolerance. I hope that in this upcoming job, I can grow from a fledgling novice into a true engineer.\nI also send my best wishes to you behind the screen: May your anxieties find a resting place, and may your efforts echo back to you in the New Year. May you live your days steadily and warmly at your own pace—you don\u0026rsquo;t have to win every step, as long as you know where you\u0026rsquo;re going. May you and those you care about be healthy and safe, with less senseless exhaustion and more definite happiness. And of course, I hope everyone finds more time to play games outside of study and work.\nAs for me, I will continue learning where I should learn, and hit the road when it\u0026rsquo;s time to move. See you next year.\nRefers to enterprises derived from the former Ministry of Electric Power, i.e., the State Power Corporation prior to 2002, and the State Grid, China Southern Power Grid, and Inner Mongolia Power Grid formed after 2002.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCommunist Party of China, obviously.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nThe first candidate draws a set of questions from sealed envelopes, and subsequent candidates answer the same ones. Therefore, they had to prevent subsequent candidates from knowing their questions in advance.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nBank cards issued by banks in the Chinese mainland are subject to the financial regulations of the mainland. Meanwhile, mainland banks rarely issue bank cards with Visa and MasterCard logos. So, for my overseas payments, I had to open a bank account in Hong Kong.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReferring to Crucial memory, which recently axed its entire consumer product line.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nI bought a Mechrevo Aurora X Pro\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nWhen I picked him up last year, he was still a tiny kitten.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRefers to the number of digits in the ranking. For example, rank #114514 is 6 digits, while #11451 is 5 digits. Obviously, the fewer digits, the higher the player\u0026rsquo;s skill level.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-12-31T19:28:17+08:00","image":"/2025/12/2025-end-of-the-year-summary/cover_hu_546f5de4e94beb95.webp","permalink":"/en/2025/12/2025-end-of-the-year-summary/","title":"2025 Year in Review"},{"content":"I have been rather occupied of late, yet in my spare moments, words remain my sanctuary. My schedule involves classes starting at 8:30 AM and running until evening self-study ends at 9:00 PM. Occasionally, I even put in \u0026ldquo;overtime\u0026rdquo;, not returning home until 10:00 PM. This routine persisted for forty-one consecutive days. Given my mental state under such intensity, returning home to play reaction-based games like osu! was out of the question; I didn\u0026rsquo;t even have the energy to click through a single chapter of a visual novel.\nThe only viable activity was to engage in small talk with an LLM (Large Language Model).\nI must confess, although I maintain a blog, I have never managed to articulate my views or philosophies (or perhaps simply my \u0026ldquo;ideas\u0026rdquo;) in a systematic fashion. I have often thought that this collection of overly broad and disparate viewpoints is difficult to convey through plain narrative. To address this, I attempted various methods, such as using a random event as a cross-section to \u0026ldquo;slice open\u0026rdquo; the tangled mess of my thoughts and depict their internal structure.1 Yet, these methods never allowed me to express my inner voice freely. Writing does not flow effortlessly from my pen; I cannot simply write whenever I wish. Ultimately, a blog is meant for an audience, so when the words wouldn\u0026rsquo;t come, I resorted to writing simple technical posts to garner some search engine traffic.\nThe advent of LLMs, however, has altered this dynamic. All my thoughts can now receive immediate responses in relative privacy. Why have I not written a blog post in so long? Fatigue is naturally a major factor, but the fact that LLMs satisfy a significant portion of my need for expression and validation—without the need for self-censorship—has diluted my desire to blog during this period.\nThere is no doubt that composing long-form text is beneficial for the brain. The impact of LLMs on my ability to write at length is likely akin to, if not greater than, the impact micro-blogging (like Twitter or Weibo) had when it first appeared. Micro-blogging habituated people to fragmented expression and immediate feedback, weakening the ability to construct complex arguments and long-form narratives. LLMs go a step further: they not only provide a channel for expression but also directly engage in providing emotional value, acting as a substitute for a \u0026ldquo;confidant\u0026rdquo;. When posting on social media, one must consider privacy, controversy, or simply whether the content is worth exposing; the publisher invariably exercises some degree of caution.\nLLMs are different. The worst-case scenario for content sent to an API is that it gets used to train the model, not that Sam Altman will turn up at your doorstep the next day. As long as you aren\u0026rsquo;t sharing bank passwords or mentioning your real name, you can safely treat the large model as a close friend regarding current affairs commentary, psychological counselling, and the like. (Naturally, in this specific context, one wouldn\u0026rsquo;t touch domestic Chinese models with a bargepole due to censorship and privacy concerns). Every grumble receives a precise, earnest response—an affirmation akin to that of a soulmate. I fear no other place can offer such treatment.\nWhen a tool stimulates a \u0026ldquo;confidant\u0026rdquo; so perfectly, we may abandon the search for real human connection, or cease striving to become individuals capable of independent thought and self-integration. Writing these words serves as a summary, but also as a reflection. The LLM itself is merely a technology; used well, it helps immensely, but used poorly, the consequences can be severe. The New York Times report \u0026ldquo;Chatbots Can Go Into a Delusional Spiral. Here’s How It Happens.\u0026rdquo; serves as a prime example. Although OpenAI has begun to address the issue of \u0026ldquo;sycophancy\u0026rdquo; in its models, I must acknowledge that while I can treat the model as a confidant and receive so-called \u0026ldquo;understanding and affirmation\u0026rdquo;, I cannot live permanently within that sensation. Joint studies from OpenAI and MIT found a positive correlation between ChatGPT usage and user loneliness: the more users utilised ChatGPT, the lonelier they felt.2 I could also argue that it is precisely because one is lonely that one turns to these LLMs, creating a vicious cycle. An LLM can serve as a seasoning for life, but it must never become the sole sustenance for the soul.\n(To Be Continued)\nThe Worst Way To Spend A Day\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nJoint studies from OpenAI and MIT found links between loneliness and ChatGPT use\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-09-08T23:53:48+08:00","permalink":"/en/2025/09/satisfactory-llm/","title":"My LLM Confidant, and My Writing in Suspension"},{"content":"Like most people, when I started using Clash/Mihomo, I opted for graphical clients based on the core for the sake of convenience. Essentially, these Mihomo clients are very similar: they all use the same backend, with the primary purpose of offering a GUI, managing config files, handling subscription updates, and system proxy settings. With this in mind, I think the usefulness of a Mihomo client largely depends on how well it implements config overrides. Every configuration file obtained or subscribed to through a client goes through various override steps—such as changing the mixed-port, adding sniffer settings, and so on—before it’s handed over to the Mihomo core for startup.\nHowever, not all clients do this basic job adequately. Take ShellCrash, for example—the override mechanism is frequently buggy and feels more like an afterthought. If the client can\u0026rsquo;t even reliably update and tweak config files, it hardly deserves to be called a decent client.\nInstead of depending on these unreliable, black-box Mihomo clients, why not just take direct control? Manage your own configuration files and start the core yourself. This way, you get a cleaner, more reliable, and fully transparent setup.\nWhat You Need to Know Basic Linux skills Familiarity with CLI editors, like nano Have a Substore instance set up (optional) Installing the Mihomo Core On Debian-based systems, you can install precompiled .deb packages. For other systemd-enabled distributions, just download the compiled binary, rename it to mihomo, and place it in /usr/local/bin:\nsudo curl -o /usr/local/bin/mihomo sudo chmod +x /usr/local/bin/mihomoNext, create /etc/systemd/system/mihomo.service:\n[Unit] Description=mihomo Daemon, Another Clash Kernel. After=network.target NetworkManager.service systemd-networkd.service iwd.service [Service] Type=simple LimitNPROC=500 LimitNOFILE=1000000 CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE Restart=always ExecStartPre=/usr/bin/sleep 1s ExecStart=/usr/local/bin/mihomo -d /etc/mihomo ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.targetRun systemctl daemon-reload to refresh systemd. Since there’s no config file yet, you can’t actually start the core, but you can enable it to start on boot using systemctl enable mihomo—ready for when your config is set up.\nConfiguration Files When starting, the core reads /etc/mihomo/config.yaml. With the black-box clients out of the picture, you’re free to customise your config files however you like. Some VPN/proxy providers supply a complete config file you can download with curl.\nFor subscription management, I’m currently using Substore. I’ve previously shared a Quickstart Guide to Substore Subscription Management if you want to refer to that for custom subscription workflows. To support a pure-core setup, my Substore custom override script now includes a full parameter, generating a standalone config file with all necessary ports, unified delay, external-controller settings, and more—ready to use out of the box.\nOnce you’ve set up Substore, just download your config file and start the core:\ncurl -o /etc/mihomo/config.yaml systemctl start mihomoCustom Override Rules If my example config doesn’t work for your needs, that’s no problem—just tweak or add any overrides you like.\nOver the years, I’ve tested various override rules, sometimes even writing my own from scratch. Even with that, there are always edge cases—private domains for stuff like SSH on non-standard ports, for instance—that you won’t want to publish on GitHub. In those cases, you’ll want to append your own private overrides on top.\nThe good thing is that Substore supports chaining multiple override scripts. All you need to do is add your custom script during config generation.\nfunction main(config) { config[\"rules\"].unshift(\"DOMAIN-SUFFIX,xxx,DIRECT\") return config }Note: When overriding rules, always use .unshift() to add to the top of the list—don’t use .push() to add at the end, or they’ll never match (since anything after MATCH is ignored).\nBuilding Your Own Config From Scratch Don’t like my override rules, or prefer not to use Substore at all? No problem! Just refer to the Mihomo Docs and build your own config from the ground up:\nmode: rule mixed-port: 7890 redir-port: 7892 tproxy-port: 7893 allow-lan: true log-level: info ipv6: true external-controller: 127.0.0.1:8000 # secret: yoursecret unified-delay: true routing-mark: 7894 tcp-concurrent: true disable-keep-alive: true # Recommended when proxying mobile devices to prevent excessive standby drain dns: # Your DNS configuration sniffer: # Your domain sniffing config geodata-mode: true geox-url: # Custom GeoData file URL proxy-providers: # Your proxy subscriptions rule-providers: # External routing rules rules: # Proxy rules proxy-groups: # Custom proxy groupsControl Panel / Dashboard Choose whichever dashboard you like. For example, I ran into some odd issues with Mihomo\u0026rsquo;s built-in external-ui. So, I just deploy a separate Docker web dashboard—after all, it’s just a simple web UI. Just make sure your core’s API uses HTTP, and set your web panel to use HTTP as well (if you try HTTPS, you’ll be blocked by CORS policy).\n$ mkdir zashboard \u0026\u0026 cd zashboard $ nvim compose.yml $ docker compose up -dcompose.yml example:\nservices: zashboard: image: ghcr.io/zephyruso/zashboard:latest ports: - \"8899:80\" restart: \"unless-stopped\"Automatic Maintenance With the core running, how do you handle auto-updates for your subscription configs?\nSimple—write a shell script and automate it with cron. For instance, if you want to update your config and restart Mihomo at 3am daily, create /etc/mihomo/auto_update.sh:\n#!/bin/bash # === Config Info === CONFIG_URL=\"\" CONFIG_PATH=\"/etc/mihomo/config.yaml\" BACKUP_DIR=\"/etc/mihomo\" BACKUP_PREFIX=\"config.yaml\" MAX_BACKUPS=7 TMP_PATH=\"/tmp/config.yaml.tmp\" LOG_FILE=\"/var/log/mihomo_update.log\" # === Logging === log() { echo \"$(date '+%F %T') $1\" | tee -a \"$LOG_FILE\" } # === Backup existing config and clean old backups === backup_config() { if [ -f \"$CONFIG_PATH\" ]; then backup_file=\"$BACKUP_DIR/${BACKUP_PREFIX}.$(date '+%Y%m%d_%H%M%S').bak\" cp \"$CONFIG_PATH\" \"$backup_file\" log \"Config backed up to $backup_file\" # Retain only the latest $MAX_BACKUPS backups old_backups=$(ls -1t $BACKUP_DIR/${BACKUP_PREFIX}.*.bak 2\u003e/dev/null | tail -n +$(($MAX_BACKUPS+1))) for f in $old_backups; do rm -f \"$f\" \u0026\u0026 log \"Deleted old backup $f\" done else log \"No existing config found, skipping backup\" fi } # === Download new config === download_config() { log \"Downloading new config...\" curl -fsSL -o \"$TMP_PATH\" \"$CONFIG_URL\" if [ $? -ne 0 ]; then log \"Download failed—check network or URL\" return 1 fi # Basic validation: check file size if [ ! -s \"$TMP_PATH\" ]; then log \"Config file is empty—update aborted\" return 2 fi log \"Config downloaded\" return 0 } # === Update config file === replace_config() { mv \"$TMP_PATH\" \"$CONFIG_PATH\" log \"Config updated\" } # === Restart Mihomo service === restart_service() { systemctl restart mihomo if [ $? -eq 0 ]; then log \"Mihomo restarted\" else log \"Failed to restart Mihomo—please check manually\" fi } main() { backup_config download_config DL_STATUS=$? if [ \"$DL_STATUS\" -ne 0 ]; then log \"Aborted: config not updated, keeping old config\" exit 1 fi replace_config restart_service log \"=== Update complete ===\" } main \"$@\"Use crontab -e to edit your crontab and schedule auto-updates at 3am daily:\n0 3 * * * /etc/mihomo/update_config.shFirewall Configuration Manually setting up firewall rules to direct traffic through the Mihomo core isn’t as tricky as it sounds. I’ve previously covered the details in my article “From Beginner to Advanced: Tailscale + ShellCrash for Remote Networking and Bypassing Internet Censorship”. Here, I’ll just summarise the practical steps.\nFor my setup, I want all traffic from the tailscale0 interface to be transparently proxied by Mihomo. If all you need is TCP interception, iptables REDIRECT is sufficient; but for UDP, QUIC, etc., you’ll need TPROXY. Don’t forget IPv6!\nHere’s my firewall config:\n# Create custom chain iptables -t mangle -N MIHOMO # Exclude local traffic as needed iptables -t mangle -A MIHOMO -d 127.0.0.1/8 -j RETURN iptables -t mangle -A MIHOMO -d 100.64.0.0/10 -j RETURN iptables -t mangle -A MIHOMO -d 192.168.1.0/24 -j RETURN iptables -t mangle -A MIHOMO -d 172.17.0.0/16 -j RETURN # Mark TCP and UDP for proxy iptables -t mangle -A MIHOMO -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233 iptables -t mangle -A MIHOMO -p udp -j TPROXY --on-port 7893 --tproxy-mark 233 # Hook into the interface iptables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO # Routing table echo \"233 mihomo\" | tee -a /etc/iproute2/rt_tables ip rule add fwmark 233 lookup mihomo ip route add local 0.0.0.0/0 dev lo table mihomo # IPv6 # Create chain ip6tables -t mangle -N MIHOMO6 # Skip local addresses ip6tables -t mangle -A MIHOMO6 -d ::1/128 -j RETURN ip6tables -t mangle -A MIHOMO6 -d fd7a:115c:a1e0::/48 -j RETURN # Mark TCP/UDP ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233 ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233 # Interface hook ip6tables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO6 # Routing table echo \"233 mihomo\" | tee -a /etc/iproute2/rt_tables ip -6 rule add fwmark 233 lookup mihomo ip -6 route add local ::/0 dev lo table mihomoMost modern distributions do not persist firewall rules by default. For details about rule persistence, see the “Routing Rule Persistence” and “iptables Rule Persistence” sections referenced in my Tailscale article.\nHow Do I Configure Local Proxying? Most proxy clients, such as the default for ShellCrash, use REDIRECT as standard, which is usually sufficient for most use cases. Example for REDIRECT:\n# IPv4, intercept eth0 iptables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892 # IPv6, intercept eth0 ip6tables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892Here, 7892 should match the redir-port in your Mihomo config, and eth0 is the interface you want to intercept. Compared to TPROXY, this is remarkably straightforward.\nPersonally, I dislike forcing all local traffic through the firewall proxy route. Instead, I prefer to use environment variables, proxychains, or per-application proxy settings as needed to route traffic through Mihomo. For example, if you want Docker to use the proxy, you only need to edit Docker\u0026rsquo;s /etc/docker/daemon.json and specify the proxy endpoints, rather than intercepting all network traffic at the interface level:\n{ \"proxies\": { \"http-proxy\": \"http://127.0.0.1:7890\", \"https-proxy\": \"http://127.0.0.1:7890\", \"no-proxy\": \"127.0.0.0/8\" } }Why Go to All This Trouble? Isn’t Using a GUI Client Easier? Don’t ask. Some of us just prefer living in the terminal void.\n","date":"2025-07-03T18:30:00+08:00","image":"/2025/07/switch-to-pure-mihomo-kernel/cover_hu_1a93b6f26c22bbfe.webp","permalink":"/en/2025/07/switch-to-pure-mihomo-kernel/","title":"Ditch the Client Applications: Using the Mihomo Core Directly"},{"content":"When I first saw headlines such as “Americans using ‘buy now, pay later’ plans to buy groceries, survey finds,” my instinctive reaction was: isn’t this just business as usual? Only after reading the article did it strike me—this world is dafter than even my most cynical estimates.\nAs a financial innovation, Buy Now, Pay Later (BNPL) certainly holds a certain appeal. It allows consumers to acquire goods and services immediately, with the payment split into later instalments—a model that’s taken the worlds of e-commerce, travel and even dining by storm in recent years.\nBut as always, there’s no such thing as a free lunch. In the end, the cost falls on consumers. In order to make a profit, BNPL providers rely on two main avenues: charging merchants fees higher than those of traditional payment services, and slapping late fees on delinquent customers. At its core, BNPL exploits a psychological trap—minimising the “pain” of paying in the moment, thus encouraging more spending. This is precisely why so many merchants are eager to shoulder those higher fees for BNPL integration.\nTo be clear, I have nothing against BNPL—I’m an avid user myself. My phone was purchased through a 24-month interest-free instalment plan with JD.com; at the moment, I’m only six payments in. Most of my day-to-day expenses go through Huabei (a Chinese BNPL solution) and are automatically settled from my savings or bank account. When a platform truly provides a genuine interest-free or “zero-fee” offer, BNPL becomes an opportunity for free leverage—it’s a costless way to put someone else’s money to work on your behalf. In simple terms: the financial provider pays for your consumption, letting your own cash sit untouched, potentially earning a modest return elsewhere. Financial efficiency, maximised.\nHowever, there are three absolutely critical prerequisites:\nPay on time, every time—no excuses; Never fall into habitual overspending; Maintain at least basic financial literacy and management skills. Contrast this with what the headlines are pointing out—“Americans using BNPL for groceries.” Skimming the survey data, I saw that one third of BNPL users have missed at least one repayment?? Some even defaulted on takeaway orders??\nThe ideal is promising, but reality bites hard. Data shows that the number of people with poor financial self-management is greater than I had imagined. 46% of BNPL users already carry existing credit card debt, and among them, 28% are aged just 18-241. This demographic typically has weaker credit, with 63% juggling loans from multiple BNPL platforms simultaneously2—a glaring sign of questionable spending habits.\nWhat’s even more concerning is how BNPL, by design, appears particularly seductive to individuals with poor financial discipline. Compared to credit cards, approval is instant and the credit barriers are low, all the while trumpeting “interest-free instalments.” For many, BNPL feels like an inexhaustible source of easy money—buy what you want today for a small upfront payment, and worry about the rest later. But, needless to say, the world does not work that way.\nInevitably, the day of reckoning comes. As bills mount, many are shocked by the size of their accumulated debt. Missed payments lead to late fees and penalties, which quickly snowball. Add on steep merchant charges, and it’s little wonder BNPL providers are making a killing.\nTo reiterate: I am not against BNPL. As a free financial lever, it’s simply common sense to take advantage of it. For instance, when I bought a £429 phone, I could have paid it off in full straight away, but I opted for a 24-month interest-free plan. The point is, I am fully aware that I have advanced the total purchase price, not merely spent £17.87 per month. This is reflected in my accounts as an immediate reduction in assets, so my net worth accurately includes the liability. Live within your means, avoid piling on unnecessary debt—or, worse, layering leverage upon leverage. Even if idle funds only generate minimal returns in a savings or low-risk investment account, this keeps my finances healthy and ensures I’ll never default. While a modest bit of borrowing can enhance quality of life, solid financial habits are always more prudent when your income isn’t rock-solid.\nStill, such warnings will do little to sway those long accustomed to unhealthy spending. Even without the alluring convenience of BNPL, these individuals would almost certainly max out their credit cards and then flounder, sinking beneath 20% APRs.\nThe problem isn’t BNPL itself (it’s just another tool), but the underlying disconnect: fragile finances on one side and runaway consumerism on the other. For those who lack self-control, underestimate financial risk, or whose circumstances are already precarious, any easily accessible credit—whether credit cards, BNPL, or the far more dangerous payday loans—serves only as a different route to the same pit of debt. BNPL’s particular twist is its ease of access and immediate gratification, meaning those on the edge tumble even faster—and crash that much sooner.\nA sizeable portion of BNPL delinquents are already mired in credit card debt. This suggests a robbing-Peter-to-pay-Paul cycle, borrowing from one source to stave off another, ultimately piling interest upon fees and sinking into ever-deepening crisis. Even if BNPL vanished tomorrow, these vulnerable consumers would likely stumble into other financial traps, whether it’s payday loans or some new “convenient” scheme. In the haste to stave off disaster, some might even resort to pawning off family heirlooms.\nSo the real concern isn’t any one financial product, but the underlying structural malaise: economic pressures, the omnipresence of consumerism, inadequate financial education, and the widespread prioritisation of instant gratification over long-term planning. The rise of BNPL merely makes these chronic issues more visible—sharper and harder to ignore.\nIn the end, if we truly want to “save” these individuals, simply restricting access to financial products (be it BNPL or credit cards) is merely treating symptoms, not causes. The real solution—if there is one—lies in tackling the roots: boosting earnings, improving financial literacy, and reshaping spending norms. But these foundational changes are far harder than wishing BNPL companies would simply shut up shop.\nOne last piece of advice: borrowed money always needs to be repaid; don’t make needless offerings to financial middlemen.\nAlas, all told—the world really is more foolish than I had ever imagined.3\nSource: CFPB Research Reveals Heavy Buy Now, Pay Later Use Among Borrowers with High Credit Balances and Multiple Pay-in-Four Loans\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSource: Buy now, pay later users pile on debt, CFPB finds\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCover image source: https://www.visa.com.tw/pay-with-visa/featured-technologies/mobile-payments.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-06-05T19:54:56+08:00","image":"/2025/06/on-buy-now-pay-later/mobile-payments-1920x720_hu_4cbcee30d76215b2.webp","permalink":"/en/2025/06/on-buy-now-pay-later/","title":"The World Is More Foolish Than You Think: A Brief Look at BNPL"},{"content":"Disappointing Right Out of the Box From the moment I placed the order for the MECHREVO Aurora X Pro, I was quite looking forward to it. However, the courier didn’t arrive before my trip to Hong Kong, and by the time I got home and unboxed it, the novelty had already worn off. When I tried to install another SSD, I found the M.2 screw was stripped, so I had to make do with some electrical tape—my fondness for the new laptop instantly halved.\nSpecs and Price i9-14900HX + RTX 5070 Ti, and at 8,699 yuan after subsidy, the price is fairly reasonable. As for peripherals, to put it simply, I’ve happily used a Hasee before, so there’s really nothing I can’t get on with.\nScreen: 2560x1600@300Hz, 100% sRGB colour gamut, with decent colour accuracy. When playing Cities: Skylines 2, the red of the brake lights in traffic jams is even more vivid than in real life. Keyboard: The key travel is rather short and the feel is average, but since I mostly use an external keyboard, I don’t really mind. Wi-Fi Card: The AX201 is rather stingy, and the antenna design seems poor—you can clearly tell the speed is much slower than wired, even when right next to the router. SSD: Comes with a 1TB Zhiti drive, which is average; the 2TB Crucial I added myself is the main workhorse. Battery: The 80Wh battery is fine for emergencies on a gaming laptop. I usually keep it in workstation mode to prolong battery life. Discrete GPU Direct Connection: Supports hot switching, which is great—no need to reboot to change modes. BIOS: The AMI BIOS has a rather weird GUI, which is still much better than my previous Hasee Laptop. User Experience Cities: Skylines 2—even with a population of 120,000, simulation speed could still be kept at 3x, though the fans were roaring by then. Cyberpunk 2077 runs with ray tracing on Ultra, and Forza Horizon 5 also runs at max setting without issue—but honestly, the actual gaming experience feels much the same as with my old RTX 3060. Sure, reflections are more realistic and details richer, but just for the sake of improved visuals, I doubt I’d spend more time on these games that have already kept me entertained for over a hundred hours.\nThe skin-like coating on the palm rest does feel nice, but with peripherals connected, I’m mostly just touching the external keyboard. Nahimic audio effects have serious latency—turning them off actually makes osu! more responsive, a textbook case of negative optimisation.\nThe Meaning of Tools I’ve used this laptop for two weeks—no surprises, good or bad. The Wi-Fi card is so rubbish I’ve had to use wired, the fan is extremely loud in turbo mode, and the southbridge gets very hot due to lack of cooling. Apart from that, it’s just a machine that lights up when it should and makes noise when it must.\nPerhaps that’s how good tools should be: as I write this, I realise I’ve been staring blankly at the battery icon in the bottom right corner for five minutes, and it’s quietly holding at 98%—just sitting there, like a mute brick.\n","date":"2025-05-04T23:18:43+08:00","image":"/2025/05/new-laptop-briefing/DSCF0267_hu_9e18f49bd8ce081.webp","permalink":"/en/2025/05/new-laptop-briefing/","title":"Quick Review of the MECHREVO Aurora X Pro"},{"content":"During several trips, I attempted to upload newly taken photos to my Immich server at home in Changsha. I used to rely on Cloudflare Tunnel for reverse proxying, but due to the particularities of the Chinese network environment, slow connections, frequent disconnections, and failed uploads became the norm. Later, I started experimenting with Tailscale — a NAT traversal tool based on WireGuard — and it finally allowed me to stably and quickly access my home NAS and photo library from anywhere in China.\nHowever, a new problem emerged: when Tailscale runs on Android, it needs to take over the system traffic as a VPN service. Meanwhile, my usual tool Clash also relies on the VPN interface for traffic routing. Due to Android\u0026rsquo;s limitation of allowing only one VPN service at a time, the two cannot coexist. This meant I had to choose between \u0026ldquo;accessing home services\u0026rdquo; and \u0026ldquo;accessing the open internet\u0026rdquo;.\nThis article walks through the principles of Tailscale, how to set up your own DERP server to improve connection quality, and how to hijack traffic from a Tailscale Exit Node using iptables and forward it to ShellCrash, enabling flexible traffic routing and secure tunnelling. Whether you\u0026rsquo;re looking to access your home NAS or photo library remotely, or protect your data on untrusted networks, this guide offers a practical and stable solution.\nWhat is Tailscale? Tailscale is a zero-config virtual private network (VPN) tool based on the WireGuard protocol. It allows devices located in different network environments to communicate as if they were on the same secure local network. By automatically traversing NATs and firewalls, Tailscale enables access to your home NAS, personal servers, development environments, and other internal resources without requiring a public IP or port forwarding. Its key strengths lie in its simplicity, security, and stability — it\u0026rsquo;s ready to use out of the box, encrypts all traffic end-to-end, and is suitable for developers, remote workers, and home users alike.\nTailscale\u0026rsquo;s technical implementation is quite ingenious. It builds on the WireGuard encryption protocol but reimagines traditional VPN IP allocation. Each device authenticates using SSO/OAuth2 and receives a node key that is permanently bound to its identity. This identity-based networking model allows a \u0026ldquo;NAS in Changsha\u0026rdquo; and a \u0026ldquo;phone in Hong Kong\u0026rdquo; to communicate as if they were coworkers in the same office.\nHow Tailscale Establishes Connections Control Server When a Tailscale client starts, it first connects to the control server (controlplane) to authenticate and fetch information about other nodes in the network, including each device’s public IP, port, NAT type, etc. This step is essentially the \u0026ldquo;getting to know your peers\u0026rdquo; phase. The control server does not relay any traffic — it only coordinates connections, acting like a dispatcher.\nDERP Servers A key factor behind Tailscale’s high connection success rate is its custom relay protocol called DERP. In Tailscale’s architecture, DERP (Designated Encrypted Relay for Packets) is a crucial component that only steps in when needed. In simple terms, it’s an encrypted HTTP-based relay server that acts as an intermediary when two devices cannot connect directly. All clients initially connect via DERP (relay mode), meaning the connection is established instantly with no waiting. Then, both sides begin path discovery in parallel, and within a few seconds, Tailscale typically finds a better route and transparently upgrades the connection to a direct peer-to-peer tunnel.1\nImportant notes:\nAll traffic relayed via DERP is end-to-end encrypted, so DERP servers cannot see the content; Tailscale uses DERP only when necessary — once a direct connection is established, it automatically switches; Official DERP nodes are distributed globally, and clients automatically select the one with the lowest latency; You can also deploy your own DERP server (for example, within China) to improve latency and reliability. Think of DERP as a fallback mechanism — although it’s not as performant as direct connections, it ensures devices can always stay connected even when NAT traversal fails. NAT Traversal After obtaining the peer’s address info, Tailscale attempts to establish a peer-to-peer (P2P) connection via NAT traversal. This process uses the STUN protocol, where both sides send probe packets to try to punch a hole through the NAT router and create a direct UDP tunnel. If the network conditions allow, a direct tunnel is established, offering fast speeds and low latency.\nDue to space constraints, we won’t go into detail about NAT traversal here. For more information, refer to Tailscale’s official article “How NAT traversal works”.\nFull Connection Flow Diagram flowchart TD A[Device A starts Tailscale] --\u003e B[Initial connection via DERP server] B --\u003e C[Exchange network info and WireGuard keys] C --\u003e D[Both sides perform NAT type detection] D --\u003e E{Direct connection possible?} E -- Yes --\u003e F[Establish P2P tunnel] F --\u003e G[Periodically check connection quality] G --\u003e H{Is P2P better than DERP?} H -- Yes --\u003e I[Switch most traffic to P2P] H -- No --\u003e J[Continue relaying traffic via DERP] E -- No --\u003e J style B fill:#e3f2fd,stroke:#2196f3,color:#000 style F fill:#e8f5e9,stroke:#4caf50,color:#000 style J fill:#fff3e0,stroke:#ff9800,color:#000Hosting Your Own DERP Server Tailscale installation is straightforward across platforms, and the official documentation covers it in detail. This article assumes you have already installed and logged into Tailscale on the relevant devices.\nWhy Host Your Own DERP? Tailscale has deployed numerous DERP relay servers globally2. However, for well-known reasons, there are no official DERP nodes within mainland China. This leads to the following issues:\nWhen NAT traversal fails, all traffic must be routed via overseas DERP nodes, resulting in high latency and poor performance; Some official DERP nodes may be disrupted by the Great Firewall (GFW), causing connection drops or handshake failures; Even when direct connections succeed, Tailscale still relies on DERP for exchanging route info and WireGuard keys — if DERP is unreachable, connection quality suffers. Therefore, in the Chinese network environment, hosting a local DERP node can significantly improve connection stability and performance, while also avoiding unexpected issues caused by network restrictions — making it a worthwhile optimisation.\nPrerequisites As DERP is HTTP-based, you’ll need an HTTP reverse proxy and an SSL certificate. This guide uses Docker for deployment. Before proceeding, ensure your server has Docker and Docker Compose installed. You’ll also need basic knowledge of editors like nano to modify config files. For optimal results, your server should have a static public IPv4+IPv6 dual-stack address. If all requirements are met, you can begin deployment:\nmkdir tailscale-derp \u0026\u0026 cd tailscale-derp nano docker-compose.ymldocker-compose.yml services: derper: name: tailscale-derp image: fredliang/derper environment: - DERP_DOMAIN=derp.nightcity.pub - DERP_VERIFY_CLIENTS=true - DERP_ADDR=:4433 network_mode: host restart: unless-stopped volumes: - \"/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock\"The network_mode: host setting is crucial. It allows the container to share the host\u0026rsquo;s network stack. If Docker’s default bridge mode is used, the container’s network will be NATed, and the DERP STUN service will detect a 172.17.0.0/16 address instead of the client’s real public IP, causing connection issues.\nvolumes: - \"/var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock\"As mentioned earlier, DERP traffic is end-to-end encrypted, and DERP servers do not know who is using them. Without proper access control, anyone who knows your DERP address and port could use it. This volume mounts the Tailscale socket file into the container, allowing the DERP service to authenticate clients via the local tailscaled service. Combined with DERP_VERIFY_CLIENTS=true, it prevents freeloaders from using your DERP node.\nNotes:\nYour host must already have the Tailscale client (tailscaled) installed and logged in, or the socket file won\u0026rsquo;t exist and the container will fail; tailscaled must run with root privileges to create this socket. Reverse Proxy Using Caddy as an example, reverse proxy port 4433 and deploy an SSL certificate:\n{ email webmaster@l3zc.com } *.l3zc.com { encode gzip tls { dns dnspod APP_ID,APP_KEY resolvers 119.29.29.29 223.5.5.5 } @derp host derp.l3zc.com reverse_proxy @derp :4433 }Note: If you\u0026rsquo;re using MagicDNS with Caddy and DNS providers to request a wildcard certificate, you may encounter local certificate validation errors. Setting the resolvers parameter resolves this issue. Visit the proxied node — if you see the following page, the setup is correct:\nConfiguring ACL Policies Go to the Tailscale admin console\u0026rsquo;s page and add your custom DERP configuration.\nTailscale’s ACL policies are written in HuJSON3. To edit in VSCode, set the language mode to “JSON with Comments (jsonc)”. Here\u0026rsquo;s an example configuration:\n{ \"acls\": [{ \"action\": \"accept\", \"src\": [\"*\"], \"dst\": [\"*:*\"] }], \"ssh\": [ { \"action\": \"check\", \"src\": [\"autogroup:member\"], \"dst\": [\"autogroup:self\"], \"users\": [\"autogroup:nonroot\", \"root\"] } ], // Custom DERP configuration \"derpMap\": { \"OmitDefaultRegions\": false, // Set to true to exclude official DERPs \"Regions\": { \"900\": { \"RegionID\": 900, \"RegionCode\": \"sha\", \"RegionName\": \"Shanghai\", \"Nodes\": [ { \"Name\": \"myderp\", \"RegionID\": 900, \"HostName\": \"derp.l3zc.com\" } ] } } } }Tailscale reserves RegionIDs 1–899 for official nodes. Custom DERPs must use IDs 900 and above.\nTesting the Connection Once you’ve saved your ACL configuration, Tailscale will automatically sync it to all clients. After a short wait, run tailscale netcheck on a client to test the connection:\nPay attention to whether the returned IP is your actual public IP. If it shows an address in the 172.17.0.0/16 range, your Docker configuration is incorrect.\nCoexisting with Internet Access: Hijacking Exit Node Traffic to the Clash Engine Declaring an Exit Node Prepare a device at home that stays on 24/7 — this could be a Raspberry Pi or a Mac Mini. Install Tailscale and declare it as an Exit Node. You can also advertise your home LAN subnet if needed. Once enabled in the Tailscale console, this device becomes a free VPN, allowing you to securely access the internet from unfamiliar networks.\nsudo tailscale up --advertise-exit-node --advertise-routes 192.168.1.0/24 Once broadcasting is complete, you can access all devices on your home network from anywhere in the world, as long as you\u0026rsquo;re connected to the Tailscale network.\nEnabling IP Forwarding and Disabling UDP GRO4 Enabling IP forwarding is essential for devices like the Raspberry Pi to function as an Exit Node. The following commands are for Raspberry Pi — if you\u0026rsquo;re using a different device, refer to the official Tailscale documentation.\necho 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf sudo sysctl -p /etc/sysctl.d/99-tailscale.confAccording to Tailscale5, disabling UDP GRO6 can improve forwarding performance. Although the official persistence method may not work on Raspberry Pi, we can configure it manually.\n# Install ethtool sudo apt update \u0026\u0026 sudo apt install ethtool -y # Disable UDP GRO sudo ethtool -K eth0 gro offTo persist this setting, create a systemd service:\n# Create the service file sudo nano /etc/systemd/system/ethtool.serviceAdd the following content:\n[Unit] Description=Configure eth0 GRO After=network.target [Service] Type=oneshot ExecStart=/sbin/ethtool -K eth0 gro off [Install] WantedBy=multi-user.targetThen enable and start the service:\nsudo systemctl daemon-reload sudo systemctl enable ethtool sudo systemctl start ethtoolHijacking Traffic on the Exit Node My Exit Node is a Raspberry Pi. After configuring it as an Exit Node, the final step is to hijack the traffic sent from my phone to the Exit Node in order to enable open internet access. For stability reasons, I prefer not to run proxy software directly on the main home router. Therefore, hijacking traffic on the Pi is the only viable solution.\nFirst, install ShellCrash. Follow the prompts to import configuration files and set up automation as needed:\nexport url='https://fastly.jsdelivr.net/gh/juewuy/ShellCrash@master' \u0026\u0026 wget -q --no-check-certificate -O /tmp/install.sh $url/install.sh \u0026\u0026 bash /tmp/install.sh \u0026\u0026 source /etc/profile \u0026\u003e /dev/nullStart the service and change the firewall mode to “Clean Mode”. I also recommend enabling SNI sniffing and switching the DNS mode from fake-ip to redir-host7, and enabling IPv6 transparent proxy.\nSetting Clean Mode allows us to manually configure iptables for more precise traffic hijacking. Use ifconfig to check the Tailscale interface name — it’s usually tailscale0 by default:\nroot@raspberrypi:~# ifconfig eth0: ...... tailscale0: flags=4305 mtu 1280 inet 100.111.19.50 netmask 255.255.255.255 destination 100.111.19.50 inet6 fd7a:115c:a1e0::3d01:1332 prefixlen 128 scopeid 0x0 inet6 fe80::c0d5:1a1b:2005:48eb prefixlen 64 scopeid 0x20 ...Hijack all traffic from tailscale0 to the local Clash engine’s listening port 7892. This is called the “static route port” in ShellCrash. Don’t forget to also hijack IPv6 traffic:\n# IPv4: Hijack tailscale0 TCP traffic iptables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892 # IPv6: Hijack tailscale0 TCP traffic ip6tables -t nat -A PREROUTING -i tailscale0 -p tcp -j REDIRECT --to-ports 7892Advanced: Hijacking UDP Traffic with TProxy The iptables REDIRECT target can only redirect TCP traffic. Since UDP is a connectionless protocol, REDIRECT cannot retain the original destination address, which prevents transparent proxies from identifying the original destination.\nSo, if you attempt to hijack UDP traffic using a rule like this:\niptables -t nat -A PREROUTING -i tailscale0 -p udp -j REDIRECT --to-ports 7892This rule will either not work or cause abnormal proxy behaviour.\nIs there a way to proxy UDP traffic? Yes, indeed. But the prerequisites are:\nThe proxy core must support UDP transparent proxying (Clash Premium and Mihomo both support this); You must use TProxy mode instead of REDIRECT; The iptables mangle table and policy routing must be correctly configured; UDP proxying must be enabled in the proxy configuration file (e.g. mode: rule and udp: true). Assuming you\u0026rsquo;ve fulfilled the first, second, and last conditions, here is an example8:\n# Create a custom chain sudo iptables -t mangle -N SHELLCRASH # Exclude local traffic as needed sudo iptables -t mangle -A SHELLCRASH -d 127.0.0.1/8 -j RETURN sudo iptables -t mangle -A SHELLCRASH -d 100.64.0.0/10 -j RETURN sudo iptables -t mangle -A SHELLCRASH -d 192.168.1.0/24 -j RETURN sudo iptables -t mangle -A SHELLCRASH -d 172.17.0.0/16 -j RETURN # Mark TCP and UDP traffic for the proxy sudo iptables -t mangle -A SHELLCRASH -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233 sudo iptables -t mangle -A SHELLCRASH -p udp -j TPROXY --on-port 7893 --tproxy-mark 233 # Interface redirection sudo iptables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH # Routing table configuration echo \"233 shellcrash\" | sudo tee -a /etc/iproute2/rt_tables sudo ip rule add fwmark 233 lookup shellcrash sudo ip route add local 0.0.0.0/0 dev lo table shellcrashAnd of course, don’t forget about IPv6:\n# Create a chain sudo ip6tables -t mangle -N SHELLCRASH6 # Exclude local addresses sudo ip6tables -t mangle -A SHELLCRASH6 -d ::1/128 -j RETURN sudo ip6tables -t mangle -A SHELLCRASH6 -d fd7a:115c:a1e0::/48 -j RETURN # Mark TCP/UDP traffic ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233 ip6tables -t mangle -A SHELLCRASH6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233 # Interface redirection sudo ip6tables -t mangle -A PREROUTING -i tailscale0 -j SHELLCRASH6 # Routing table configuration echo \"233 shellcrash\" | sudo tee -a /etc/iproute2/rt_tables sudo ip -6 rule add fwmark 233 lookup shellcrash sudo ip -6 route add local ::/0 dev lo table shellcrashPersisting Routing Rules Rules created with ip rule and ip route are lost after a reboot, so we need to persist them manually. The simplest method is to create a script and add it to crontab.\nCreate a script:\nsudo nano /usr/local/bin/policy-route.shEdit it as follows:\n#!/bin/bash # IPv4 policy routing ip rule add fwmark 233 lookup 233 ip route add local 0.0.0.0/0 dev lo table 233 # IPv6 policy routing ip -6 rule add fwmark 233 lookup 233 ip -6 route add local ::/0 dev lo table 233After granting execution permissions, edit crontab:\nsudo chmod +x /usr/local/bin/policy-route.sh sudo crontab -eAdd the following line at the end of the crontab file:\n@reboot /usr/local/bin/policy-route.shHow It Works: How Does TProxy Forward UDP Traffic? If you\u0026rsquo;ve read this far, you might be wondering: why, throughout the entire process, we never specified --to-ports in iptables, nor did we see the destination address being modified, yet UDP traffic was somehow successfully proxied? How is that possible?\nTo explain this, let’s first look at the fundamental differences between TProxy and REDIRECT:\nREDIRECT Mode:\nUses the iptables nat table; Rewrites the destination address to a local one (e.g. 127.0.0.1:7892); Typically used for TCP traffic; Cannot preserve the original destination address; Requires specifying --to-ports. flowchart TB A[Client device\ninitiates TCP request via Tailscale] B[tailscale0 interface receives traffic] C[iptables NAT PREROUTING\nREDIRECT --to-ports 7892] D[ShellCrash listens locally\non 127.0.0.1:7892] E[ShellCrash initiates new TCP connection\n→ target server] F[Response returns from the network\nShellCrash forwards it] G[Response reaches client device] A --\u003e B --\u003e C --\u003e D --\u003e E --\u003e F --\u003e G style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000 style D fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000TPROXY Mode:\nUses the iptables mangle table; Does not modify the destination IP, preserving the original target; Uses fwmark and policy routing to route packets to lo; The proxy listens on a special port (e.g. 7893) with IP_TRANSPARENT enabled; Supports both UDP and TCP; No need to specify --to-ports in iptables since this is not NAT, but marking + routing. flowchart TB A[Client device\ninitiates TCP/UDP request via Tailscale] B[tailscale0 interface receives traffic] C[iptables MANGLE PREROUTING\nassign fwmark 233] D[ip rule: fwmark 233\nuse routing table shellcrash] E[ip route: local 0.0.0.0/0\ndev lo table shellcrash] F[ShellCrash listens on lo:7893\nin IP_TRANSPARENT mode] G[ShellCrash retrieves original destination\ninitiates proxy connection] H[Response returns from the network\nShellCrash forwards it] I[Response reaches client device] A --\u003e B --\u003e C --\u003e D --\u003e E --\u003e F --\u003e G --\u003e H --\u003e I style C fill:#f9f,stroke:#aaa,stroke-width:1px,color:#000 style F fill:#bbf,stroke:#aaa,stroke-width:1px,color:#000TProxy does not use DNAT/REDIRECT. Instead, it marks packets using the mangle table, then uses policy routing (ip rule + ip route) to route those packets to the lo interface. The proxy application (e.g. Clash / ShellCrash) listens on a port on lo9, and with the IP_TRANSPARENT option enabled, it can read the original destination IP and port from the packet and forward the traffic accordingly.\nIn short, TProxy mode only requires:\niptables to mark packets; ip rule + ip route to route them to lo; The proxy to listen on lo with IP_TRANSPARENT enabled. Thus, there\u0026rsquo;s no need to specify --to-ports in iptables, because the destination IP and port remain unchanged, and the proxy can detect and handle them itself.\nPersisting iptables Rules Install iptables-persistent:\nsudo apt update sudo apt install iptables-persistentDuring installation, you\u0026rsquo;ll be prompted to save the current IPv4 and IPv6 rules — select \u0026ldquo;Yes\u0026rdquo;. If you later add new rules, remember to save them:\n# Save current IPv4/IPv6 rules sudo netfilter-persistent saveSaved rules are stored in:\nIPv4: /etc/iptables/rules.v4 IPv6: /etc/iptables/rules.v6 You can also edit the rules.v4/rules.v6 files directly as needed.\nFinal Result The performance of this setup largely depends on your home network\u0026rsquo;s upload bandwidth. I have a 500Mbps down / 60Mbps up connection, and I haven’t encountered a single NAT traversal failure so far. Speeds are consistently high, latency is acceptable, and I can securely access my Immich server, OpenWRT router, and other home devices from anywhere with end-to-end encryption — all while enjoying unrestricted internet access. Overall, I’m quite satisfied with the solution.\nReference: https://icloudnative.io/posts/custom-derp-servers/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://tailscale.com/kb/1232/derp-servers\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAbout HuJSON: https://github.com/tailscale/hujson\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://tailscale.com/kb/1408/quick-guide-exit-nodes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://tailscale.com/kb/1320/performance-best-practices#linux-optimizations-for-subnet-routers-and-exit-nodes\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nUDP GRO (Generic Receive Offload) is a Linux kernel network optimisation that merges small packets to improve efficiency. However, when acting as a forwarding node, this can increase latency and reduce throughput under packet loss conditions.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCompared to fake-ip, redir-host offers better compatibility, fewer issues, and avoids temporary disconnects caused by fake IP residue when toggling proxy modes. My Pi uses a preconfigured SmartDNS setup with no DNS pollution, resulting in a smooth experience.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://blog.zonowry.com/posts/clash_iptables_tproxy/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nlo is the default loopback interface in Linux systems. In transparent proxy setups, it not only handles localhost traffic but is also used to receive network connections originally destined for external addresses, enabling local hijacking and forwarding of external traffic.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-04-14T18:10:25+08:00","image":"/2025/04/tailscale-setup-recap/tailscale_hu_62168b00e213c4a1.webp","permalink":"/en/2025/04/tailscale-setup-recap/","title":"From Beginner to Advanced: Remote Networking and Internet Access with Tailscale + ShellCrash"},{"content":"After switching to my own Mihomo override rules, my phone kept spinning when downloading apps from Google Play, but using the rules from the VPN service worked fine, which was very strange. So, I checked the Mihomo kernel logs.\nplay-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using Static Resources[🇭🇰 Hong Kong 07] play.googleapis.com:443 match GeoSite(GFW) using Node Selection[🇭🇰 Hong Kong 07] play-lh.googleusercontent.com:443 match RuleSet(cdn_domainset) using Static Resources[🇭🇰 Hong Kong 07] play-fe.googleapis.com:443 match GeoSite(GFW) using Node Selection[🇭🇰 Hong Kong 07] services.googleapis.cn:443 match GeoSite(CN) using Direct Connection[DIRECT](The above logs have been simplified.)\nThe Google Play services pre-installed on Chinese smartphones use the domain services.googleapis.cn instead of services.googleapis.com, and this domain is set to direct connection in most traffic routing rules. Yes, that\u0026rsquo;s where the problem lies. Modifying the rules to route this domain through a proxy solves the issue!\n\u0026hellip;Or does it?\nrr4---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using Leakage[🇭🇰 Hong Kong 07] rr2---sn-j5o7dn7s.xn--ngstr-lra8j.com:443 match Match using Leakage[🇭🇰 Hong Kong 07] rr1---sn-j5o76n7z.xn--ngstr-lra8j.com:443 match Match using Leakage[🇭🇰 Hong Kong 07]Wait, why do these strange domains appear every time I download an app from the Play Store? After some research online, a new world opened up.\nÅngströ: The Opposite Yet Similar Counterpart to Google Seeing xn--ngstr-lra8j.com, those familiar with domain names will know that this is PunyCode encoding, which decodes to ångströ.com. Anders Jonas Ångström was a Swedish physicist and a pioneer in spectroscopy.1 The ångström (Å) is a unit of length named in his honour, where $ 1 Å = 10^{-10} m = \\frac{1}{10} nm $. It is commonly used to describe very short distances, such as atomic and molecular sizes or the wavelength of light. It represents an extremely small scale, especially in physics and chemistry for fine measurements.\nAlthough Google\u0026rsquo;s name is not directly derived from \u0026ldquo;Googol,\u0026rdquo; it was inspired by the term. \u0026ldquo;Googol\u0026rdquo; is a mathematical term representing $10^{100}$, a 1 followed by 100 zeros, an extremely large number often used to denote vast quantities in computer science.\nIn terms of naming philosophy, Google and Ångströ, two seemingly opposite technological concepts, exhibit a fascinating symmetry. The former originates from the creative adaptation of the mathematical term \u0026ldquo;Googol,\u0026rdquo; while the latter stems from the reimagining of the physical unit \u0026ldquo;Ångström.\u0026rdquo; These artistically transformed names resemble the double helix of technological civilization: Google embodies the ambition to navigate the vast digital universe, while Ångströ hints at the meticulous crafting of atomic-scale technological landscapes. When these creatively altered professional terms meet in Silicon Valley, they form a perfect cognitive coordinate system—pointing to the limits of human information processing while heralding the grand journey of technological civilization towards both the macro and micro scales.\nIntroduction to Cryptography: The Patterns of Google\u0026rsquo;s Infrastructure Domains2 OK, enough. Now let\u0026rsquo;s turn our attention back to Google\u0026rsquo;s infrastructure. First, let\u0026rsquo;s look at some complete connection domains:\nrr4---sn-j5o7dn7s.xn--ngstr-lra8j.com rr1---sn-j5o76n7z.xn--ngstr-lra8j.com rr5---sn-i3b7knzs.xn--ngstr-lra8j.comThese seemingly random strings are actually the result of some simple cryptographic encryption. Let\u0026rsquo;s break down the core components.\nCity Name Conversion Rules Taking rr1---sn-j5o76n7z as an example, the key information segment is the 8 characters after sn-: r1---sn-[123][45][6][78]. The first three characters, in this case j5o, represent the city name, derived from the IATA code of the city\u0026rsquo;s main airport through a cryptographic transformation.\nFirst, construct a 5 * 7 alphanumeric table:\nRow0: 1 0 2 3 4 Row1: 5 6 7 8 9 Row2: a b c d e Row3: f g h i j Row4: k l m n o Row5: p q r s t Row6: u v w x yRotate this table counterclockwise, starting from the bottom-left corner of the new table, and copy the data from the original table in the order \u0026ldquo;left to right, top to bottom\u0026rdquo; into the new table in the order \u0026ldquo;bottom to top, left to right.\u0026rdquo;\nAfter rotation, we get the following table:\n| 6 d k r y | | --------- | | 5 c j q x | | 4 b i p w | | 3 a h o v | | 2 9 g n u | | 0 8 f m t | | --------- | | 1 7 e l s |The 5*5 section in the middle of the table, enclosed by lines, is the final cipher table, containing 25 characters representing letters a to y in order from left to right, top to bottom. For example, the IATA code for Shanghai Hongqiao International Airport is sha. We can use this table to get the encrypted ciphertext:\ns is the 19th letter in the alphabet. Counting from left to right, top to bottom in the cipher table, the 19th character is n. h is the 8th letter in the alphabet. Counting from left to right, top to bottom in the cipher table, the 8th character is i. a is the 1st letter in the alphabet. Counting from left to right, top to bottom in the cipher table, the 1st character is 5. The encrypted ciphertext is ni5.\nConversely, going back to the original city name j5o, we can reverse-engineer the plaintext from the cipher table:\nj is the 3rd character in the cipher table, which corresponds to c. 5 is the 1st character in the cipher table, which corresponds to a. o is the 14th letter in the alphabet, which corresponds to n. The decrypted plaintext is can, which is the IATA code for Guangzhou Baiyun International Airport. Clearly, the server we connected to is located in Guangzhou.\nWhat if Google had a server in Zurich, and Zurich Airport\u0026rsquo;s IATA code is zrh, which includes the letter z? Our cipher table only goes up to y. Don\u0026rsquo;t worry, Google has considered this issue—z corresponds to 1 in the cipher table.\nHere\u0026rsquo;s the code representation of these rules:\ntable = \"5cjqx4bipw3ahov29gnu08fmt1\" def iata2cipher(iata): global table iata = iata.upper() cipher = \"\" for element in iata: index = ord(element) - 65 cipher += table[index] return cipher def cipher2iata(cipher): global table cipher = cipher.lower() iata = \"\" for element in cipher: index = table.find(element) iata += chr(65 + index) return iata # print(iata2cipher(input(\"IATA:\"))) # print(cipher2iata(input(\"Cipher:\")))Server Group Number The [45] and [78] positions, such as 76 and 7z, represent the server group (access point) number, composed of characters from the first column of the following table (separated by a line). The characters 7elsz6dkry represent 0123456789. Therefore, 76 translates to 05, and 7z translates to 04.\n| 1 2 3 4 5 6 7 | 8 9 a b c d e | f g h i j k l | m n o p q r s | t u v w x y z | 0 1 2 3 4 5 6 | 7 8 9 a b c d | e f g h i j k | l m n o p q r | s t u v w x y | zHere\u0026rsquo;s the Python representation:\ncodeTable = \"7elsz6dkry\" def cipher2code(cipher): global codeTable cipher = cipher.lower() codeString = \"\" for element in cipher: index = codeTable.find(element) codeString += chr(index + 48) return codeString # print(cipher2code(input(\"Cipher:\")))Similarly, encrypting numbers into ciphertext follows the same logic, which we won\u0026rsquo;t elaborate on here.\nSupported Protocols The [6] position indicates network protocol information:\nn: IPv6 (address range 0x000-0x3FF) u: IPv6 (address range 0x400-0x7FF) m: IPv4 only For example:\na5mekn7r, IPv6 prefix: 2607:f8b0:4007:a::/64, IPv4 prefix: 74.125.103.0/24 a5m7zu7r, IPv6 prefix: 2607:f8b0:4007:407::/64, IPv4 prefix: 74.125.215.0/24 a5mekm76, IPv4 only, prefix: 208.117.242.0/24 Correct Traffic Routing Understanding the cryptographic patterns of Google\u0026rsquo;s infrastructure domains allows us to implement more precise traffic routing strategies. Currently, it\u0026rsquo;s known that Google\u0026rsquo;s CDN nodes in China are mainly located in Beijing (2x3), Shanghai (ni5), and Guangzhou (j5o). The corresponding domain characteristics can be identified using regular expressions:\nrules: - DOMAIN-REGEX,^r+[0-9]+(---|\\.)sn-(2x3|ni5|j5o)\\w{5}\\.xn--ngstr-lra8j\\.com$,DIRECTThis rule will match all domains like rr1---sn-j5o76n7z.xn--ngstr-lra8j.com that belong to domestic CDNs and mark them for direct connection. For other Google domains not covered (such as play.googleapis.com), the original proxy rules will still apply.\nIn fact, the upstream GeoSite database v2fly/domain-list-community has already optimised the rules for Google Play\u0026rsquo;s domestic CDN nodes3. Simply enabling the GEOSITE,GOOGLE-PLAY@CN rule in Mihomo\u0026rsquo;s configuration will automatically implement intelligent traffic routing for domestic CDN direct connections and overseas domain proxies:\nrules: - GEOSITE,GOOGLE-PLAY@CN,Direct Connection - GEOSITE,GOOGLE,Proxy Selection Reference: https://en.wikipedia.org/wiki/Anders_Jonas_%C3%85ngstr%C3%B6m\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://github.com/lennylxx/ipv6-hosts/wiki/sn-domains\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSpecific commit: https://github.com/v2fly/domain-list-community/pull/2436/commits/a86c1bf3d9bf577869180874d87c76ddf6282fc1\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-03-15T21:15:01+08:00","image":"/2025/03/chinese-cdn-used-by-playstore/cover_hu_8e92ca92eba5064b.webp","permalink":"/en/2025/03/chinese-cdn-used-by-playstore/","title":"Google Play Store's CDN in China: From Cryptography Basics to Traffic Routing Optimisation"},{"content":"After I subscribed to multiple proxy providers during the Spring Festival promotions, how to make full use of every node became a problem that was neither especially hard nor especially easy. Of course, I could subscribe to the config files provided by each proxy provider and switch between them, but that would be far too troublesome. What\u0026rsquo;s more, I also have self-hosted nodes, and I don\u0026rsquo;t want to create a separate new config just for that one node.\nSub-Store solves this problem very well. It can extract node information from multiple subscriptions, organize them with regular expressions or JS, and finally output a single subscription that integrates all node information.\nTo deploy it, you can directly use the image packaged by xream. This image includes both the frontend and backend. If you deploy it on the public internet, remember to change the backend path, otherwise your config files may very well be stolen.\nservices: sub-store: image: xream/sub-store container_name: substore restart: always environment: - SUB_STORE_CRON=55 23 * * * - SUB_STORE_FRONTEND_BACKEND_PATH=/super-random-path ports: - \"3001:3001\" volumes: - ./data:/opt/app/dataJust reverse proxy port 3001 to access the Substore frontend. Here Caddy is used as an example:\nsub-domain.example.com { reverse_proxy :3001 }Of course, when entering the frontend for the first time, don\u0026rsquo;t forget to add the backend address. The backend address here depends on the setting in the previous compose file. In the example in this article, the backend address is https://sub-domain.example.com/super-random-path.\nManaging combined subscriptions After adding all upstream subscriptions from proxy providers and your self-hosted nodes, you can start adding them all into a single combined subscription. However, different proxy providers use all kinds of naming conventions for nodes, so by default it looks very messy, and there may even be duplicate names between different proxy providers. Fortunately, Sub-Store supports batch renaming nodes through scripts. Here I recommend a script that can help us rename all proxy provider nodes.\nTo use this script, just paste the following address into the script operation field when editing the subscription.\nhttps://raw.githubusercontent.com/Keywos/rule/main/rename.js Finally, perform any additional node operations you like, and you can organize a unified, standardized node list.\nGenerating a Clash config Although we now have a node list, the generated config file still does not include any rules, so you need to write them yourself or pull third-party rules.\nGo to Substore\u0026rsquo;s file manager and create a new Mihomo config:\nFor \u0026ldquo;Source\u0026rdquo;, select the combined subscription, and choose your subscription group under the subscription name In the script operation field, fill in your own override config This is my own override rules: powerfullz/override-rules, designed for Mihomo/Substore, with the following core features:\nIntegrates high-quality rules such as SukkaW/Surge and 217heidai/adblockfilters, with strong compatibility and broad coverage. Adds dedicated traffic-splitting rules for scenarios such as Truth Social, E-Hentai, TikTok, and cryptocurrency to meet diverse needs. Streamlined and free of redundancy, with a clear structure and easy maintenance. Deeply integrates Loyalsoldier/v2ray-rules-dat GeoSite/GeoIP for more precise traffic splitting. IP rules add no-resolve by default, effectively reducing local DNS resolution and improving speed and privacy. Dynamic override: automatically detects node countries/regions, generates only groups that actually exist, and enumerates node names in real time for smarter configuration. JavaScript-format override Copy the raw link of the JavaScript-format override file https://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/convert.min.js, and append parameters as needed in the following format:\nhttps://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/convert.min.js#参数1=true\u0026参数2=trueThe following parameters are currently supported:\nParameter Function loadbalance Enable load balancing for country/region node groups landing Enable landing node functionality ipv6 Enable IPv6 support full Generate a complete config for pure-core usage scenarios keepalive Enable TCP KeepAlive quic Allow QUIC traffic1 fakeip Use fake-ip for DNS enhanced mode2 regex Switch country proxy groups to include-all + regex filtering mode3 threshold Do not display groups when the number of country/region nodes is below this value (default 0) For example, if you need load balancing and IPv6, the final override script link would be:\nhttps://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/convert.js#loadbalance=true\u0026ipv6=truePaste the final override script link into the script operation field. After using Substore\u0026rsquo;s preview generation feature to confirm that there are no problems, you can save it.\nYAML-format override I no longer use YAML-format overrides myself, so maintenance was sporadic, but PRs were welcome\nI wrote a GitHub Actions workflow to automatically generate YAML-format overrides from the JS-format override, so YAML-format overrides are now maintained again.\nBesides directly referencing convert.js for dynamic override, you can also use the 32 pre-generated YAML-format overrides in the repository — they are all placed in the yamls/ directory and are automatically regenerated and overwritten by GitHub Actions after each push. This is suitable for clients and conversion services such as Clash Verge that do not support JS overrides.\nFile naming convention:\nconfig_lb-{0|1}_landing-{0|1}_ipv6-{0|1}_full-{0|1}_keepalive-{0|1}_fakeip-{0|1}_quic-{0|1}.yamlExample (with full enabled and all others disabled):\nhttps://raw.githubusercontent.com/powerfullz/override-rules/refs/heads/main/yamls/config_lb-0_landing-0_ipv6-0_full-1_keepalive-0_fakeip-0_quic-0.yamlCI only uses a fake fake_proxies.json to generate the override, so it cannot implement the JS override feature that automatically matches nodes and generates the corresponding proxy groups. It can only include all regional node groups. If you have already deployed Substore and want the flexibility of \u0026ldquo;dynamic country detection + parameter passing\u0026rdquo;, JS override is still recommended.\nGenerating a download link After saving successfully, click the share button to generate a share link. Set the share validity period, then click \u0026ldquo;Create Share\u0026rdquo;. The generated link is the final Mihomo config file. Use it as a subscription link in your proxy software, and you\u0026rsquo;re done.\nSome proxy providers have poor UDP performance, and enabling this may degrade the experience.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nEnabling this may help solve the issue where TUN mode cannot access the internet.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCountry proxy groups switch to include-all + regex filtering mode, letting the Mihomo core dynamically filter nodes by regex at runtime instead of enumerating node names when the script runs (default false)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-03-07T10:54:29Z","image":"/2025/03/clash-subscription-convert/image-8_hu_92098803a4bf150d.webp","permalink":"/en/2025/03/clash-subscription-convert/","title":"The Fastest Guide to Managing Substore Subscriptions"},{"content":"The recent farce at the White House is something everyone must be aware of by now. A political system that has been stable for centuries is now being led by three rogues: Trump, Vance, and Musk. Their moral corruption, despicable actions, and underhanded tactics are unprecedented in both American and world history. In just three weeks, the credibility, status, and power that the United States has painstakingly built over centuries have been flushed down the toilet by this \u0026ldquo;trio,\u0026rdquo; making the world truly witness the joke that America has become.\nThe True Face of Trump Trump is a master of lies, corruption, and selfishness. He is a convicted felon who ran for president almost solely to avoid prison, and now, even in his second term, he hasn\u0026rsquo;t changed his criminal nature—he pardons criminals involved in the Capitol riot, lies through his teeth at press conferences, publicly humiliates foreign leaders on the diplomatic stage, allows Musk access to sensitive government data, and drastically cuts public welfare spending. Under the guise of protecting American industries, he imposes tariffs on foreign goods, effectively taxing the poor while cutting taxes for the rich.\nHe doesn\u0026rsquo;t care about America\u0026rsquo;s interests, willing to sacrifice the existing international order and America\u0026rsquo;s international credibility and status to maintain the face of his so-called \u0026ldquo;friends.\u0026rdquo; Perhaps this is the telepathic connection between dictators, a case of like attracting like. He plans to rule as a fascist dictator, and this time, he has enough support to achieve that goal. He personally takes charge of releasing smokescreens in the media, covering for Musk-led DOGE and Vance as they bypass Congress to disrupt government departments and the Supreme Court. Under the guise of opposing DEI, they are actually building a system of systemic oppression. First, they create cultural war smokescreens, then, while civil society is still reeling, they weaken civil rights, strengthen presidential power, and ultimately suppress all political opponents, sacrificing the interests of the American people to maintain the vested interests of business oligarchs and billionaire elites like Musk.\nUnder the influence of the MAGA movement represented by Trump, Musk, and others, American society may further shift to the right. This trend could pave the way for a more conservative agenda, with profound impacts on areas such as education, immigration, and labor policies. At the same time, this political atmosphere may give more space to scientific racism, sexism, and eugenics in mainstream politics, thereby undermining the foundations of American constitutional democracy. As for those groups deemed \u0026ldquo;internal enemies\u0026rdquo;—anyone who isn\u0026rsquo;t a \u0026ldquo;compliant white heterosexual male\u0026rdquo;—what awaits them is being sent to Nazi concentration camps, deportation, imprisonment, targeted rumors, and death threats.\nSometimes, one really has to marvel I can\u0026rsquo;t believe that the American people are dumb enough to re-elect this man into power.\n","date":"2025-03-03T22:44:08+08:00","image":"/2025/03/the-worst-guy-to-be-president/cover_hu_6ad90c7eb58493bd.webp","permalink":"/en/2025/03/the-worst-guy-to-be-president/","title":"The Worst Guy to Be a President"},{"content":"When using the internet in China, there are two common DNS-related issues: DNS poisoning and DNS leaks. To ensure a smooth browsing experience, these problems must be addressed.\nWhat is a DNS Leak? If you’re currently using a proxy to read this article, you can first visit this website to check whether your DNS requests are being sent to domestic DNS servers.\nIn simple terms, when you use a VPN or other proxy tools, you might think that your requests are only visible to you and your VPN provider. However, your device’s DNS requests could still be sent to your ISP’s DNS servers or public DNS servers (such as those provided by Alibaba or Tencent) for resolution. This means your real IP address and the domain names you visit could be logged. If you’re resolving sensitive domain names (like Telegram or WikiLeaks), this could attract regulatory attention, potentially resulting in warnings or even being summoned for questioning. 1\nTherefore, it’s crucial to configure your proxy tool’s routing strategies properly to avoid DNS leaks.\nConfiguring SmartDNS Preventing DNS Leaks DNS acts as the guide for your internet connection, so it must be fast. Deploying SmartDNS locally as your home network’s DNS server is an excellent choice. For details on SmartDNS’s functionality, configuration tips, and how it optimises CDN selection to speed up browsing, Sukka’s article “I Have My Unique DNS Setup” is highly recommended. Before proceeding with this article, it’s worth reading Sukka’s guide, as it covers many foundational aspects that won’t be repeated here. This article will focus on implementing these configurations via the Luci interface.\nIf, like me, you’re using OpenWRT, note that SmartDNS’s Luci app automatically generates new configuration files based on the settings saved in its interface each time it starts up. Therefore, refrain from directly modifying the configuration files in the /etc/smartdns directory. All changes should be made through the Luci app.\nGenerally, the more upstream servers SmartDNS has, the better. A higher number of upstream servers does not impact SmartDNS’s performance2, and it also increases redundancy. To prevent DNS leaks, ensure that foreign upstream DNS servers (preferably encrypted ones) are added to the default group, while domestic servers are placed in a separate group (e.g., domestic) with the additional parameter -exclude-default-group to exclude them from the default group.\nAccelerating Domestic Domain Resolution Once foreign upstream servers are configured, DNS leaks are no longer an issue. However, excluding domestic upstream servers from the default group means that accessing new domains requires waiting for responses from foreign upstream servers, which can slow down access to domestic websites and degrade CDN performance.\nThe dnsmasq-china-list project by Felix Yan provides a solution. It supports generating SmartDNS-compatible configuration files via Makefile. Some contributors have even automated daily updates via GitHub Actions, so you can directly download ready-made files. Note that these configuration files only work if the domestic upstream server group is named domestic.\ncd /etc/smartdns wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf wget https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.confNext, import these configuration files into SmartDNS by pasting the following into the “Custom Settings” section.\nconf-file /etc/smartdns/accelerated-domains.china.domain.smartdns.conf conf-file /etc/smartdns/apple.china.domain.smartdns.conf conf-file /etc/smartdns/chinalist.domain.smartdns.conf conf-file /etc/smartdns/google.china.domain.smartdns.conf With this setup, domestic domain resolution will no longer be slowed down, and DNS leaks won’t occur when accessing foreign domains.\nAd Blocking While DNS-level ad blocking has its limitations, the focus should be on avoiding false positives. Avoid using the infamous3 Anti AD list, which claims to be the “most accurate ad-blocking list in the Chinese-speaking community.” Instead, opt for alternative rules. I personally use 217heidai/adblockfilters, a well-curated aggregated rule set that can be pulled directly.\nwget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.confFinally, import the configuration file:\nconf-file /etc/smartdns/adblock.confAutomating Rule Updates Set up the following Crontab to automatically update all rules daily at 3 a.m.\n0 0 3 * * ? wget -O /etc/smartdns/adblock.conf https://gcore.jsdelivr.net/gh/217heidai/adblockfilters@main/rules/adblocksmartdns.conf 0 0 3 * * ? wget -O /etc/smartdns/accelerated-domains.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/accelerated-domains.china.domain.smartdns.conf 0 0 3 * * ? wget -O /etc/smartdns/apple.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/apple.china.domain.smartdns.conf 0 0 3 * * ? wget -O /etc/smartdns/chinalist.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/chinalist.domain.smartdns.conf 0 0 3 * * ? wget -O /etc/smartdns/google.china.domain.smartdns.conf https://gcore.jsdelivr.net/gh/Olixn/china_list_for_smartdns@main/google.china.domain.smartdns.confCan a Secondary Router Be Used? Unless IPv6 is completely disabled, it’s best to use the OpenWRT router as the primary router. When used as a secondary router, certain systems (looking at you, Windows) may ignore the IPv4 DNS broadcast by the secondary router and insist on using the IPv6 DNS broadcast by the ISP’s modem. I’ve tried modifying related registry keys, but to no avail. Ultimately, I had to call my ISP to switch the modem to bridge mode and use OpenWRT as the primary router.\nOverriding Clash Rules Even after configuring SmartDNS, running a DNS leak test while using Clash will likely show DNS requests being sent to Chinese DNS servers. This is because most VPN providers’ default configurations use encrypted DNS services from domestic tech giants like Tencent or Alibaba. While this prevents plaintext leaks to ISP DNS servers (and thus avoids calls from anti-fraud centres), it can still be unsettling for privacy-conscious users. Some providers don’t even include relevant configurations.\nFortunately, if you’re using Clash Verge Rev, it’s easy to override the provider’s configuration. Below is my override configuration, which can be pasted into the global extension configuration to replace the provider’s settings.\ndns: enable: true ipv6: false prefer-h3: true use-system-hosts: true nameserver: - \"https://dns.cloudflare.com/dns-query\" - \"https://dns.sb/dns-query\" - \"https://dns.google/dns-query\" - \"https://public.dns.iij.jp/dns-query\" - \"https://jp.tiar.app/dns-query\" - \"https://jp.tiarap.org/dns-query\" nameserver-policy: \"geosite:cn\": - system fallback: - \"tls://8.8.4.4\" - \"tls://1.1.1.1\" proxy-server-nameserver: - \"https://doh.pub/dns-query\" - \"https://223.5.5.5/dns-query\" direct-nameserver: - system Configuration Analysis According to Mihomo’s documentation4, the DNS request flow is as follows:\nflowchart TD Start[Client initiates request] --\u003e rule[Matches rules] rule --\u003e Domain[Matches domain-based rules] rule --\u003e IP[Matches IP-based rules] Domain --\u003e |Matched direct connection rule|DNS IP --\u003e DNS[Resolves domain via Clash DNS] Domain --\u003e |Matched proxy rule|Remote[Resolves domain via proxy server and establishes connection] Cache --\u003e |Redir-host/FakeIP-Direct miss|NS[Matches nameserver-policy and queries] Cache --\u003e |Cache hit|Get Cache --\u003e |FakeIP miss, proxy domain|Remote NS --\u003e |Match successful| Get[Uses resolved IP to match IP rules] NS --\u003e |No match| NF[nameserver/fallback concurrent queries] NF --\u003e Get[Query resolved IP] Get --\u003e |Cache DNS result|Cache[(Cache)] Get --\u003e S[Direct/proxy connection via IP] DNS --\u003e Redir-host/FakeIP Redir-host/FakeIP --\u003e |Query DNS cache|CacheThe nameserver-policy has a higher priority than nameserver and fallback. By specifying that websites in geosite:cn are resolved via the system DNS (i.e., SmartDNS), you can enjoy LAN-level response speeds and built-in CDN optimisation. Websites not in geosite:cn are resolved via nameserver/fallback, ensuring the reliability of results and preventing DNS leaks by using encrypted foreign upstream servers.\nReplacing GeoIP and GeoSite Databases Mihomo uses the official V2Ray GeoIP and GeoSite databases by default. However, these databases are somewhat incomplete and updated infrequently. I recommend replacing them with the enhanced Loyalsoldier/v2ray-rules-dat database. Simply download geoip.dat and geosite.dat and place them in the kernel directory. Alternatively, add the following override configuration for automatic/one-click updates:\ngeodata-mode: true geox-url: geoip: \"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat\" geosite: \"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat\"Clash Meta for Android For Clash Meta for Android (referred to as Clash), override settings should take into account the specific needs of mobile devices.\nThe Clash mobile app offers fewer flexible override options than the desktop version, Clash Verge Rev. Additionally, mobile devices frequently switch network environments, causing system DNS to change frequently. In such cases, testing shows that Clash needs to be toggled off and on again after each network change to refresh the system DNS. As a workaround, you can use a fixed domestic encrypted DNS for domestic queries.\nNavigate to Clash -\u0026gt; “Settings” -\u0026gt; “Override” and configure the following:\n“DNS” -\u0026gt; “Policy”: Force enabled “DNS” -\u0026gt; “Prefer h3”: Enabled “DNS” -\u0026gt; “Name Server”: Add two foreign encrypted DNS servers “DNS” -\u0026gt; “Name Server Policy”: Enter geosite:cn in the “Key” field and https://doh.pub/dns-query in the “Value” field. Download geoip.dat and geosite.dat from GitHub in advance, then go to Clash -\u0026gt; “Settings” -\u0026gt; “Meta Features” and use the “Import GeoIP Database” and “Import GeoSite Database” options to import them.\nWrapping Up Finally, run a DNS leak test to confirm that the issue has been resolved. If no domestic DNS servers appear, your configuration is correct. Enjoy the network optimisation and leak prevention provided by SmartDNS and Clash!\nReference: https://blog.lololowe.com/posts/8f1e/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSmartDNS is designed to handle latency efficiently. For details, see “How SmartDNS Avoids Slow DNS Resolution Due to Speed Testing”.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nA collection of Anti AD’s notable missteps is available.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSource: https://wiki.metacubex.one/config/dns/diagram/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025-02-06T17:01:51+08:00","image":"/2025/02/what-i-have-done-on-my-dns/cover_hu_1bda1c3daad4bc1b.webp","permalink":"/en/2025/02/what-i-have-done-on-my-dns/","title":"My Home Network Domain Name Resolution Plan"},{"content":"\u0026ldquo;I’m exhausted. I need a break.\u0026rdquo;\nHaving completed the formalities for my leave of absence, I dragged my weary body out of the school’s administration building, officially beginning my nine-month hiatus from university. Just two weeks prior, I had been out enjoying an all-you-can-eat barbecue with my flatmates, carefree and content. I could never have imagined that my mental health would deteriorate so rapidly. From the first onset of indescribable pain, to being prescribed medication at the hospital, and then deciding to suspend my studies, the whole process took just two weeks. And yet, two days before completing the paperwork, I had stocked up on enough daily necessities to last me an entire year. Now, as I packed everything to move back home, I couldn’t help but curse how heavy it all was.\nThe day I cleared out my room, the sky poured with rain. As I entered my dorm, the router lights were still blinking on my desk, an unfinished can of cola sat nearby, and the ketchup from a takeaway meal was still messily spilt on the surface, as usual. \u0026ldquo;I won’t be back for a long time\u0026hellip;\u0026rdquo; Indeed, this desk might never look the same again.\nTogether with a flatmate who had also taken leave, we split the remaining box of cola, dismantled a newly purchased ergonomic chair, packed up miscellaneous items into boxes, and loaded everything into the car in the pouring rain. Watching my bed gradually empty, for a fleeting moment, I considered abandoning the leave of absence and returning to normality. Before leaving, I stood staring at my now-bare bed, lost in thought, barely noticing my flatmate patting my shoulder. After saying goodbye to someone I had lived with for over three years, returning borrowed library books, and handing in my dorm keys, I finally set off for home.\nEven so, occasional meet-ups are still a must Even while on leave, our tradition of gathering for meals as a group couldn’t be abandoned. Once my body began adjusting to the appetite-suppressing side effects of the medication, I rarely turned down dinner invitations. Going out for food and drinks, sharing updates about our lives, exchanging information, and splitting the bill—it’s a simple yet delightful experience. The only downside is that, considering my past issues with alcohol abuse, my doctor specifically advised me to quit drinking. So now, when we go out, I usually just sip on some watermelon juice as a token gesture.\nBed by 11, up by 8 Although quetiapine’s efficacy is limited, it did manage to turn my 5 a.m. bedtime and 9 a.m. wake-up routine into a more reasonable 11 p.m. to 8 a.m. schedule, at least for the first month. I also started going to the gym. Weightlifting is a fascinating activity—eating loads of carbs and protein while gradually stacking weights. Within two months, I’d gained 10 kilograms, 9 of which were muscle and just 1 was fat. It was beginner gains at its finest.\nOvertraining, however, especially on leg days, left me so wired that it disrupted my sleep. I had no choice but to scale back my workouts. Given my situation, prioritising sleep for mental health was the most sensible choice.\nTravel, near or far Travel remains an essential part of life. My trips to Hong Kong in September and Guilin in October were among the few longer journeys I took this year, but I also enjoyed numerous local outings, such as a visit to the Seven Immortals Temple in Luxi Village. Whether near or far, travelling is still travelling. As the saying goes, \u0026ldquo;Read a thousand books, travel ten thousand miles.\u0026rdquo; The satisfaction and sense of connection that comes from applying knowledge gained from books to real-world experiences are some of the best reminders of one’s existence and place in the world.\nThe sky doesn\u0026rsquo;t rain dumplings, but it does rain kittens The sky might not rain free lunches, but it might just rain a ginger kitten. On my way to the gym one day, I was drawn to a meowing sound coming from a fire escape. Upon investigation, I stumbled upon this little cat.\nIt’s been nearly six months since I adopted her, and she’s brought me plenty of joy—along with a fair number of scratches and one shredded leather sofa. Animals simply can’t understand humans, and there were countless times I wanted to throw her out. Yet, I always let her stay in the end. That’s how it is with cats. They might drive you mad at times, tempting you to abandon them, but the irreplaceable emotional bond they offer compels you to keep them around.\nPlants are essential for modern living Plants are not only aesthetically pleasing but also help purify the air by absorbing formaldehyde and trapping dust. Most importantly, they bring a touch of nature into indoor spaces, which is beneficial for mental and physical well-being. In my opinion, greenery is a must-have for modern living.\nMy desk underwent significant changes this year. Beyond upgrading my setup with a new monitor and screen bar, I also introduced some plants. I chose snake plants for their low maintenance and ability to thrive in low-light conditions. With the right amount of fertiliser, my two snake plants even sprouted new shoots. Succulents are even easier to care for, requiring minimal watering. The only exception is a plant called \u0026ldquo;string of coins,\u0026rdquo; which has been a mystery to me—too much water and it struggles, too little and it withers, and fertiliser seems to do nothing. Clearly, I have much more to learn about gardening.\nMy online presence I’ve never \u0026ldquo;managed\u0026rdquo; my blog for traffic; I simply write what’s on my mind. As a result, my readership is fairly modest. Thanks to search engines, though, it’s not entirely deserted. This year, I also started a livestream channel, which primarily serves as a repository for my gaming recordings. Unsurprisingly, there isn’t much of an audience—it’s hard to imagine many people would want to watch an obscure streamer play mundane games.\nIn osu!, I climbed from 300pp to 3439pp this year. Having recently switched to a tablet, I’m still getting used to it, so my progress has temporarily plateaued.\nThe desire to live For a long while, I’ve gradually lost interest in everything in life. One manifestation of this was a complete lack of desire—wanting nothing, buying nothing. I wouldn’t buy new clothes unless they were worn out, wouldn’t get a haircut unless my fringe was blocking my vision, and had no interest in new products. Until recently, even my libido seemed to be fading. Desire is truly a curious thing—it can’t be too strong, but it can’t be too weak either. A person without desire can’t truly live. Even if the human instinct to survive acts as a safety net, preventing an early demise, life becomes meaningless—a fate worse than death.\nA lack of desire leaves you uninterested in everything, aimless and adrift, which only deepens the pain. Having a healthy amount of desire gives you something to strive for, creating a positive cycle of effort and fulfilment.\nWhen properly channelled, desire can drive progress. \u0026ldquo;Don’t believe that pressure can be turned into motivation; pressure only turns into medical records. True motivation comes from the pursuit of joy and passion.\u0026rdquo; Indeed, in my current state, the pressure has been lifted. But that pressure has already manifested in my medical records. Deep down, I no longer seek joy or passion. This feeling has only grown over the years—whatever I do, I’m met with a deep, unrelenting exhaustion. While my body remains healthy, my soul is battered and bruised, weak beyond repair.\nI’m truly afraid that my desires are fading even faster than before. I’ve long since lost the fiery spirit my peers possess, lost the yearning for love, and lost the drive to pursue a better life that others my age hold dear\u0026hellip;\nAs 2024 draws to a close, it’s clear that this year was a pivotal one—both in my university life and my journey as a whole. This was the year I began to rediscover my desire for life and my confidence in the future. Everything I’ve tried this year has been an effort to \u0026ldquo;reignite the desire to live.\u0026rdquo; After all, who doesn’t want to \u0026ldquo;do what they love and love what they do\u0026rdquo;? The depletion of desire and motivation can’t simply be willed away. When both \u0026ldquo;what you love\u0026rdquo; and \u0026ldquo;what you want\u0026rdquo; are missing, how many people can truly offer a meaningful solution?\n","date":"2024-12-26T17:57:10+08:00","image":"/2024/12/2024-end-of-the-year-summary-public/image-13_hu_5d398f1badf5ff5d.webp","permalink":"/en/2024/12/2024-end-of-the-year-summary-public/","title":"Life in 2024"},{"content":"I bought a 5ber SIM card, only to find that it was surprisingly picky about devices – not a single one of my Android phones was compatible. I had long been dissatisfied with my previous backup phone, the Redmi Note 9 5G. Taking this opportunity, I sold the Redmi Note 9 5G and purchased a Xiaomi Mi 10.\nAs for why I chose the Xiaomi Mi 10 over other phones, there wasn’t any particularly compelling reason: firstly, it’s on 5ber’s list of officially supported devices; secondly, it’s cheap – with a budget of 500 to 600 RMB, you can easily get one; thirdly, it integrates well with my Mi Home devices; and finally, as a \u0026ldquo;new-generation classic\u0026rdquo;, it’s a well-rounded choice.\nI didn’t deliberate much and casually ordered one on Xianyu (a Chinese second-hand marketplace). In hindsight, such a hasty purchase was a bit reckless. Let’s talk about the phone’s condition: in a word, \u0026ldquo;battered and bruised\u0026rdquo;. This phone had a replaced back cover, a frame that had been dropped so many times it was barely recognisable, a charging port with visible wear and tear, and a screen with a stain in one corner that couldn’t be removed. Looking back, the 619 RMB I paid for this 8GB + 256GB Xiaomi Mi 10 was clearly overpriced.\nMy Requirements for a Backup Phone When it comes to my requirements for a backup phone, after some thought, I realised they’re not all that extensive. Firstly, it must fulfil the basic function of a phone – communication: I need a phone that supports 5G and can write 5ber SIM profiles. Secondly, battery life should be as long as possible, as this phone is intended to serve as my mobile hotspot when I’m out and about. Thirdly, it should cater to my occasional tinkering needs, with a wealth of modding resources and an active community – something the Xiaomi Mi 10, as a \u0026ldquo;new-generation classic\u0026rdquo;, excels at. Finally, it must have a high-refresh-rate display, as I’ve had enough of the 60Hz screen on the iPhone 12 series.\nReplacing the Battery The original battery had significant degradation, with Battery Guru estimating its capacity at only 3100mAh/4780mAh – just 65% health. You might ask why I bought a phone with such a poor battery. Well, it’s because the \u0026ldquo;battery\u0026rdquo; section on Xianyu’s inspection report looked fine at first glance but actually just said \u0026ldquo;Android devices cannot detect\u0026rdquo; – a common issue. With the battery in this state, even light use of apps like news feeds would drain it in under four hours, and standby time was so bad I didn’t even want to take it out. Since one of this phone’s intended uses is as a hotspot, it needs to be portable. At this point, replacing the battery was a must.\nInitially, I planned to have the battery replaced at an authorised service centre. A quick online search revealed a price of 159 RMB, including labour and parts. Considering the phone itself only cost 619 RMB, I decided to replace it myself instead. Back in the days when I was active on Coolapk (a Chinese tech community) six or seven years ago, \u0026ldquo;Zhongdian\u0026rdquo; (a third-party battery brand) was already gaining popularity as the first batch of Xiaomi Mi 6 phones needed battery replacements. So, I bought a Zhongdian extended-capacity battery for 89 RMB and replaced it myself.\nThe battery replacement process can be found on Bilibili1 – just follow the steps. During the replacement, I discovered another issue with this phone: it had clearly been opened before, and not very carefully. The anti-tamper stickers had been disturbed, and, amusingly, some screws were missing. Although I knew the back cover had been replaced when I bought it, the shoddy internal handling didn’t seem to affect the phone’s functionality, which I found remarkable.\nBack to the topic, the battery replacement went smoothly, and the extended-capacity battery didn’t push against the back cover. Once a protective case was on, it was completely unnoticeable. After the replacement, battery life was transformed – it now easily lasts a full day. In short, I’m finally willing to take this phone out with me.\nUnlocking the Bootloader Modern Xiaomi is no longer the enthusiast-friendly brand it once was.\nUnlocking the bootloader – such a simple and basic requirement – has become a major hassle with HyperOS. According to the system’s requirements, you need to reach level 5 in the Xiaomi community, apply for beta access to unlock, pass a quiz, then bind your Xiaomi account to the device, insert a SIM card, and wait 168 hours before you can unlock it.\nAre you joking??\nFortunately, the Xiaomi Mi 10 originally shipped with MIUI, so there are ways to bypass the additional unlock restrictions introduced by HyperOS2. However, the original 168-hour wait for MIUI cannot be bypassed, so I’ll have to wait a week before I can start tinkering. In the meantime, I’ll begin preparing.\nRemoving Ads The previously popular tools like \u0026ldquo;Dashi Purify\u0026rdquo; and \u0026ldquo;Li Tiaotiao\u0026rdquo; have ceased updates due to legal action from Tencent. While browsing online, I discovered \u0026ldquo;GKD\u0026rdquo;, an open-source alternative. It’s a shame about the Dashi Purify license I purchased earlier. The setup process involves Shizuku, which seems to have developed a much better ecosystem compared to seven or eight years ago.\neSIM The Xiaomi Mi 10 doesn’t natively support eSIM, but I had already prepared a 5ber SIM card. The eSIM functionality works well, with no issues using data roaming. The basic retention plan I activated is also fine. To set up an eSIM, you’ll need a VISA or Mastercard and can follow the many guides available online.\nPersonally, I find ClubSIM quite convenient – it only costs 6 HKD per year to keep the number active. As for the widely-discussed Ukrainian LifeCell, I wouldn’t recommend it, as their numbers are often flagged for security checks. For example, Telegram SMS verification might be forced to switch to voice call verification, requiring you to receive an international call while roaming. LifeCell’s roaming fees aren’t cheap, so ClubSIM, which faces fewer security checks, is a better choice for keeping a number active.\nFor heavy data roaming needs, 3HK’s roaming plans are a good option. However, since I only occasionally need a native IP and mostly just need to receive SMS, I opted for ClubSIM.\nSatisfactory Performance and Battery Life Over the years, I’ve gradually stopped playing mobile games, which I didn’t play much to begin with. Now, I only need my phone to fill idle moments when I’m out. My sole performance requirement is smooth day-to-day use. For gaming, as long as it can handle light games like Honor of Kings, I’m satisfied. With such modest demands, even the nearly five-year-old Xiaomi Mi 10 performs admirably. I won’t embarrass this old device by running benchmarks like AnTuTu.\nAfter the battery replacement, the Xiaomi Mi 10 easily meets my on-the-go battery life needs: it can handle a full day of navigation without issue, and even with hotspot usage, there’s no concern about running out of power. Overall, I’m satisfied with its battery life.\n\u0026ldquo;It’s Still Usable\u0026rdquo; Now for the downsides. Firstly, the 8GB of RAM. While this was acceptable a few years ago, it feels a bit limiting today. Forget about keeping apps running in the background – I’m just grateful it stays smooth during regular use. As a result, I’ve had to get into the habit of manually clearing all apps from the background more frequently.\nNext, the awful bootloader unlock process – a direct criticism of HyperOS. Why should I need to reach level 5 in your community and pass a quiz to unlock my own phone? It’s absurd. This erosion of user rights has significantly worsened my impression of Xiaomi.\nFinally, the terrible charging port design. Unlike other phones with modular charging ports, the Xiaomi Mi 10’s port is soldered directly to the motherboard. Over time, it wears out and becomes loose, leading to charging issues. Replacing it is a hassle, as it requires resoldering. My phone’s charging port is already showing slight signs of looseness, which will be an additional problem if it needs replacing in the future.\nIn Conclusion… Overall, the Xiaomi Mi 10 works well and meets all my requirements for a backup phone. It’s a well-rounded device, and despite the aforementioned issues, it remains a solid choice. I’m fairly satisfied with this backup phone purchase and hope this Xiaomi Mi 10 will accompany me for a good while – at least until it’s time to sell it. 3\nI followed this tutorial.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRefer to the GitHub project: MlgmXyysd/Xiaomi-HyperOS-BootLoader-Bypass\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSource of the cover image is watermarked.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-12-05T19:56:29+08:00","image":"/2024/12/umi-experience/cover_hu_d7a8279c694f004e.webp","permalink":"/en/2024/12/umi-experience/","title":"Using a Xiaomi Mi 10 as a Backup Phone in Late 2024"},{"content":"For some unknown reason, my computer never automatically syncs its time with the NTP server. While it doesn\u0026rsquo;t have much of an impact in the short term, if left unchecked, a significant time drift could cause problems with applications that rely on timestamps, such as SSL and TOTP. Additionally, manually pressing the sync button is quite inconvenient. Today, I decided that I must resolve this issue.\nSpecialPollInterval After searching online for a while, I found that someone mentioned the SpecialPollInterval key. Its specific location is HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\W32Time\\TimeProviders\\NtpClient. Microsoft’s official documentation describes it as follows:\nThis entry specifies the special poll interval in seconds for manual peers. When the SpecialInterval 0x1 flag is enabled, W32Time uses this poll interval instead of a poll interval determined by the operating system. The default value on domain members is 3,600. The default value on stand-alone clients and servers is 604,800.\nFor regular clients, the default value of this key is 604800, which means it syncs once a week. This interval is obviously too long, so I set it to 3600 to make it sync every hour.\nw32tm A few days later, when I checked the time again, I found that it still hadn\u0026rsquo;t automatically synced. This time, the drift had reached 15.8 seconds.\nI then searched for more information about \u0026ldquo;SpecialPollInterval\u0026rdquo; to understand its specific function and why it didn\u0026rsquo;t meet my expectations. I came across a troubleshooting guide from Microsoft. The guide pointed out: “Each time a client polls the time sample from the NTP server, the NTP client enters the SPIKE state. The time service manages its internal state, and if the client enters the SPIKE state, the client will not sync its time.”\nTo resolve this issue, you need to configure Windows Time to use MinPollInterval/MaxPollInterval as the polling interval.\nw32tm /config /update /manualpeerlist:cn.pool.ntp.org /syncfromflags:MANUALService Not Started When I ran this command, I received a \u0026ldquo;service not started\u0026rdquo; message, indicating that the service responsible for time synchronization had not started at all. I’m not sure if this is a Windows bug or something I inadvertently caused later on. Regardless, the first step is to start this service:\nnet start w32time w32tm /register # Registers basic registry keys and related servicesNext, press Win + R, run services.msc to open the services management panel, find the \u0026ldquo;Windows Time\u0026rdquo; service, and set its startup type to \u0026ldquo;Automatic\u0026rdquo; to ensure it starts automatically on boot.\nAfter restarting the computer, open \u0026ldquo;Settings\u0026rdquo; \u0026ndash;\u0026gt; \u0026ldquo;Time \u0026amp; Language\u0026rdquo; \u0026ndash;\u0026gt; \u0026ldquo;Date \u0026amp; Time\u0026rdquo; \u0026ndash;\u0026gt; \u0026ldquo;Additional Clocks,\u0026rdquo; switch to the \u0026ldquo;Internet Time\u0026rdquo; tab. If you see the \u0026ldquo;Next synchronization\u0026rdquo; message, it means the system is finally working correctly.\n","date":"2024-11-18T18:03:47+08:00","image":"/2024/11/windows-ntp-hiccup/cover_hu_e196169a0c972a34.webp","permalink":"/en/2024/11/windows-ntp-hiccup/","title":"Windows Troubleshooting: Fixing Automatic Time Sync Issues"},{"content":"Bank Cards HSBC Blue Lion This is the HSBC MasterCard debit card, commonly known as a debit/savings card in the mainland. After opening an account in Hong Kong, you can apply for it directly through the app; just remember to update your Chinese address in the user profile before applying. The card will be mailed to you within one to two weeks.\nThis card shares a balance with HSBC\u0026rsquo;s UnionPay card (Red Lion) and offers 0.4% cashback on every purchase with no limit. This cashback offer even surpasses many domestic credit cards. It allows global withdrawals without fees and has no annual fee, making it a great card for spending.\nICBC Constellation Card This card is tailored by ICBC for students with needs for overseas shopping. It has a zero credit limit and sits between a credit card and a debit card. The main attraction is the VISA logo. Despite having the VISA logo, using it isn\u0026rsquo;t very convenient: you need to manually exchange currency before overseas payments, and the currency must be correct, or the transaction will fail.\nThe card design looks decent but is prone to scratches and paint chipping, so it\u0026rsquo;s best kept at home. Fortunately, you don\u0026rsquo;t need to visit the ATM often with this card.\nIndustrial Bank Global Life \u0026amp; Unlimited Card A UnionPay debit card, as the name suggests, designed for those with cross-border financial needs. It waives fees for the first 30 cross-border wire transfers and overseas ATM withdrawals, saving money for frequent cross-border transactions.\nThe Unlimited Card reportedly shares the Global Life benefits, but with a transparent card design that looks more interesting. The appearance is subjective; as long as it\u0026rsquo;s practical, that\u0026rsquo;s what matters.\n5ber SIM Card A SIM card that was popular a couple of years ago, allowing phones that don\u0026rsquo;t support eSIM to use eSIM. I recently got one. 5ber is one of the simplest eSIM to physical SIM card solutions; usually, you just need to scan the eSIM QR code. After getting it, I downloaded a Ukraine lifecell and Hong Kong ClubSIM, both great for keeping numbers.\nWhile the world is embracing eSIM, domestic carriers are discontinuing eSIM services, which is baffling. The real-name system issue can be easily resolved by referring to Hong Kong\u0026rsquo;s approach, yet domestically there\u0026rsquo;s a regression with the so-called \u0026ldquo;Super SIM Card.\u0026rdquo; It\u0026rsquo;s uncertain when convenient and affordable eSIMs will be available domestically.\nWacom Tablet Buying a tablet for drawing? No way, who buys a tablet just for drawing? It\u0026rsquo;s for playing osu!, of course. Recently, after achieving 3000pp with a mouse, I decided to get a tablet to try it out. Since it\u0026rsquo;s for osu!, I went for the cheapest option, the Wacom CTL-472, an entry-level tablet.\nUpon trying it out, I found that switching from a mouse to a tablet involves a challenging adaptation period. Not only does the difficulty of playable songs differ, but the muscle soreness in the hand also varies. After two days, I decided to switch back to the mouse temporarily.\nXiaomi 10 The Xiaomi 10 Pro I recommended to my dad years ago is still working well after four years, so I got a Xiaomi 10 as a backup phone. The Xiaomi 10 is somewhat of a new generation staple, with balanced performance, battery life, and screen quality. After using it for a while, I\u0026rsquo;m even considering making it my main phone.\nThe main reason for buying the Xiaomi 10 was the 5ber mentioned earlier—truly a case of buying a whole dumpling for a drop of vinegar. 5ber\u0026rsquo;s phone compatibility is peculiar; many Android phones I expected to support it didn\u0026rsquo;t, forcing me to sell my previous backup phone and get this Xiaomi 10.\nRegarding whether to flash the ROM, I\u0026rsquo;m currently undecided. On one hand, HyperOS is already good enough, rich in features, and smooth in use. On the other hand, the well-known privacy issues make me consider flashing it to Lineage. For now, I\u0026rsquo;ll use it for a while—it\u0026rsquo;s also a kind of privacy negligence.\nVirtual Private Server (VPS) My server is about to expire, so I\u0026rsquo;m looking for new machines. Currently, I\u0026rsquo;ve tried Contabo and Hetzner and plan to try Netcup. With each new machine, I reinstall the system and test if it can handle the growing data of my Iceshrimp service. Given that Cloudcone\u0026rsquo;s performance is poor, I need a stable machine.\nI find Hetzner quite good, with strong performance, guaranteed SLA, and a nice self-developed panel. The return quality of the three networks is good; choosing the Nuremberg data center, China Mobile uses CMIIN2, and the other two use regular routes. Although the price is not as low as RackNerd or CloudCone, it\u0026rsquo;s among the lowest for machines with the same performance, plus better abuse prevention mechanisms, making it suitable for running services.\nAs for Contabo, I find its performance weak and the SLA guarantee excessively poor (95% SLA, which means it can be down for over 18 days a year), along with many restrictions (like not opening mail ports). After trying it, I don\u0026rsquo;t recommend it.\nTaobao Data Plan Card Buying cheap data cards on Taobao requires mental preparation for traps. This time, I bought one and roughly figured out the data card tricks: initially, they show a 9 yuan/month plan in the promo image; after ordering, they claim they can\u0026rsquo;t ship for various reasons; finally, they use all kinds of wordplay to trick you into switching to a more expensive and worse plan.\nThe one I got initially advertised as 9 yuan/month with 105G general + 30G directed data. After claiming they couldn\u0026rsquo;t ship, they switched it to a Unicom 70G national + 120G provincial data plan, and the actual monthly rent seems to have changed to 19 yuan (though the actual fee will be known next month). Worse, it has a two-year contract. Nevertheless, it\u0026rsquo;s still relatively cheap, so I\u0026rsquo;ll use it for now—I\u0026rsquo;ll discard it after half a year. Understanding the traps and gaining experience is a good return.\niOS p12 Certificate Many people think Android is synonymous with tinkering, but that\u0026rsquo;s not true. iOS can be just as customizable if you\u0026rsquo;re willing to explore. The p12 certificate was originally a tool provided by Apple for developers to test apps that haven’t been uploaded to the App Store. Any app signed with a developer\u0026rsquo;s p12 certificate can run on iOS/iPadOS.\nApple limits the number of apps that can be signed with a non-developer account to three, while developer accounts can sign an unlimited number. The developer account subscription costs $99/year, which is unnecessary for those wanting to install just a few apps. Some people sell already signed developer certificates online; you can buy and use them directly.\nCurrently, I\u0026rsquo;m using it to install BHTwitter and EhPanda. By installing Esign and importing the certificate, I can freely install software and add third-party software sources. Free sources are generally lower quality, while paid sources offer better quality and a wider selection. However, we can\u0026rsquo;t experience this today.\nThese certificates can also be used for app duplication, game plugins, etc., offering high playability. Most certificates are reasonably priced, ranging from 6 to 100 yuan, making them a good deal compared to Apple\u0026rsquo;s official $99/year developer subscription.\n","date":"2024-11-15T18:39:17+08:00","image":"/2024/11/recent-gadgets/cover_hu_7ec602792c5b9b6.webp","permalink":"/en/2024/11/recent-gadgets/","title":"Some Recent Gadgets I've Tried"},{"content":"A Short Stay in Guilin Here comes that cliché again: \u0026ldquo;Guilin\u0026rsquo;s scenery is the best under heaven.\u0026rdquo;\nWhen it comes to Guilin, most people have either heard of it or visited it. World-class landscapes paired with a third-tier urban build. Over a decade ago, I lived in Guilin for a while: the Elephant Trunk Hill in the city, the boat tours of the Two Rivers and Four Lakes; outside the city, Yangshuo, bamboo rafting on the Li River, and barbecues on the river banks.\nTourist destinations that once intrigued me no longer do. You might have heard the jest, \u0026ldquo;He who has never been to the Great Wall is not a true man, and he who goes again is a fool,\u0026rdquo; which captures that feeling—it\u0026rsquo;s like a sugary drink, heavenly at first taste but just sickening once the novelty wears off.\nI had just returned from Hong Kong and within days, I was off to Guilin, albeit reluctantly. The trip to Hong Kong had been oppressively stressful, filled with travel fatigue. It felt like I would need the entire National Day holiday just to recover.\nBut life is always full of bizarre twists. Somehow, I found myself embarking on a new journey to this city I had almost thoroughly explored.\nA City Reflects a Mood If Changsha is about entertainment to death, Guilin, in my eyes, is relatively laid-back: unlimited servings of Guilin rice noodles for six yuan a bowl, enough to cover three meals a day for just 18 yuan; during the off-season, the Two Rivers and Four Lakes are nearly deserted due to its overcapacity, resulting in significantly lower hotel and tourist site prices. Thus, my previous stays in Guilin were quite affordable, allowing me to enjoy its landscapes on a modest budget.\nOver the National Day holiday, I stayed at a relative\u0026rsquo;s house who was away on a trip, so accommodation was free; my mother\u0026rsquo;s company covered all the fuel costs for the family car; coupled with Guilin\u0026rsquo;s generally low cost of living, this way of changing scenery was indeed very cheap economical.\nSo, I ended up happily staying in Guilin for five days.\nRaspberry Pi and Cloudflare Tunnel The Raspberry Pi was purchased with my roommate AA for a project, and after the project, it became mine. Since it\u0026rsquo;s a ready-to-use computing resource, I installed Raspberry Pi OS Lite 64bit on it, connected it to the network, and used it as a server.\nBeing an ARM architecture, the Raspberry Pi has very low power consumption, less than 5W in standby and 25W at peak, rarely reaching peak power, costing at most a few tens of yuan in electricity per year.\nThe toughest part was dealing with network issues, thanks to China\u0026rsquo;s unique internet environment. Publishing content from a home server to the public internet is a headache: ISPs typically don\u0026rsquo;t provide public IPv4 addresses by default, and even if they do, they\u0026rsquo;re dynamic; and forget about opening common ports like 80 or 443; getting a static IP isn\u0026rsquo;t impossible, but requires a business line, which not only involves various certifications but is also at least ten times more expensive than residential internet.\nI didn\u0026rsquo;t have high hopes for Cloudflare Tunnel initially, given the numerous online complaints about its frequent disconnections and packet losses. However, to my surprise, it performed much better than expected. Except for the relatively long loading times for root documents, other static resources benefited greatly from Cloudflare\u0026rsquo;s powerful Edge Network, loading very quickly. And, perhaps due to regional differences or between ISPs, my Tunnel remained stable and relatively fast even during peak hours. This is a test blog I set up on the Raspberry Pi; if you\u0026rsquo;re interested in seeing the specific acceleration effects, check it out: https://rpiblog.l3zc.com.\nSolving the problem of internal network penetration greatly enhanced the potential uses of the Raspberry Pi, allowing for many more interesting projects in the future. I can only hope that the ISP doesn\u0026rsquo;t arbitrarily cut off my Tunnel.\nMy RSS Feed Joining Follow, Verifying My Blog I first heard about Follow from DIYGod\u0026rsquo;s tweet, another open-source project by DIYGod following RSSHub, still in its Alpha stage but already quite polished.\nFollow is an RSS reader that integrates deeply with RSSHub and RSS3 (For real, why??). Unlike most RSS readers, Follow uses a cloud server to fetch content, hence there\u0026rsquo;s a limit on the number of feeds you can subscribe to; currently, it also requires an invitation code during its beta phase, which fortunately isn\u0026rsquo;t too hard to get with the help of the online community.\nI\u0026rsquo;ve been using Follow for a week now, and overall, the experience has been very satisfactory; it\u0026rsquo;s powerful, smooth, and features an unlimited (for now) LLM summary/translation function.\nFollow is also a community where feed maintainers can verify their own feeds, allowing them to receive donations. Donations are implemented using a type of ERC-20 token called Power (for real, why?? I just want to read RSS Feeds, why do I need to deal with blockchain?). Although I still see this approach as controversial, Follow has polished this feature well, making the experience enjoyable.\nFixing RSS Feed Reading Experience Issues The original RSS Feed on my blog was generated using Hugo\u0026rsquo;s default template. The content pulled directly from the .Content variable included a mess of different resolution image URLs generated by built-in image processing features, and code blocks included line numbers and highlighting marks produced by chroma, which most readers can\u0026rsquo;t handle properly. So, I added a few Render Hooks to fix these issues in Hugo:\n./layouts/_default/render-codeblock.rss.xml:\n{{ .Inner }}./layouts/_default/render-image.rss.xml:\n{{- $url := urls.Parse .Destination -}} {{- if not (or (eq $url.Scheme \"http\") (eq $url.Scheme \"https\")) -}} {{- $result := .Page.Resources.GetMatch (printf \"%s\" (.Destination | safeURL)) -}} {{- with $result -}} {{- end -}} {{- end -}}This way, the previously poor reading experience is no longer an issue (hopefully).\nI also made some minor front-end optimizations. Now, when copying code, it\u0026rsquo;s no longer possible to accidentally select the line numbers next to the code. The font has been switched to Jet Brains Mono, and the padding has been adjusted to make copying easier.\nBesides blogging, fixing bugs on the blog is always a pleasant task. This modification not only improved my mood but also enhanced everyone\u0026rsquo;s reading experience.\n","date":"2024-10-23T02:05:20+08:00","image":"/2024/10/guilin-raspberrypi-and-rss-feed/cover_hu_9e407f5dddff8939.webp","permalink":"/en/2024/10/guilin-raspberrypi-and-rss-feed/","title":"Recent Updates: Guilin, Raspberry Pi, and My RSS Feed"},{"content":"I recently visited Hong Kong and brought back some souvenirs. I’ve compiled these souvenirs and my memories of the trip into this article as a keepsake.\nOctopus Card I had already activated my Octopus card before arriving in Hong Kong. The Octopus card can be used almost everywhere in Hong Kong: MTR, convenience stores, trams, buses, the Peak Tram, Star Ferry\u0026hellip; you name it, and it probably works there.\nYou can activate an Octopus card directly in Apple Pay using RMB, with a 50 HKD deposit. Though it\u0026rsquo;s refundable, I wonder how many people actually bother to get it back—it’s more like a card activation fee. However, there are no extra fees for using RMB in Apple Pay to activate and top up the card, and the exchange rate is quite favorable. Thumbs up for that.\ncsl. SIM Card The cheapest SIM card for tourists in Hong Kong is CMHK’s MySIM, but I opted for the more expensive csl., almost double the price. Why? To keep my number active, of course.\nEven though I knew CSL\u0026rsquo;s ClubSIM could let me keep a number active for just 6 HKD per year, the first convenience store I visited didn’t have that card on shelf. So, I had no choice but to go with csl.’s Hong Kong Tourist SIM. With a 50 HKD top-up, it extends the validity for 180 days. Doing the math, that’s 100 HKD a year to keep the number active. Annoying! If only I had found a store selling ClubSIM.\nOf course, this card can be kept as a memento.\nSUA Registration Label / Hong Kong Government Letter The first thing I did upon arrival in Hong Kong was to pick up the SUA (Small Unmanned Aircraft) registration label I had applied for earlier.\nAccording to Hong Kong law, Chapter 448G, \u0026ldquo;Small Unmanned Aircraft Order,\u0026rdquo; my drone falls under Category A2, requiring registration with the Civil Aviation Department (CAD), along with affixing a QR code from the CAD. I also needed to register online as a \u0026ldquo;remote pilot\u0026rdquo; to obtain the necessary license. Only with both can I legally fly the drone.\nSince there are no direct flights from Changsha to Hong Kong, I entered through the West Kowloon High-Speed Rail Station. From there, I headed straight to Austin Station, took the Tuen Ma Line to the Tung Chung Line, and headed for the airport. The convenience of direct access to the city center from high-speed rail was overshadowed by the long and painful journey to and from the CAD office.\nAfter getting off the Tung Chung Line, I transferred to the S1 Airport Bus and got off at the \u0026ldquo;CAD Headquarters; Tung Yiu Road\u0026rdquo; stop, where you’re greeted by this lovely middle-of-nowhere scene:\nHong Kong is really hot. Even though the air conditioning indoors is strong, there’s no AC in this remote area. For someone like me who sweats easily, it was hell. After a short uphill walk, I was drenched. Thankfully, once inside the CAD office, I was greeted by some much-needed air conditioning. The staff were very friendly and efficient, quickly retrieving the label I had applied for two months ago. They also helped me complete the drone pilot license process (just reviewing some rules, nothing to worry about).\nAnd so, I happily got my registration label.\nHSBC Bank Card Opening a bank account in Hong Kong has many benefits, such as not needing the 500,000 HKD minimum deposit to trade Hong Kong stocks, and not being subject to mainland China’s foreign exchange controls. Since I had enough time on this trip, I decided to open an account early in the morning.\nMy hotel was in Wan Chai, and I wanted to go to the HSBC headquarters in Central, which meant traveling from the west to the east side of Hong Kong Island. There are two main ways to traverse the island: the MTR Island Line or Hong Kong’s famous trams, nicknamed \u0026ldquo;ding dings.\u0026rdquo; Compared to the expensive MTR, the ding ding is one of the best transportation experiences in Hong Kong. No matter how far you travel, the fare is always a flat 3 HKD. For such a low price, you get to experience a century-old tram system. With dedicated tracks, the ding ding rarely gets stuck in traffic, slowly making its way through the bustling streets of Hong Kong, occasionally ringing its bell. The streets are filled with noise: the roar of double-decker buses, the hum of the crowd, and even the beeping sounds of traffic lights for the visually impaired. This is the vibrant, noisy Hong Kong.\nThe main entrance of the HSBC headquarters is actually on Queen’s Road Central, not the Des Voeux Road entrance by Statue Square, though most people use the latter due to its convenient public transport connections. After getting off at Bank Street, you’ll see the HSBC headquarters right across. Elevator up, and you\u0026rsquo;re there. An appointment to open an account? Don’t be ridiculous. Some branches are booked a month in advance—who can wait that long? Just walk in, tell the receptionist you want to open an account, and they’ll give you a number on the spot. I have no idea where all those people with appointments go, because there was barely any wait when I went. I only had to wait for one person. The service was great, and after a few questions about why I wanted to open an account, I got my card on the spot.\nAfter opening the account, I strolled around Central, visiting the Court of Final Appeal, Statue Square, the Cenotaph, Prince of Wales Barracks, and the government headquarters in Tamar. It’s impressive how you can find so many historical monuments and buildings in such a skyscraper-filled area, showcasing Hong Kong’s rich history. With so many renowned financial institutions and government offices gathered here, Central truly lives up to its reputation as Hong Kong’s political and economic center.\n\u0026ldquo;Besieged City\u0026rdquo; and \u0026ldquo;書店有時\u0026rdquo; These two books don’t hold any special meaning. I just found them interesting while browsing a bookstore. Together, they cost me nearly 300 HKD—Hong Kong is really expensive.\nHong Kong’s publishing industry was once thriving, but since the handover, it has gradually declined under the pressure of censorship. During this trip, every bookstore I visited, every book I read, gave me the feeling that authors and publishers were holding back, which was hard to accept. In the end, I chose one book published before the National Security Law took effect and another rare vertical-printed book from mainland China as keepsakes.\nPeak Tram Round-Trip Ticket Even though I could have used my Octopus card, I deliberately bought a round-trip Peak Tram ticket as a souvenir. At 88 HKD, the price is steep, but by Hong Kong standards, it could even be considered a daily commuting option. Hmm, maybe I’m just too poor as a mainlander.\nOn the way to the Peak Tram Central Terminus, I also visited the French Mission Building and St. John’s Cathedral, which are behind the HSBC headquarters. The latter is the oldest Western-style church in Hong Kong, with a solemn atmosphere, elegant decorations, and a simple, peaceful exterior. Central is full of treasures.\nThe Peak Tram has been in operation since 1888 and is now in its sixth generation. The flow of passengers boarding at the Central Terminus is constant, with many tourists but also locals. The design of the tram has evolved over time, and it even has air conditioning now. This puts it miles ahead of the ding ding, which has never changed its cars since it started service, though the ticket price is more than 20 times higher.\nUpon reaching Victoria Peak, unfortunately, the weather wasn’t great, so I couldn’t take any good photos. Even with the drone, the results weren’t ideal.\nGiven the weather, equipment, and the fact that I had the aperture too wide, I’m fairly satisfied with the outcome.\nHong Kong Dollar Banknotes Unlike other places, Hong Kong has a unique currency issuance system, with three note-issuing banks authorized and regulated by the Hong Kong Monetary Authority: HSBC, Standard Chartered, and Bank of China Hong Kong. The Hong Kong government issues the 10 HKD note. I didn’t manage to get any coins with the Queen’s portrait this time, which is a bit of a shame. But these banknotes from different issuers are still somewhat valuable as souvenirs (actually, I just didn’t spend them all).\nSome Other Photos As I write this, I realize that while these souvenirs can string together most of my experiences in Hong Kong, they can’t capture the random moments I discovered while wandering the city. Since this is meant to be a keepsake, key moments should be recorded as thoroughly as possible. So, I must pause my description of the souvenirs and instead document the rest of my time in Hong Kong through photos.\nHigh-Speed Rail Ticket (Reimbursement Proof) \u0026amp; Wrap Up Tickets have long been fully digitalized, and the only physical tickets you can get now are reimbursement proofs, which don’t function as actual tickets. I didn’t need to get reimbursed, but I still got the physical ticket, naturally making it my final souvenir before leaving Hong Kong.\n","date":"2024-10-12T11:49:42+08:00","image":"/2024/10/hk-souvenirs/cover_hu_330bd22de0169088.webp","permalink":"/en/2024/10/hk-souvenirs/","title":"What Souvenirs Did I Bring Back from Hong Kong?"},{"content":"Long story short, I recently started live streaming my gameplay. I don\u0026rsquo;t expect many viewers, Just, Why not? Plus, every streaming platform offers playback. It\u0026rsquo;s like free storage for every game recording, why not take advantage of it?\nTo get to the point, as you can see, I recently needed to stream on multiple platforms simultaneously. However, OBS itself clearly doesn\u0026rsquo;t support multi-streaming, and due to its lack of proxy settings and other well-known reasons, I couldn\u0026rsquo;t stream to twitch.tv. So, I started looking for solutions.\nDefining the Requirements Multi-platform simultaneous streaming Minimal performance impact One stream requires a proxy Multi-Streaming There is a ready-to-use plugin for multi-streaming: Github\nDownload the .exe installer from the Release page. After installation, remember to check \u0026ldquo;Multi-Output\u0026rdquo; in the \u0026ldquo;Dock\u0026rdquo; menu. By default, a separate window will pop up, and you can take this opportunity to adjust the layout of OBS.\nThe plugin settings are straightforward, just follow the same steps as you would in the original OBS.\nBilibili Live Assistant Oddly, Bilibili requires streamers with fewer than 50 followers to use their Live Assistant, and other domestic platforms may have similar rules. After enabling third-party streaming mode, the live panel will display the streaming configuration. You don\u0026rsquo;t need to follow the streaming address provided by the Live Assistant, as it inexplicably gives a changing local IP each time. Simply replace it with 127.0.0.1 or localhost.\nStreaming to Twitch Streaming to Twitch involves many unnecessary troubles due to the GFW.\nOBS does not support proxy settings. Initially, I planned to use Clash\u0026rsquo;s TAP mode to force proxy OBS traffic, but Clash\u0026rsquo;s TAP does not listen to rtmp traffic, so that plan failed. I decided to set up a forwarding server that can communicate with Twitch without being affected by the GFW.\nAfter some research, I found that Caddy does not support the RTMP protocol, while the nginx-rtmp module is specifically designed for RTMP. It seems to be the only choice.\nPrerequisites:\nFamiliarity with basic Linux command line operations. A server with docker and docker compose installed. A suitable command line text editor, such as nvim or the default nano in most distros. Create a directory:\nmkdir nginx-rtmp \u0026\u0026 cd nginx-rtmpCreate docker-compose.yml:\nservices: nginx-rtmp: image: tiangolo/nginx-rtmp:latest container_name: nginx_rtmp ports: - \"1935:1935\" # RTMP port volumes: - ./nginx.conf:/etc/nginx/nginx.conf restart: unless-stoppedCreate nginx.conf:\nworker_processes auto; events { worker_connections 1024; } rtmp { server { listen 1935; chunk_size 4096; application live { live on; push rtmp://live.twitch.tv/app/{Twitch Main Stream Key}; } } }Start the setup:\ndocker compose up -d # For older versions of compose plugin, use docker-compose upDon\u0026rsquo;t forget to open the firewall port. My firewall configuration is based on the article To Fix The Docker and UFW Security Flaw Without Disabling Iptables, and the command to open the port is:\nufw route allow proto tcp from any to any port 1935After configuration, set the streaming target address in OBS to rtmp://server_ip/live, and any stream key will work. Once set, you can start streaming.\nNetch If you don\u0026rsquo;t have a server, you can also use Netch to proxy the OBS process for streaming.\nFirst, download Netch version 1.9.2, making sure it\u0026rsquo;s version 1.9.2. Add a process mode, include the OBS folder, and then enable the proxy for that process mode.\nConclusion To stream simultaneously on Twitch and Bilibili, I encountered many unnecessary troubles. I could blame OBS for not having built-in proxy support, or blame tools like Clash Verge for not supporting RTMP. But ultimately, the blame lies with the GFW. Although the final result is satisfactory, the experience of going through all this trouble just for streaming is far from pleasant. Maybe in the future, I should look into modifying the Mihomo kernel to support RTMP protocol, or develop an independent implementation to forward OBS traffic. Whatever the case, streaming smoothly in this land is a long and arduous journey.\nFinally, feel free to follow my Twitch channel: https://www.twitch.tv/powerfullz233\n","date":"2024-09-18T14:16:51+08:00","image":"/2024/09/my-multi-streaming-setup/cover_hu_891493d684eb454d.webp","permalink":"/en/2024/09/my-multi-streaming-setup/","title":"OBS Multi-Streaming Struggles: Unnecessary Troubles"},{"content":"One morning, I woke up to find an email in my inbox, notifying me of a login to my mailbox from South Korea. At first, I dismissed it, assuming it was just an occasional IP mismatch. However, over the following days, new alerts kept arriving, always from different IP addresses. It was clear something was wrong and if I didn’t act soon, there was a real risk my mailbox would end up being used for spam campaigns. I immediately removed the MX, SPF, and DMARC records from my domain and started looking for a more secure solution.\nInitially, my plan was simply to change my email password, but after struggling to find the password-reset option in Tencent\u0026rsquo;s enterprise mailbox (it was truly well hidden!), I decided it was time to migrate my email hosting altogether and spare myself further hassle.\nThis is where this guide began: from the initial step of exposing the necessary firewall ports, to finally optimising deliverability. Here I share my experience setting up a mail server with docker-mailserver, so you can avoid some of the pitfalls I encountered.\nPreparations Before Deploying docker-mailserver Configuring Your Firewall First, you\u0026rsquo;ll need to allow the standard email service ports through your firewall—specifically: 25, 143, 465, 587, and 993. The steps vary depending on your operating system, so consult the relevant documentation or AI tools if needed. For Ubuntu users with ufw, you can run:\nufw allow 25 ufw allow 143 ufw allow 465 ufw allow 587 ufw allow 993In my case, I use a combination of ufw and the setup outlined in To Fix The Docker and UFW Security Flaw Without Disabling Iptables. This means that if I want to expose Docker container ports to the outside world, I have to add some extra configuration.\n# Open required ports for the mail server ufw route allow proto tcp from any to any port 25 ufw route allow proto tcp from any to any port 143 ufw route allow proto tcp from any to any port 465 ufw route allow proto tcp from any to any port 587 ufw route allow proto tcp from any to any port 993Checking if Your Server is Suitable Not every server is suitable for running your own mail server—especially many of the cheaper options. Large email providers commonly blacklist IP addresses that have been associated with spam activity. If your hosting provider happens to allocate you one of these, any emails you send will get blocked, and it can be a painful process to discover and resolve this only after hours of configuration.\nEven if your IP doesn’t appear on public blacklists, some hosting companies block outbound email ports to reduce the risk of abuse.\nTo check that your server meets the necessary requirements for running a mail server, use the following script (This script is in Chinese and also contains ads. If you mind, you can check your server IP on services like Scamalytics instead.):\nbash \u003c(curl -sL IP.Check.Place) If you find your server is unsuitable or cannot run docker-mailserver (due to blocked ports or a tainted IP), it’s best to cut your losses and find a cleaner, less restricted IP address from a different provider.\nSetup Guide Downloading Configuration Files docker-mailserver requires two main configuration files: compose.yaml and mailserver.env. Fetch them as follows:\nmkdir mailserver \u0026\u0026 cd mailserver sudo apt install wget wget -O compose.yaml \"https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/compose.yaml\" wget -O mailserver.env \"https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env\"Tweak the configuration as needed. Here’s a snippet of settings I modified:\nPOSTMASTER_ADDRESS=webmaster@l3zc.com TZ=Asia/Shanghai SSL_TYPE=manual SSL_CERT_PATH=/certs/cert.crt SSL_KEY_PATH=/certs/cert.keyUsing Caddy to Obtain and Maintain TLS Certificates Automatically1 Since I already use Caddy as my reverse proxy/web server, I decided to let it handle all certificate management for the mail server as well. According to the official documentation, all you need to do is define a relevant subdomain in your Caddyfile, and Caddy will automatically obtain and renew its certificate:\nmail.example.com { tls internal { # Note: This would create an internal CA certificate, not a public one. so delete the `internal` mark. key_type rsa2048 } # Optional – may be helpful for troubleshooting respond \"Hello DMS\" }After I updated the DNS records, I found that my situation was rather unique in practice: my server wasn\u0026rsquo;t actually using Caddy\u0026rsquo;s automatic certificate issuance feature, but was instead configured with a wildcard Cloudflare Origin Certificate valid for 15 years. Even if I didn\u0026rsquo;t specify a certificate file for this particular subdomain, once Caddy loaded the wildcard certificate, it assumed there was no need to obtain a new one. This was rather awkward, as with Caddy you can only choose between using the Cloudflare Origin Certificate or running a mail server – not both simultaneously.\nI ended up installing dns.providers.cloudflare and gave up on the Cloudflare Origin Certificate, allowing Caddy to automatically obtain and manage TLS/SSL certificates for each subdomain. Since the process is all automated, it saves me the headache and keeps things tidy:\n{ acme_dns cloudflare {API_KEY} }Finally, map Caddy\u0026rsquo;s certificate storage location into your docker-mailserver container:\nvolumes: - /home/user/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/:/certs/If Caddy runs as the root user, certificates will be stored in /root/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/yourdomain.tld/—adjust as necessary.\nSince we’re mapping the certificate path inside the container as /certs/, the config is as follows:\nSSL_CERT_PATH=/certs/cert.crt SSL_KEY_PATH=/certs/cert.keyFirst Launch When you first start the container, you need to create a new Postmaster account within 120 seconds:\ndocker compose up -d docker exec -it setup email add Deliverability Optimisation DNS Records An A record for your mail server (e.g. mail) pointing to your server\u0026rsquo;s IP address (on Cloudflare, set to \u0026ldquo;DNS only\u0026rdquo;). An MX record for the root domain (@), pointing to mail.yourdomain.tld. A TXT record (SPF) for the root domain (@). You can generate an SPF record here (optional). A TXT record (DMARC) with the name _dmarc; you can generate this using this tool (optional). A TXT record (DKIM) named mail._domainkey—instructions on how to set this up follow below (optional). DKIM, SPF, and DMARC2 So, what are DKIM, SPF, and DMARC? To put it simply:\nDKIM is a digital signature for your emails. SPF is like an “approved sender list,” declaring which mail servers can send emails on your behalf. DMARC instructs receiving servers how to handle emails that fail DKIM or SPF checks.\nFor a more in-depth explanation, see Cloudflare’s introduction.3 To set up DKIM, generate a key pair on your server:\ndocker exec -it setup config dkimYou’ll find the generated files in the mapped directory: ./docker-data/dms/config/opendkim/keys/.\nDownload mail.txt (or copy all its contents into a file locally), then import it via your DNS provider\u0026rsquo;s dashboard to set up DKIM. Remember: you must restart your docker-mailserver container for changes to take effect4.\ndocker compose up -d --force-recreateAs previously mentioned, SPF is your “authorised sender” list. If you only have a single mail server, your DNS TXT record should look something like:\n\"v=spf1 mx ~all\"DMARC can be generated with one of the online tools linked above. Many DNS providers (like Cloudflare) also offer guided wizards to make DNS setup even easier.\nClient Setup This part is straightforward: use mail.yourdomain.tld for both your IMAP and SMTP servers.\nEnable SSL; the standard ports are 993 (IMAP SSL) and 465 (SMTP SSL). Log in with your credentials.\nMake Use of MailTester Congratulations—we’ve now reached the end of this docker-mailserver deployment! Before you start sending real emails, test your configuration with MailTester. A score of 10/10 means your emails have an excellent chance of landing in inboxes—enjoy your setup!\nIf you don’t get a 10/10 first time, don’t worry—carefully check any issues reported and work through them one by one.\nTODO List Add a WebUI Reference: https://docker-mailserver.github.io/docker-mailserver/latest/config/security/ssl/#caddy\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://docker-mailserver.github.io/docker-mailserver/latest/config/best-practices/dkim_dmarc_spf/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nFor a deeper dive: https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSee the official documentation for details.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-09-06T11:57:18+08:00","image":"/2024/09/docker-mailserver-deployment-recap/docker-mailserver_hu_a649605529f354c4.webp","permalink":"/en/2024/09/docker-mailserver-deployment-recap/","title":"Setting Up a docker-mailserver"},{"content":"My Firefish instance was set up in November last year, and the database version it uses is naturally not the latest. Specifically, it\u0026rsquo;s PGroonga 12, an ancient version based on Postgres from five years ago. To avoid future compatibility and security issues, and for better performance, upgrading this database today is a must.\nUpgrading a database running in Docker is quite different from upgrading other Docker containers. It\u0026rsquo;s not just a matter of pulling a new image and calling it a day. Since Postgres often has breaking changes with each major version, the persistent files generated by the old and new versions are not compatible. If you directly pull a new image and run it, the database won\u0026rsquo;t start. You must use certain methods to migrate the data.\nPostgres has an official upgrade tool called pg_upgrade1, but I won\u0026rsquo;t be using it this time because my database has been running for over a year, and it\u0026rsquo;s based on a version from five years ago. I\u0026rsquo;m not sure if this tool would work without issues. For this migration, the general idea is to export the SQL and then import it into the new version.\nDeployment Overview Before Upgrade Here is some configuration information you need to know before the upgrade:\nItem Configuration Instance Deployment Method Docker Compose Database Container Name firefish_db Pre-upgrade Database Container Image groonga/pgroonga:3.1.9-alpine-12-slim Database User example-firefish-user Database Name firefish The database part of the Docker Compose file is as follows:\ndb: restart: unless-stopped image: groonga/pgroonga:3.1.9-alpine-12-slim container_name: firefish_db networks: - calcnet env_file: - .config/docker.env volumes: - ./db:/var/lib/postgresql/data healthcheck: test: pg_isready --user=\"${POSTGRES_USER}\" --dbname=\"${POSTGRES_DB}\" interval: 5s timeout: 5s retries: 5Exporting the Current Database Before upgrading the database, first, take down the Firefish orchestration:\ndocker compose downStart the database container alone and export the current database to SQL:\ndocker compose up -d db docker exec -it firefish_db pg_dumpall -U example-firefish-user \u003e backup.sqlSince Firefish\u0026rsquo;s database is relatively large, the export process may take a long time. For instance, my personal instance took over ten minutes.\nProcessing the Exported File It might be due to changes in the authentication method in the new version of Postgres, but if you directly import the previously exported backup.sql file into the new database, it will change the new database\u0026rsquo;s Authentication Scheme, causing Firefish to fail to authenticate when connecting to the database later. To avoid this issue, you need to process the current backup.sql file, extracting only the firefish database portion instead of importing all the data. 2\n#!/bin/bash [ $# -lt 2 ] \u0026\u0026 { echo \"Usage: $0 \"; exit 1; } sed \"/connect.*$2/,\\$!d\" $1 | sed \"/PostgreSQL database dump complete/,\\$d\"Create a Shell script with the above content and save it as script.sh to process the current backup.sql:\nnvim script.sh # Or any editor you prefer chmod +x script.sh ./script.sh backup.sql firefish \u003e\u003e upgrade.sqlIf everything goes well, a file named upgrade.sql will appear in the current directory. You can open it to check and ensure it was exported correctly.\nImporting Existing Data into the New Database Modify docker-compose.yml:\ndb: restart: unless-stopped image: groonga/pgroonga:3.2.2-alpine-16-slim # Latest container container_name: firefish_db networks: - calcnet env_file: - .config/docker.env volumes: - ./database:/var/lib/postgresql/data # Note this change healthcheck: test: pg_isready --user=\"${POSTGRES_USER}\" --dbname=\"${POSTGRES_DB}\" interval: 5s timeout: 5s retries: 5Notice that the database directory mapping configuration has changed from ./db:/var/lib/postgresql/data to ./database:/var/lib/postgresql/data. This is to give the new database a fresh start while preserving the old persistent data. Even if something goes wrong, you can always revert to the old database.\nPull and start the new container:\ndocker compose pull docker compose up db -dImport upgrade.sql into the new database:\ncat upgrade.sql | docker exec -i firefish_db psql -U example-firefish-user -d firefishDepending on the size of the database, the import process may also take a long time.\nFinishing Up Once the import is complete, start the entire orchestration:\ndocker compose stop db docker compose upIt is recommended not to use the -d parameter for the first startup after the import. Start without any parameters to ensure there are no issues during the startup process, then restart with the -d parameter.\nFinally, log in to the just-started Firefish instance and check for any data loss. If everything is fine, congratulations, you\u0026rsquo;re done! 🎉\nOfficial documentation: https://www.postgresql.org/docs/current/pgupgrade.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference: https://thomasbandt.com/postgres-docker-major-version-upgrade\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-08-30T12:45:12+08:00","image":"/2024/08/upgrading-firefish-database-docker/cover_hu_c0ecfda1e4670195.webp","permalink":"/en/2024/08/upgrading-firefish-database-docker/","title":"Firefish Database Upgrade Recap"},{"content":"In the previous article, I mentioned that I set up a BT offline download server based on Aria2. To deploy the container with Caddy as a reverse proxy, I uninstalled the Trojan that I had lazily deployed using a one-click script before. The reason is simple: the script occupied ports 80 and 443, making it impossible to continue without uninstalling it.\nSo, the offline download server was set up, but the proxy that was occasionally needed had to be taken offline. After some thought, I recalled seeing someone revive a blocked VPS using Cloudflare + V2Ray + WS. Since it\u0026rsquo;s WebSocket, it can be reverse proxied by Cloudflare, so why not use Caddy? Moreover, Caddy has automatic TLS, which is certainly easier to configure than the Nginx + Certbot combination. Once configured successfully, I can run the proxy while keeping port 443 for Caddy.\nInstalling VLESS and Caddy You can install VLESS directly using the v2fly/fhs-install-v2ray one-click script:\n# Install executable and .dat data files bash \u003c(curl -L https://raw.githubusercontent.com/v2fly/fhs-install-v2ray/master/install-release.sh)For installing Caddy, please refer to the official tutorial. For example, if I use Debian 12, I would use the following commands:\nsudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddyConfiguring VLESS Edit /usr/local/etc/v2ray/config.json:\n{ \"log\": { \"access\": \"/var/log/v2ray/access.log\", \"error\": \"/var/log/v2ray/error.log\", \"loglevel\": \"warning\" }, \"inbounds\": [ { \"port\": 1234, // Any port \"listen\": \"127.0.0.1\", \"protocol\": \"vless\", \"settings\": { \"clients\": [ { \"id\": \"super-random-uuid\", // Replace with a randomly generated UUID \"level\": 0, \"email\": \"example@example.com\" } ], \"decryption\": \"none\" }, \"streamSettings\": { \"network\": \"ws\", \"security\": \"none\", \"wsSettings\": { \"path\": \"/\" // Must match the Caddy configuration later } } } ], \"outbounds\": [ { \"protocol\": \"freedom\" } ] }Save and exit after editing.\nConfiguring Caddy Create a Caddyfile in your preferred location, or edit an existing Caddyfile, adding the following content to reverse proxy the port set in the VLESS configuration file:\nyourdomain.com { # Use a Matcher to match all WebSocket requests for the specified path @websockets { path / # Must match the path in the VLESS configuration header Connection Upgrade header Upgrade websocket } reverse_proxy @websockets :1234 }At this point, all the editing work for server configuration files are done.\nStarting the Services Start VLESS and set it to start on boot:\nsystemctl enable v2ray \u0026\u0026 systemctl start v2rayStart Caddy or update the Caddy configuration:\ncaddy start caddy reload # Use to update the configurationAfter completing all the above steps, the Caddy reverse proxy for VLESS is done.\nClient Configuration Client settings need to be done manually:\nThe address should be the domain bound to VLESS. The port should be 443. TLS should be enabled. The transport method should be WebSocket. The UUID should be the one defined in the VLESS configuration file. Most client settings are similar. For Clash configuration files, you can refer to the Mihomo documentation1. Below is my example configuration file:\nproxies: - name: \"A Random Name\" type: vless server: yourdomain.com port: 443 udp: true uuid: super-random-uuid flow: xtls-rprx-vision packet-encoding: xudp tls: true servername: yourdomain.com alpn: - h2 - http/1.1 skip-cert-verify: false network: ws smux: enabled: falseTo generate a Clash configuration file, you can use the tool provided by V2RaySE.\nConclusion Now we have a VLESS server, and Caddy will automatically apply for and maintain the TLS certificates for us, which is very convenient.\nWith just a little effort, I feel the same joy as when I used one-click scripts to set up SSR back in the day. Enjoy the excitement of being able to smoothly open google.com.\nTODO List Basic setup Camouflage After all, Mihomo, inheriting the Clash Meta mantle, is considered the true successor of Clash.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-08-13T16:24:26+08:00","image":"/2024/08/caddy-vless-proxy/cover_hu_1cd20ddda32e23a.webp","permalink":"/en/2024/08/caddy-vless-proxy/","title":"Bypass China's Internet Censorship: Using Caddy to Reverse Proxy VLESS"},{"content":"Ever since I picked up a few cheap VPS instances, I\u0026rsquo;ve had an Azure student VM sitting idle on my hands. Apart from running a Trojan proxy, it hasn\u0026rsquo;t had much other use. Recently, I wanted to download some BT resources, and the state of BT networking in China is well-known, so I decided to use this nearly idle machine for offline BT downloading.\nFirst, a disclaimer: all BT downloading activities carry the risk of copyright complaints, which you must bear yourself.\nInstall Aria2 For software that isn\u0026rsquo;t quite \u0026lsquo;infrastructure\u0026rsquo;, I prefer simply pulling a Docker container. I opted for the modified Docker container P3TERX/Aria2-Pro-Docker1. Deploying the Docker container is straightforward: first edit docker-compose.yml, run docker compose up to start all containers, and finally reverse proxy the exposed ports.\nMy docker-compose.yml is as follows2:\nservices: Aria2-Pro: container_name: aria2-pro image: p3terx/aria2-pro environment: - PUID=65534 - PGID=65534 - UMASK_SET=022 - RPC_SECRET=replace-me # Remember to modify this line - RPC_PORT=6800 - LISTEN_PORT=6888 - DISK_CACHE=64M - IPV6_MODE=false - UPDATE_TRACKERS=true - CUSTOM_TRACKER_URL= - TZ=Asia/Shanghai volumes: - ./aria2-config:/config - ./aria2-downloads:/downloads network_mode: bridge # If you need to use IPv6 networking, you can also use host mode ports: - 6800:6800 - 6888:6888 - 6888:6888/udp restart: unless-stopped # Prevent logs from filling up the hard drive logging: driver: json-file options: max-size: 1m # You can also use other panels AriaNg: container_name: ariang image: p3terx/ariang command: --port 6880 --ipv6 network_mode: bridge ports: - 6880:6880 restart: unless-stopped logging: driver: json-file options: max-size: 1mReverse Proxy with Caddy Previously, I always used Nginx-based solutions for reverse proxying. However, during this period, I\u0026rsquo;ve heard nothing but good things about Caddy, so this time I switched to Caddy. Naturally, I also wanted to try some new things and technologies.\nUsing Caddy is truly very simple, even extremely friendly for newcomers. For example, to commonly reverse proxy a local port, you only need three lines in the Caddyfile.\ndomain.com { reverse_proxy :1234 }Please refer to the official documentation for installing Caddy. For example, my server uses Debian, so I used the following commands to install:\nsudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update sudo apt install caddyAfter installation, remember to open ports 80 and 443 in the firewall and your provider\u0026rsquo;s security group.\nPoint the domain you want to bind to the server at your domain provider, and you can start writing the Caddyfile. We need to reverse proxy three ports for different purposes. My Caddyfile is as follows:\n# Aria-NG Panel download.l3zc.com { reverse_proxy :6880 } # Aria2 API arapi.l3zc.com { reverse_proxy :6800 } # Alist downloaded.l3zc.com { reverse_proxy :5244 }Seeing such a concise configuration file, I can\u0026rsquo;t help but sigh; compared to Nginx, using Caddy is truly a breath of fresh air. After writing the configuration, restart Caddy3, and you can begin to admire the splendid process of Caddy automatically applying for SSL certificates and configuring HTTPS for you. At this point, the reverse proxy part is complete.\nConfigure Aria-NG Aria-NG is just a frontend panel and can communicate with any Aria2 backend. To this end, we need to provide configuration information to Aria-NG. Enter the Aria-NG panel you just reverse proxied, go to AriaNG Settings, and fill in the configuration details.\nAmong them, the RPC Address is the reverse-proxied RPC address you just set up, select HTTPS for the Protocol, POST for the Request Method, and the RPC Secret is the value you replaced at replace-me in docker-compose.yml.\nIf \u0026lsquo;Connected\u0026rsquo; appears next to \u0026lsquo;Aria2 Status\u0026rsquo;, the configuration is correct.\nMap Download Directory with Alist We map the download directory via Alist to view, manage, and download files already on the server more conveniently. Alist is also deployed via Docker. Since Alist itself runs inside a container, it cannot access files outside the container; during deployment, you need to bind Aria2\u0026rsquo;s download directory to the Alist container beforehand.\nMy docker-compose.yml is as follows:\nservices: alist: image: \"xhofe/alist:latest\" container_name: alist volumes: - \"/etc/alist:/opt/alist/data\" - \"/home/azureuser/aria2/aria2-downloads:/aria2-downloads\" # Bind your own directory here as appropriate ports: - \"5244:5244\" environment: - PUID=0 - PGID=0 - UMASK=022 restart: unless-stoppedWhen starting Alist for the first time, it will randomly generate a password for the admin user. Therefore, when starting this orchestration for the first time, it is best not to use the -d parameter, but rather directly use docker compose up, and save the container\u0026rsquo;s startup output carefully. If you accidentally didn\u0026rsquo;t save the admin password, you can use this command to randomly generate a new one:\ndocker exec -it alist ./alist admin randomAfter successfully logging into Alist, you can start mounting the directory.\nSelect \u0026lsquo;Local Storage\u0026rsquo; for the driver. Note the mount path; according to my orchestration, I mounted the /home/azureuser/aria2/aria2-downloads directory outside the container to /aria2-downloads inside the container, so the mount path should be /aria2-downloads.\nCompletion Now all services are deployed and online. Open AriaNG to add download tasks; downloaded files can be viewed, downloaded, and even streamed online via Alist.\nBefore setting up this service, please carefully check your VPS provider\u0026rsquo;s Terms of Service to ensure that BT downloading does not violate them. Furthermore, aside from a few countries/regions like Luxembourg, downloading resources protected by DMCA may lead to complaints; you must bear these risks yourself.\nAnyway, this Azure student VM of mine is essentially free; if it gets reclaimed due to a complaint, I don\u0026rsquo;t really care. Regardless, now I can enjoy the pleasure of finding resources without distraction.\nOf course, you can also try SuperNG6/docker-aria2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nReference the original author\u0026rsquo;s docker-compose.yml\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ncaddy stop and caddy start, refer to Caddy Documentation\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-08-10T15:46:40+08:00","image":"/2024/08/aria2-downloading-server/cover_hu_935547325c8335d7.webp","permalink":"/en/2024/08/aria2-downloading-server/","title":"Setting Up a BT Offline Download Server"},{"content":"Changsha really is a great place, surrounded by mountains on three sides, with the only opening to the north facing Dongting Lake. This makes it easy for cold air to penetrate in winter and difficult for hot air to escape in summer. Combined with the inherent humidity of the south, the felt temperature is even more oppressive, freezing in winter and scorching in summer.\nThe recent heat has been unbearable, so I decided to cool off by playing in the mountain waters.\nDestination The place we\u0026rsquo;re heading to is called Qixian Temple in Luxi Village, located at the border between Changsha and Yueyang, under the jurisdiction of Yueyang, more than 80 kilometers from downtown Changsha, requiring a two-hour drive. It\u0026rsquo;s necessary to allow enough time for the trip, like an entire afternoon.\nHaving lived in Changsha for so long, I\u0026rsquo;ve pretty much visited all the places in the city, but I had never heard of these rural spots until my dad came across them on TikTok. Now that we had our destination, we picked a weekday afternoon, had lunch, and set off!\nDriving from downtown Changsha to Qixian Temple, there are generally two routes: one involves a stretch on the Wushen Expressway, and the other takes purely national and rural roads. Since the distance isn\u0026rsquo;t too far and the expressway section isn\u0026rsquo;t long, using the expressway only saves about 20 minutes one way. It really depends on personal preference. I\u0026rsquo;m on summer break right now, so I have plenty of time and taking the national and rural roads is enough for me.\nAfter nearly two hours on rural roads, where the speedometer mostly swung between 50 and 60 km/h, I find this is the charm of rural roads—compared to the flat and monotonous highways, the rural routes are winding and scenic. With one hand on the steering wheel and the other holding a drink just taken out of the fridge—the fridge being a later addition, a car-mounted fridge powered by the car\u0026rsquo;s cigarette lighter—it was easy and enjoyable. Sipping a cool drink while admiring the countryside and driving effortlessly is delightful. On the highway, when traffic is light, it\u0026rsquo;s okay but a bit dull; when it\u0026rsquo;s heavy, it requires full attention, leaving no room for relaxation.\nCold Water Well As we moved deeper into the mountain area, just before reaching our destination, a temple came into view with a plaque reading \u0026ldquo;Cold Water Well.\u0026rdquo; This well is a legendary fairy spring, revered and popular. I had long heard of this well but didn\u0026rsquo;t expect to encounter it in such a secluded spot on our way to the wilderness. We immediately pulled over, took out the bucket prepared in the back of the car, knelt before the well to pay our respects, and began to draw water. There was a constant flow of people coming to pay respects and draw water, with a queue already forming at the well, but fortunately, the process was quick and there weren\u0026rsquo;t many people that day, so the wait was short.\nInside the temple, an LED screen alternated between displaying a merit list1 and stories of the well\u0026rsquo;s \u0026ldquo;miraculous\u0026rdquo; effects, such as someone diagnosed with a certain type of cancer at a hospital, considered incurable, but who showed significant improvement and eventually recovered after drinking the well water. Whether these stories are true or false, and whether it\u0026rsquo;s really the miraculous effect of the well water or something else, is not for discussion here. This is a sacred well locally, and regardless of the truth, I follow local customs out of respect for the local people and their beliefs.\nThere is a fee to draw water from the well, which isn\u0026rsquo;t very high. Previously, the well water was open to the public and tools were provided for anyone to draw water, but now a fee is required. Even so, during peak times, the queue for buckets can stretch outside the temple, and waiting times can extend to five or six hours.\nQixian Temple Proceeding a bit further and crossing some wilderness, we arrived at our destination, Qixian Temple. Here, there is a series of waterfalls, each with a pool below, filled with cool mountain spring water. However, care must be taken in choosing paths, as the slippery rocks can easily lead to falls. A special note here: if it weren\u0026rsquo;t for my dad slipping and falling into a pool, the consequences could have been unimaginable.\nAfter a two-hour drive, we finally arrived, immediately changed into swimsuits, and went into the water to cool off. This natural spot is also crowded on weekends, with people bringing food and spending a day playing in the water and picnicking. However, the litter left behind by them really spoils the scenery.\nThankfully, the water, although not very clear, was genuinely refreshing. Entering the water was a bit nerve-wracking at first, but after a moment of hesitation and a chill, I finally got in. Then it was all about playing in the water, diving, swimming, washing hair—the heat vanished like smoke. An unexpected pleasure was the presence of many small fish. I didn\u0026rsquo;t realize these little creatures were there until I looked underwater. They scatter at the sight of people, and since I\u0026rsquo;m not a fish, I won\u0026rsquo;t speculate on the reasons for their behavior.\nMy phone supports IP68 water resistance, theoretically handling underwater photography without any issues. Normally, I adhere to the principle of \u0026ldquo;waterproof but not foolproof,\u0026rdquo; and don\u0026rsquo;t actually bring my phone into the water. This time, however, I made an exception and allowed it to capture underwater footage (because I want to change phones). So far, my phone is functioning normally.\nWe also met a couple there; when we arrived, they mentioned they had been soaking in the water for over two hours, so I helped them film a diving video.\nYour browser doesn't support HTML5 video. Here is a link to the video instead. It\u0026rsquo;s quite nice to meet fellow travelers.\nThe water temperature here is relatively low, around 23 degrees Celsius, which isn\u0026rsquo;t suitable for staying in the cold water for too long. After playing for a while, it\u0026rsquo;s important to replenish your energy.\nWrapping Up, Heading Home After soaking in the water for two hours, it was a thoroughly refreshing experience. Once we were done soaking, we packed up and headed home. Thanks to the energy spent and the heat dissipated, I was ravenous when I got home and ate a lot.\nMy feeling over the past few years is that each summer has been hotter than the last. From this perspective, global warming truly does not deceive us. Even Europeans, who can no longer stand the heat, have started installing air conditioning. Such a severe issue should perhaps not be underestimated.\nAfter a few days of the typhoon acting like an \u0026ldquo;air conditioner\u0026rsquo;s outdoor unit,\u0026rdquo; I hope the upcoming typhoon can bring some positive effects and come quickly to help us cool down.\nDonors and their donation amounts.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-07-26T17:14:06+08:00","image":"/2024/07/summer-cooling-off-wild-swimming/cover_hu_b2f1244eb71894a1.webp","permalink":"/en/2024/07/summer-cooling-off-wild-swimming/","title":"Summer Cooling Off: A Water Play Diary"},{"content":"When blogging, I often find myself drafting main titles and subheadings, and perhaps even tacking on a subtitle once I’ve finished. Titles certainly have their merits: they help readers quickly grasp the content and structure of an article, and naturally, they are excellent for SEO.\nThis habit of strictly structuring everything before writing a single word was likely forced upon me during composition lessons in middle school. Accustomed to prompt-based essays, we unconsciously began by drafting a title, using it as an anchor to expand, diverge, and fill in the content, ensuring the piece never \u0026ldquo;went off-topic\u0026rdquo;. By secondary school, we were required to draft frameworks and outlines to ensure our formulaic essays always had something to say and wouldn\u0026rsquo;t run out of steam halfway through. People often describe this kind of exam-focused writing as \u0026ldquo;dancing in chains\u0026rdquo;. I once believed that the moment I left school, I would be able to \u0026ldquo;write whatever I thought\u0026rdquo;. As time passes, that sentiment seems increasingly laughable. Although the chains have long since vanished, the bird that was once in the cage has not found freedom; years of self-censorship have stiffened its wings, making taking flight again incredibly difficult.\nThe fundamental purpose of writing is to express the author\u0026rsquo;s inner world. Shen Congwen insisted on writing \u0026ldquo;the history of my heart and dreams\u0026rdquo; in his prose. It is precisely this carefree, unfettered expression that allows an author to achieve true internal freedom—like the poet Tao Yuanming finding solitude even whilst \u0026ldquo;building a hut within the realm of men\u0026rdquo;. This sort of writing, in its truest sense, is what I desire.\nMy previous method involved drafting a main title and then expanding on the content through various subheadings after the introduction. This created a problem: every time I drafted a new subheading, I would stop to think, agonizing over the wording, sometimes even racking my brains just to come up with a catchy header. When I finally wrote a paragraph, if I wasn\u0026rsquo;t satisfied, I would edit it, sometimes repeatedly. The joy of writing vanished without a trace during this process. Consequently, after half an hour, I would put down my pen only to find I had merely added a single paragraph.\n\u0026ldquo;My hand writes my voice; my hand writes my heart.\u0026rdquo; This was my original intention when setting up this blog, and looking back, it aligns perfectly with what I am writing today. Over the past two years, moving towards the goal of having the blog actually \u0026ldquo;be read\u0026rdquo;, I made certain compromises. While these trade-offs certainly had their positive aspects, I have increasingly realised that even when writing technical articles, true writing is something that ultimately cannot be compromised. This blog has now been live for two years. The more I write, the more I learn. What seems like an ordinary daily life is, in fact, changing me bit by bit. Writing this article, I offer my heartfelt thanks to the person I was two years ago for starting this blog.\nIn the future, my lifestyle-orientated posts will likely have fewer and fewer subheadings, and outlines may become hard to find. Regardless, essentially, I intend to strip away all those elements that have become a cocoon of my own making. \u0026ldquo;Less formatting, more genuine writing\u0026rdquo;—to write the \u0026ldquo;history of my heart and dreams\u0026rdquo;.\n","date":"2024-05-10T00:01:22+08:00","image":"/2024/05/2nd-anniversary/cover_hu_3980137ebd8fec38.webp","permalink":"/en/2024/05/2nd-anniversary/","title":"Less Formatting, More Genuine Writing"},{"content":"The Origin Finally, I have some time to go out and explore.\nAround this time last year, we were also on the road. Back then, the pandemic prevention policies had just shifted, and the state of the tourism industry was almost the same as during the lockdown period. This year, popular tourist destinations are crowded and bustling. Initially, we planned to visit Malaysia since they offered visa-free entry for Chinese citizens. However, due to my family\u0026rsquo;s busy work schedule, we couldn\u0026rsquo;t go. By the time they were free, the airfare had soared to over five thousand yuan. Given the price, we decided to make new plans to travel domestically, tentatively planning to spend the New Year in Xiamen and Quanzhou.\nBefore that, I had to return to my childhood hometown to visit some family members. This part is personal, so I won\u0026rsquo;t elaborate.\nUnlike last year, we didn\u0026rsquo;t make detailed plans for the entire trip. We only decided to transit through a city in Jiangxi and drafted a rough itinerary.\nDeparture Yongzhou: Not in Jiangxi, but a Must-Visit Starting from Changsha, we took the Jingzhu Expressway (G4) and then transferred to the Quannan Expressway (G72). After a minor traffic jam, Yongzhou was in sight.\n7319 Yongzhou is a lazy little city with a slow pace of life. If you want to get something done, just leave 15 minutes early. This city, which once had a GDP of only a few billion yuan, now has a GDP of about 20 billion yuan. This is the place where I grew up.\nThis photo was taken from the rooftop of my house back then. A river of clear water, a sky with light clouds, and a production area full of newly produced cars just behind a wall. At that time, Liebao (Leopaard) was producing Mitsubishi Pajero under license, and being a military-to-civilian enterprise, it had a steady market on both sides. Life was quite good. Across the Xiang River was the central urban area of Yongzhou. From the starting point, you could take bus route 12 to go to school or shop across the river. Back then, Changfeng Liebao was the best car company in Hunan, and the output value of this factory alone once accounted for more than half of Yongzhou\u0026rsquo;s GDP. However, a series of events led this state-owned enterprise to file for bankruptcy in 2021, but that\u0026rsquo;s another story.\nUnfortunately, such scenery has disappeared forever. This time when I returned, I saw that the riverside parking lot had been replaced by a thirty-story high-rise. Even the small community where I used to live is rumored to be demolished or converted into \u0026ldquo;talent housing\u0026rdquo; or \u0026ldquo;welfare housing.\u0026rdquo; So many things have quietly changed during my absence.\nBinjiang Park Binjiang Park, also known as Zhongshan Park in Lengshuitan, was a place where I used to play as a child. I took some photos during an evening walk, though not intentionally. Now, I regret not taking more photos during the day. After all, I hadn\u0026rsquo;t visited Binjiang Park for over ten years. The trees in the park, which were saplings back then, have now formed a \u0026ldquo;tunnel\u0026rdquo; of shade, and the young trees have grown into towering ones.\nThere are also some 24-hour self-service libraries along the river, with a good environment and beautiful scenery. However, the books inside are not so satisfactory. I randomly picked a book from the only bookshelf, and to my amusement, the printing was chaotic—cost control seemed a bit too extreme.\nLingling Ancient City \u0026ldquo;I once recognized Lingling County in a painting, but today I find the painting inferior.\u0026rdquo;\n—— Ouyang Xiu\nOuyang Xiu gave a very high evaluation of Lingling County. In terms of tourism, Yongzhou\u0026rsquo;s resources are actually quite good, but they haven\u0026rsquo;t been well developed. Additionally, it\u0026rsquo;s overshadowed by nearby Guilin, a heavyweight tourist destination, which leads to Yongzhou being relatively unknown today.\nLingling Ancient City has been developed in recent years and currently doesn\u0026rsquo;t attract many tourists from outside the area. The lack of tourists results in unique shops struggling to survive, which in turn makes it less attractive to tourists—a vicious cycle. Nowadays, Lingling Ancient City serves more as a commercial gathering place for locals (it can\u0026rsquo;t even be called a center), with few shops catering to tourists except for some costume rentals. However, the old streets are genuinely old, making it a good choice for photography.\nI must complain about Yongzhou\u0026rsquo;s no-fly zone—a net clearance protection zone around Lingling Airport covers the entire urban area. As long as you\u0026rsquo;re in the city, you must apply for permission to fly a drone, even though Lingling Airport has very few flights.\nLiuzhou Temple is worth a visit; it\u0026rsquo;s almost like a small museum.\nEven during the Cultural Revolution, this relic was protected by the Revolutionary Committee, so it\u0026rsquo;s not just any ordinary relic. At that time, Yongzhou was still known as Lingling District and didn\u0026rsquo;t become Yongzhou City until the 1980s. Seeing the heritage protection unit and the Revolutionary Committee\u0026rsquo;s signatures on the same stone tablet always gives a sense of incongruity.\nLingling Martial Temple Yongzhou Martial Temple is the largest and most regionally distinctive martial temple south of the Yangtze River. It enshrines Guan Yu, and this ancient building, built during the Hongwu era and last renovated during the Guangxu era, is the only precious heritage that survived the Cultural Revolution. \u0026ldquo;Among them, four blue stone dragon and phoenix pillars, carved with male and female coiled dragons, with large dragon heads holding pearls in their mouths, appear ready to soar. Two female dragons hold small dragons, which are lively and vivid, exquisitely crafted,\u0026rdquo; are rare ancient carving art masterpieces. 1\nIt\u0026rsquo;s hard to imagine the exquisite craftsmanship of the carved beams and painted rafters without seeing it in person.\n\u0026ldquo;Not Developing is the Greatest Development\u0026rdquo; \u0026ldquo;A thousand mountains with no birds in flight, ten thousand paths with no human traces.\nA lone boat, an old man in a straw cloak, fishing alone in the cold river snow.\u0026rdquo;\n—— Liu Zongyuan, \u0026ldquo;River Snow\u0026rdquo;\nWhen Liu Zongyuan was exiled to Yongzhou, he wrote many wonderful texts for Yongzhou. Besides \u0026ldquo;River Snow,\u0026rdquo; his \u0026ldquo;Eight Records of Yongzhou\u0026rdquo; include \u0026ldquo;Record of the Little Stone Pool\u0026rdquo; and \u0026ldquo;The Snake Catcher,\u0026rdquo; which were selected as classics in textbooks. Liu Zongyuan\u0026rsquo;s descriptions of water, wood, fish, and stone are vivid and detailed. As he said in his \u0026ldquo;Preface to the Poems of Yuxi,\u0026rdquo; \u0026ldquo;clear and beautiful, ringing like metal and stone.\u0026rdquo; Liu Zongyuan found his spiritual solace in the mountains and waters of Yongzhou, and his writings, if well-managed, could completely become a powerful free tourism advertisement like \u0026ldquo;Guilin\u0026rsquo;s landscape is the best under heaven.\u0026rdquo;\nWhenever I talk about Yongzhou, my favorite phrase is: \u0026ldquo;If you want industry, the industry is not well-developed; if you want tourism, tourism is not well-developed.\u0026rdquo; Yongzhou is now in a very awkward position, with beautiful mountains and rivers, but next to it is Guilin, a world-class tourist destination. The city\u0026rsquo;s once pillar industrial enterprise has now fallen into bankruptcy. What does Yongzhou still have to offer now? I really can\u0026rsquo;t think of anything.\nLocals often say: \u0026ldquo;Not developing is the greatest development for Yongzhou,\u0026rdquo; meaning preserving the beautiful mountains and rivers, making the city exquisite, clean, and full of \u0026ldquo;leisurely charm,\u0026rdquo; is the greatest development for Yongzhou.\nNanchang: An \u0026ldquo;Important\u0026rdquo; Central City in the Middle Reaches of the Yangtze River The distances between Wuhan, Changsha, and Nanchang are about the same, forming a tripartite structure of the middle Yangtze River city cluster. I usually stay in Changsha and have visited Wuhan many times, but I\u0026rsquo;ve never been to Nanchang. This time, I finally unlocked all the cities in the middle Yangtze River city cluster.\nThere are many online references to the \u0026ldquo;Ring Jiangxi,\u0026rdquo; like the \u0026ldquo;Ring Jiangxi Economic Circle\u0026rdquo; and \u0026ldquo;Ring Jiangxi 5G Demonstration Belt.\u0026rdquo; Modern Jiangxi\u0026rsquo;s economy indeed lags behind its neighboring provinces for many reasons, which are explained in detail in various videos and articles online 2, so I won\u0026rsquo;t elaborate here. Nanchang gave me the impression of a \u0026ldquo;Ring Jiangxi\u0026rdquo; city—it feels like Changsha ten years ago.\nSpicier than Hunan Hunan and Jiangxi are the two provinces in China that can tolerate the most spicy food. But if you ask who is the top, Jiangxi would claim second place, and Hunan would not dare to claim first. Yes, Jiangxi is indeed spicier than Hunan.\nIn fact, Hunan people are essentially Jiangxi people. During the Yuan Dynasty, there was a saying, \u0026ldquo;Jiangxi fills Huguang, Huguang fills Sichuan\u0026rdquo; 3, leading to many Hunan people\u0026rsquo;s ancestral homes being in Jiangxi (including mine). In terms of spiciness, Jiangxi\u0026rsquo;s \u0026ldquo;mildly spicy\u0026rdquo; is genuinely spicy. Subjectively, Jiangxi\u0026rsquo;s mildly spicy is close to Hunan\u0026rsquo;s moderately spicy.\nJiangxi Provincial Museum As a window into a province\u0026rsquo;s ancient culture, the provincial museum is naturally worth a visit when in the capital.\nThe Jiangxi Provincial Museum gave me the overall impression of being large and grand, but with relatively few exhibits. It\u0026rsquo;s understandable, as Jiangxi doesn\u0026rsquo;t have world-class heritage like the Lady of Dai from the Mawangdui Han Tombs in Hunan, nor was it an economic, political, and cultural center like ancient Chu in Hubei. Having seen the museums of Hunan and Hubei, the Jiangxi museum might feel underwhelming.\nThe exhibits in the Jiangxi Provincial Museum include the usual ancient bronzes and exquisite artifacts of the ancestors, as well as local specialties. Jiangxi\u0026rsquo;s Jinggangshan, as the central Soviet area, left many traces of the activities of the Soviet regime. Additionally, as an important center of pottery production throughout history, Jiangxi\u0026rsquo;s museum showcases many ancient and modern ceramics, all very distinctive.\nTengwang Pavilion Your browser doesn't support HTML5 video. Here is a link to the video instead. The wealth of nature, the talent of people, the city shrouded in mist, the stars of talent shining. The \u0026ldquo;Preface to Tengwang Pavilion\u0026rdquo; epitomizes the ancient glory of Jiangxi. Half of Jiangxi\u0026rsquo;s ancient prosperity is reflected in Tengwang Pavilion.\nNot the Most \u0026ldquo;Jiangxi\u0026rdquo; City When it comes to the most \u0026ldquo;Jiangxi-flavored\u0026rdquo; city, the first one that comes to mind is Ganzhou, not Nanchang. Nanchang\u0026rsquo;s presence is really too low. The original terminal of the Beijing-Kowloon Railway was planned to be Jiujiang in Jiangxi, and the most representative ancient city in Jiangxi is undoubtedly Ganzhou. Even Jiangxi\u0026rsquo;s own development strategy focused on developing Jiujiang and Ganzhou until recently. In today\u0026rsquo;s \u0026ldquo;thousand cities with one face,\u0026rdquo; Nanchang is really \u0026ldquo;too un-Jiangxi.\u0026rdquo; It is ultimately a provincial capital with no presence.\nYichun: Hot Springs How did I know about Yichun? The answer is: from advertisements on the highway. The hot springs here are called \u0026ldquo;selenium-rich hot springs,\u0026rdquo; some say they are the only ones in the world, some say they are one of two in the world. In any case, it\u0026rsquo;s about soaking in hot springs, who cares if it\u0026rsquo;s the only one in the world. The hot springs are said to maintain a temperature of around 70°C year-round, with 20,000 tons of hot spring water gushing out daily, rich in selenium and low in sulfur, making it an excellent destination for hot spring therapy and vacation.\nThe hotel\u0026rsquo;s bathwater is also hot spring water, with the drawback of fluctuating temperatures. Although it\u0026rsquo;s uncertain if it\u0026rsquo;s truly hot spring water and how much heated water is mixed in, having a bathtub in the hotel makes it almost like having a private hot spring. However, the bathtub\u0026rsquo;s water flow was disappointingly slow, and since the room came with hot spring tickets, it was better to soak in the real hot springs. The so-called infinity pool on the mountain top was also hot spring water but turned into a bounded pool due to insufficient water levels, which was a bit disappointing. The air here is very fresh, supposedly high in \u0026ldquo;negative ions,\u0026rdquo; though the reliability of this claim is uncertain.\nThe ancient well in Wentang Town, which maintains a temperature of 70°C year-round, is open to the public for free, creating an interesting culture—this temperature is perfect for\u0026hellip; soaking feet. The locals, regardless of age, come to the ancient well every day with buckets, fill them with the perfectly warm selenium-rich hot spring water, and sit by the well or in nearby pavilions to soak their feet and chat. If the water gets cold, they dump it out and refill the bucket, soaking until satisfied. I would never believe anyone here has athlete\u0026rsquo;s foot.\nThe hotel prices during the Spring Festival didn\u0026rsquo;t increase much, possibly because they were already relatively high. Knowing this, I should have stayed a few more days.\nWhat Happened to Quanzhou and Xiamen? As for why we didn\u0026rsquo;t go to Quanzhou and Xiamen\u0026hellip;\nReferenced from Baidu Encyclopedia\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nYou can watch this video for more details, https://www.bilibili.com/video/BV1mw411C7RL\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nFor more on this history, you can refer to Wikipedia and Hunan Daily\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-02-18T14:20:47+08:00","image":"/2024/02/2024-new-year-trip-archive/cover_hu_13f375941587768a.webp","permalink":"/en/2024/02/2024-new-year-trip-archive/","title":"An Unplanned 'Incomplete Jiangxi Trip'"},{"content":"As I write the words “Year in Review”, my mood is roughly one of “alone in desolation, tears falling unbidden”. A year-end summary that was mostly finished long ago very nearly ended up abandoned because of a mycoplasma infection and exam pressure, so being able to finish this review at all has been no easy feat.\n2023 was a difficult year: difficult to endure the wave of infections at the start of the year, difficult to return to campus and sit the postponed final exams from the previous term, difficult to complete course projects, difficult to adjust my mental and emotional state, difficult to watch the world continue to deteriorate\u0026hellip; I struggled through my own life, and what kept me going through the year was probably nothing more than the basic human instinct to survive.\nI don’t know how your year has been, but if you’re still alive, that’s enough.\nStudies Life played a very cruel joke on me.\nEver since I entered university, my studies have never gone especially smoothly, and this year was no exception.\nAfter a winter break longer than the summer holiday, I discovered that the final exams for five major modules, postponed because of the chaos caused by the abrupt shift in COVID policy last term, were now bearing down on us all at once. Perhaps thanks to some kind of self-protection mechanism, my mind had not yet completely fallen apart. My roommate, by contrast, was already on the verge of a breakdown, which says enough about how crushing that explosion of exam scheduling pressure was. But because of that same protection mechanism, I could only revise under immense mental strain and at very low efficiency, and the final outcome was predictably grim. In the second semester of my second year, I adjusted my state, rested, and recovered my energy. In plain terms: I stopped going to lectures. But that did not mean I stopped studying; at the very least, my exam results were much better than in the previous disastrous term. Even so, something unexpected happened: I failed Embedded Systems, which was frankly absurd. After asking around, I found out it was because I missed the 8 a.m. roll call and lost marks for attendance. Then I got 80 in the resit, while another resit student I had helped got 100 by memorising stock answers by rote. Highly ironic.\nIn the first semester of my third year, after a summer holiday, I became deeply convinced that I needed to study “hard”. Looking back now, this was not a good thing. Because of that idea, and because my dorm was too noisy with people gaming, I ended up going to the library almost every night. I seemed to push myself too “hard”, to the point that an old psychological wound flared up again; saying the pain was seared into me is no exaggeration. For several days I cried uncontrollably in the library, then went back to the dorm and forced a smile. That inner torment, combined with excessive “hard work”, drained me completely, and I simply could not keep studying. I now finally understand that, given my mental state, studying in the library was the wrong decision.\nLooking back, the way I handled it was almost exactly like my immature self in the second semester of first year, when I tried to restore my old sixth-form study habits. Running off to the library every evening was genuinely painful and terribly inefficient. Going in circles only to end up back at the starting point is not a good feeling, and yet at the time it really did seem like the best choice I could make. Still, I should not have repeated the same mistake.\nI suppose I’ve realised that my mind simply does not allow me to study too intensely. Once I go past a certain threshold, some protective mechanism kicks in, numbs my nerves, and refuses to let me push any further. At present I do not know whether I can break through that limit, but if it reminds me to rest, perhaps that is not entirely a bad thing. Still, when I want to give studying everything I have, and this limit appears instead, is that not a kind of sadness in itself? I feel like the unluckiest university student in the world: right before finals, I came down with mycoplasma pneumonia, and yet the utterly deranged timetable still forced me to revise as if I had only missed two days of class. In the end, I worked hard and got nothing. Since starting university, I’ve failed quite a few modules by now. I have never felt so deeply the despair of “no matter how hard I try, my grades just won’t improve”. Perhaps this is punishment for some foolish wish I once made. And when I think about all the suffering I have endured over the years, in the final analysis, perhaps this is simply the punishment for being lower-class, for being poor. Why should I have to pay for that? If Heaven exists, can it answer me?\nThe power grid planning course project I did this term deserves a section of its own. At first we tried to buy one, but in the end it amounted to nothing more than paying tuition fees in another form, which was rather ironic. I strongly suspect one of the people involved padded it out with ChatGPT. So in the end, we completed in one week what would normally take others a month, and in order to submit it before the deadline, everyone in our dorm stayed up until half past five doing the final copying by hand. More than forty pages of coursework report, all handwritten — quite an experience.\nHealth Compared with last year, my health did not change much. At most, I recovered from COVID at the beginning of the year, then got hit with a mycoplasma infection at the end. And I was also diagnosed at a neurological hospital with moderate depression and OCD.\nThe depression still flares up from time to time, while I personally feel the OCD does not affect me too badly. When the depression hits, apart from drinking a bit more and resting more, I really have no other way of dealing with it. The mental health crisis is still ongoing, and this year the return of an old inner wound drained all my energy for a while.\nFalling into some inexplicable state:\nCan’t enjoy entertainment, don’t even want to listen to music\nCan’t study, while exams keep coming one after another\nCan’t rest properly, can’t even sleep well\nIn other words: I can’t enjoy myself, can’t study, and can’t even rest\nThis bout of utter exhaustion finally made me realise just how precious human energy is. I know perfectly well that, in the end, my behaviour amounts to giving up on myself. I need social support, something I have almost never had. COVID and mycoplasma also triggered my anxiety and depression. The extent of how difficult life feels for me now is hard to put into words, but there is nothing I can do except grit my teeth and endure it. I started retching as well, probably because my anxiety symptoms worsened; at any rate, the psychiatrist recommended medication.\nThroughout the year I kept trying to treat myself and seek support from the outside world. Strange though it is, life has already tormented me so much, and yet the instinct for survival keeps dragging me onwards. Perhaps that is a blessing for humanity as a whole, but for each individual it also becomes a sorrow.\nBooks I Read Books do not change, but people do.\nReading is really a way of reading oneself. In 2023, I read some new books, and some old ones I love very much. Every time I reread those old books, it feels as though I am reading them for the first time.\nWhenever I finish a book, I upload the e-book to books.l3zc.com. There may be exceptions, of course, but after sorting through them, the literary works I read this year included:\nBig Breasts and Wide Hips (Mo Yan) Lynching (Liang Xiaosheng) Random Sketches of a Journey to Xiang (Shen Congwen) Autumn in the Old Capital (Collected Works) (Yu Dafu) If Cats Disappeared from the World (Genki Kawamura) A Lost Paradise (Jun\u0026rsquo;ichi Watanabe) Summer, Fireworks, and My Corpse (Otsuichi) The Eastern Capital: A Dream of Splendour (Meng Yuanlao) Ten Brocade Satin (Fang Cunguang) The Miracles of the Namiya General Store (Keigo Higashino) After School (Keigo Higashino) I Am a Cat (Natsume Sōseki) The Ferryman (whoever wrote it) Besides these, there were also some web novels and light novels, but I do not intend to list them here.\nIt is easy to see that of the twelve literary works I can remember from this year, the vast majority came from East Asia. As for The Ferryman, the only Western title among them, I thought after reading it that it was very mediocre — even downright bad. In “Diction and Sentence-Making”, I mentioned that I greatly admire the command of language possessed by Shen Congwen, Yu Dafu, and even Fang Cunguang, the author of the erotic wuxia novel Ten Brocade Satin (his command of Chinese really is excellent). As for translated works, if any one of the original author, the translator, or the publisher falls short, the reading experience suffers badly. But within the East Asian cultural sphere, there is a high degree of cultural similarity, so translations tend to read more naturally and feel more familiar.\nEven so, in terms of the kind of stylistic mastery I want, among living translators I still have not seen anyone like Fu Lei, who could so thoroughly fuse two languages while preserving a style of his own1. These translated works still cannot quite compare with native literature, which is why “original” domestic writing still made up nearly half of what I read.\nI like Republican-era writing; I like the concision of the transitional period between classical and vernacular Chinese, and that plain, unadorned, almost primal emotional force. People say China has produced masters in only two eras: the Warring States period and the Republican era. That is no exaggeration. The fact that people could write in such an easy, unpretentious tone proves at least two things: the government was not censoring them, and their income was enough to live comfortably. Since 1949, those two conditions have never been met at the same time. What is laughable is that the current authorities still promote the line about “old China” being “poor and weak”. It is shameless in the extreme.\nSomething I wrote after finishing Autumn in the Old Capital and Random Sketches of a Journey to Xiang\n(To be continued)\nPlaces I Went At the beginning of the year, I went on a long journey, more or less following the Shanghai–Kunming Expressway from Changsha to Lijiang, passing through many cities along the way. On that trip I hiked Tiger Leaping Gorge and spent quite a long time in Lijiang Old Town, which was a genuine chance to relax both body and mind — apart from the ten-hour traffic jam on the way back, of course.\nIn August I went to Wuhan, mainly to do a bit of anime/game pilgrimage for some Chinese-made visual novels.\nRather embarrassingly, as a student who is not exactly well-off, I simply cannot afford frequent long-distance travel. I would like to see more of the world too, but I have no money, my passport is not especially useful, so I can only give up and go to bed.\nMusic I Listened To Music is an important part of my life. Since none of the platforms I use produces an annual report worth looking at2, I decided to make my own year-end playlist summary.\nThe singers who really made me sit up this year were mainly Camellia and TAYORI (which is to say, Isui). The former is a high-BPM monster, the latter more in the J-pop vein. Stardust3, after her upgrade, genuinely amazed me, so naturally I looped those songs many times, and they left their mark on my year-end playlist.\nThere is a view that music is anti-social. I rather agree. When friends get together, do they usually sit and listen to music together? Clearly not. And even in the rare cases where they do, what gets played is mostly mainstream music that everyone can accept.\nFor pop music to become pop music, it always has to make certain compromises. Compared with so-called “hidden gems”, it inevitably lacks a certain uniqueness.\nBehind every song on this playlist there is some story, some experience. They may have been my companion while walking around campus in low spirits, background music adding flavour to a game, or a lazy melody helping me pass the time on the commute\u0026hellip; Here I find myself unexpectedly at a loss for words. But when I see them, I can still remember the surprise and emotion of hearing them for the first time. To someone who did not live through those moments, though, they might simply sound ordinary.\nI do not listen much to current chart music any more. This year, among the music I heard, fewer songs gave me that feeling of surprise. Compared with my manually compiled playlists for 2021 and 2022, choosing tracks for this year felt especially difficult. Is it because the music I heard really has been getting worse? Or because this year brought more pressure than before, leaving too many bad memories attached to it? I think it is probably both.\nGames I Played Let’s start with my Steam Year in Review:\nMost of my gaming activity takes place on Steam, so I think this report is quite representative.\nIn 2023, most of my gaming time went into games related to “cars”: Forza Horizon 4 and 5, and Euro Truck Simulator 2, which together kept me company for 200 hours of playtime. There is a saying that poor people play with cars and rich people play with watches. Well then, as a poor student who cannot even afford to “play with cars”, I suppose all I can do is mess about with games.\nAs for why I do not play some games on Steam — well, the reasons are complicated. Among those, the one I played most was Stellaris, which I estimate I played for at least 150 hours. Second would be Minecraft, but that game is far too boring without other people, so in reality I probably only played around 30 hours.\nIt is also worth mentioning the visual novels/Galgames I read this year: roughly 20 in total. There were “classic titles” like Nekopara; Chinese-made Galgames such as Tricolour Lovestory and Love Stories; and some more niche works like The Expression Amrilato and Natsu no Kusari. Some of these were R18 titles, though most were simply visual novels. A visual novel is really a more advanced form of the novel; a good one can completely surpass ordinary novels in both literary quality and storytelling.\nAfter making my way through that whole batch of visual novels and Galgames this year, I was genuinely surprised by the level Chinese-made Galgames are capable of reaching.\nChinese-made Galgames win me over through their sense of familiarity — the familiarity of the subject matter, the familiarity of the living environment — something other Galgames simply cannot provide. As I mentioned earlier, I am glad to see that, even under immense pressure, Chinese Galgames still hold on to their ambitions and bring us excellent works. Strange though it is, even in such a poor environment, we still manage to achieve things in some areas. People are always eager to praise those achievements, taking suffering as somehow justified, yet no one ever asks what created such a poor environment in the first place.\nAs I said earlier, outstanding works can surpass ordinary novels in literary merit and storytelling. Some of the titles I played even engaged with social issues, and some could even speak up for minority groups. In the creative environment of mainland China, that is extraordinarily rare and precious.\nI did not touch Genshin Impact at all this year. Now I’m back to do some main story quests and see what absurd things the planners have been up to.\nSocial Life My social life this year felt awful.\nThis term we changed dorms. One of my original roommates suspended his studies, and a new roommate moved in who was unbearably noisy. That was also an indirect reason I started going to the library — I needed somewhere quiet to think about my own problems.\nI may need a few friends, or a cat.\nThere really is hardly anyone to play games with me, so naturally nobody plays Minecraft with me. There is no one to go virtual driving with either, so I can only play by myself. And I do not play FPS games in the first place.\nEvery now and then a few of us from the dorm go out to play badminton, and those times do feel rather nice.\nActually, I do rather miss the days when everyone in the dorm played Genshin Impact together. We were all quite happy then. Just as I wrote in my July summary: after years of loneliness, I think I have started to long for in-person social life. I moved house many times growing up, so I never had childhood friends I stayed close to, nor anyone living nearby whom I knew well enough to casually visit. All I could do was watch the days pass by, and watch those who went to see their friends coming and going happily. Then it suddenly struck me: having friends is a genuinely happy thing. By that logic, having no friends is a profound sadness.\nHopes for 2024 This is obviously not a perfect year-end review. I have tried to write it, but there are always more thoughts waiting to be poured out. And the words I put down never quite match what I really want to express. At first I thought it was simply because I had not mastered Chinese well enough, but now I think I understand: my urge to express myself has been suppressed for too many years. To recover it will require slow practice in the end. It is a kind of cultivation, and I hope I can one day grasp its true meaning. There is no such thing as a perfectly complete year-end review. I only hope that the person writing this one can find some growth and happiness of his own.\nThere is no denying that 2023 was a difficult year. In circumstances that keep getting worse, let alone trying to pursue one’s true self. I often tell myself to face “the bleak reality” head-on, but this reality is perhaps a little too bleak.\nI hope 2024 will be happier, and more importantly, that my efforts will be rewarded. That is a reward I ought to be able to enjoy. A whole year of sadness has drained me of energy, and I do not have much more to say here. I can only hope that this year will be better.\nI recommend taking a look at Fu Lei’s translation of Eugénie Grandet\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nThis is because I often switch between apps due to copyright restrictions and the like, and some songs can only be listened to by watching the MV on YouTube.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nA virtual singer. https://space.bilibili.com/15817819\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024-01-17T17:09:20+08:00","image":"/2024/01/2023-end-of-the-year-summary-public/20240117173426_hu_28cd58b804814e66.webp","permalink":"/en/2024/01/2023-end-of-the-year-summary-public/","title":"2023 Year in Review"},{"content":"I recently came across a blog post discussing issues surrounding top-level domains (TLDs), and found the topic quite intriguing.\nTLDs are overseen by their respective parent organisations and countries. In democratic nations, they are regulated by the rule of law; in authoritarian regimes, they can be subject to the whims of government. For example, no one would consider establishing Freedom House on a .ae domain (which is managed by Saudi Arabia).\nSo, does this mean all TLDs from democratic countries are equally trustworthy?\nThe Electronic Frontier Foundation (EFF)1 published a report pointing out that domains like .com and .net are managed by ICANN and, by extension, fall under the jurisdiction of the United States. Under these circumstances, comparing them becomes somewhat redundant. As for domains run by private organisations or commercial entities, the situation is even less favourable: such domains can be revoked arbitrarily without due process. For illustration, consider the notorious cases involving Freenom, where domains have been reclaimed without warning or justification. Thus, the fundamental question narrows to: which country offers the strongest legal protections for privacy and freedom of expression?\nThere are four country-code TLDs (ccTLDs)2 globally that are governed solely by local courts:\n.at for Austria .de for Germany .ru for Russia .is for Iceland And then there is one exceptional TLD that stands beyond any national jurisdiction: .onion.\nFor these four, the only way a domain can be taken down is via a direct court order, immune to external or bureaucratic interference. Of the four, the first two are EU states. As for Russia, its judiciary\u0026rsquo;s independence is, at best, questionable.\nEU policy itself can be quite aggressive in the digital sphere. This makes EU domains less ideal than they might initially appear. Given the European Union\u0026rsquo;s recent brush with proposals that threatened to ban end-to-end encryption (see here), trust in EU structures is becoming increasingly tenuous.\nWe are therefore left with just two meaningful choices: .is (Iceland) and .onion. But if your aim is for your website to be accessible to the widest possible audience, Iceland’s .is is the only practical option.\nAll things considered, Iceland’s .is stands out as the best top-level domain in the world. However, if maximum anonymity and autonomy are your priorities, .onion remains unsurpassed.\nChoosing a domain name can easily become a geopolitical consideration. While most people may not think twice about their choice of TLD, it really does matter whose hands ultimately control your website’s fate. At present, apart from .onion, there are no truly decentralised, regulation-free domains. Still, this is not necessarily a disadvantage: Iceland’s .is domain demonstrates that when the legal system serves the public interest and the judiciary is transparent and fair, your domain can be afforded the highest possible protection.\nElectronic Frontier Foundation.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCountry Code Top-Level Domain.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2023-10-21T17:15:52+08:00","image":"/2023/10/best-tld/20231021143702_hu_1209cc434fda6c9d.webp","permalink":"/en/2023/10/best-tld/","title":"The Best Top-Level Domain"},{"content":"Due to Elon Musk\u0026rsquo;s recent controversial actions, Twitter users are once again fleeing the platform in droves, a phenomenon I like to call the \u0026ldquo;Twitter Exodus.\u0026rdquo; Seeing this, Mark Zuckerberg couldn\u0026rsquo;t sit still and rushed to bring forward the release date of his competing product. We\u0026rsquo;ll get to see it on July 6th at 10 AM.\nMeta\u0026rsquo;s competing product is called \u0026ldquo;Threads,\u0026rdquo; and at first glance, it appears to be a text-based version of Instagram, inheriting all followers and followings from Instagram. \u0026ldquo;Just another mediocre product, made to look good only because of the competition\u0026hellip;\u0026quot;—that was my initial assessment after a quick glance at its description.\nBut Threads is not that simple.\nThe new standalone app will be based on Instagram and integrate with ActivityPub, the decentralized social media protocol. That will theoretically allow users of the new app to take their accounts and followers with them to other apps that support ActivityPub, including Mastodon.\nAccording to reports, Threads will eventually integrate with the Fediverse via ActivityPub. This news has led many (ActivityPub) instance administrators to express dissatisfaction, with some outright stating that they will block Threads on their instances or initiating votes to decide whether to block it.\nWhat is the Fediverse? The Fediverse is a combination of \u0026ldquo;Federation\u0026rdquo; and \u0026ldquo;Universe,\u0026rdquo; and its Chinese translation is \u0026ldquo;联邦宇宙\u0026rdquo; (Federal Universe). As the name suggests, the \u0026ldquo;Federal Universe\u0026rdquo; is composed of multiple relatively independent instances. The way these instances communicate is similar to email—instances joining or leaving the network have no impact on the rest of the instances or the network as a whole. You can still communicate with everyone else on the network. Given this characteristic, it might be more accurate to call it a \u0026ldquo;Confederation\u0026rdquo; rather than a \u0026ldquo;Federation.\u0026rdquo;\nBy registering an account on a Fediverse instance, such as a Mastodon account, you can theoretically communicate with any account on any other instance within the Fediverse (including non-Mastodon instances like GNU Social, Friendica, Hubzilla, Diaspora, etc.).\nWhy the Collective Resistance to Threads? Masto zealots: We’re open, federate with us!\nInstagram: Great, we’re building a new thing to join you.\nMasto zealots: Not that kind of open!\nThe primary reason for the collective resistance to Threads is the fear that Threads will \u0026ldquo;take over\u0026rdquo;: first, leveraging the resource advantages of a large corporation to provide a better early experience than other instances, then monopolizing content creators before severing ties with ActivityPub. Threads already has Instagram\u0026rsquo;s user base to build on, making this scenario quite plausible.\nEven if the above scenario doesn\u0026rsquo;t occur, the following issues touch on the very nature of the Fediverse:\nAdmins who are planning on federating with Threads, what is your plan when Libs of Tik Tok sends thousands of far right users to harrass one of your users?\n——@siege@octodon.social\n@StarKiller Hello! Currently, Threads and the Fediverse are not yet connected, and the Chinese Mastodon instance will not immediately block Threads\u0026rsquo; domain. However, once federation begins, if Threads is found to have an unacceptable impact on user experience (e.g., inability to control offensive speech and abuse, serving ads to external sites, not honoring remote post deletion requests, significantly increasing server load, etc.), we will consider immediately hiding or banning Threads at the instance level.\nFor concerns about Mastodon user data flowing to Threads, please refer to point 2 in the following post. https://m.cmx.im/@strawberry/109437505\n——@strawberry@m.cmx.im\nOne of the design principles of the Fediverse is to grant everyone equal social power. To achieve this, some features were deliberately weakened during the design phase, even at the cost of user experience. Now, with Meta entering the Fediverse, bringing in a large number of celebrities and social media influencers, Meta could very well prioritize its commercial interests, disrupt this equality, and enhance the Fediverse\u0026rsquo;s missing features. With this advantage, Meta could provide \u0026ldquo;far-right users\u0026rdquo; with the means to attack others on the Fediverse at will.\nFor instance administrators, the concerns raised by the administrator of Strawberry County are almost entirely clear. Like any malicious instance, the concerns include serving ads to external sites, not honoring remote post deletion requests, and significantly increasing server load. After all, Meta is a commercial company, and if these actions maximize their profits, they are entirely possible.\nFinally, there\u0026rsquo;s the issue of privacy and data flowing to Meta. Meta doesn\u0026rsquo;t have a great reputation when it comes to privacy. In fact, Meta\u0026rsquo;s current predicament is largely due to privacy issues. As for how bad the privacy situation is, these two images provide a stark contrast:\nDuring the 2018 U.S. presidential election, Meta, then known as Facebook, was involved in the Facebook-Cambridge Analytica data scandal, where it obtained personal data from millions of Facebook users without their consent. This data was primarily used for political advertising, effectively influencing the election1. The reason commercial companies can offer free services is that the value created by our data (e.g., influencing presidential elections) far exceeds the cost of developing and maintaining the product. Now, with Meta entering the Fediverse, isn\u0026rsquo;t it equivalent to giving Meta our data (posts, usernames—public but still valuable data) for free without any effort? And Meta doesn\u0026rsquo;t even need our consent.\nPotential Side Effects of Resistance What ultimately determines whether people stay or leave a platform is not flashy features or privacy policies that most people don\u0026rsquo;t read carefully, but the community the platform fosters—more fundamentally, the people on the platform.\nI have to keep posting on twitter because that\u0026rsquo;s where my audiences are.\nWhy does Elon Musk\u0026rsquo;s erratic behavior still keep people on Twitter? It\u0026rsquo;s because of Twitter\u0026rsquo;s ace card—over a decade of user and content accumulation. The same applies to Threads—thousands of interesting people are using this service, and it\u0026rsquo;s extremely unfair to exclude them because of Meta\u0026rsquo;s issues.\nPotential Benefits of Threads Threads has generated a lot of buzz. This is clearly a boon for the average user to access the Fediverse. Think about the confusion new users face when they flood into Fediverse instances—perhaps Threads can truly make their lives easier.\nThe original intent of ActivityPub was to decentralize social media, much like email. Email can serve as a reference for Threads. If managed well, Threads could become a service akin to Gmail. Even as Gmail has grown, the email protocol itself has neither died nor become an internal Gmail-exclusive protocol. Instead, Gmail has made it easier for the average person to access email, lowering the barrier to entry in a sense.\nConclusion Threads has not yet officially launched, so it\u0026rsquo;s too early to pass final judgment on its merits and demerits. At this point, Gmail is probably the best product to glimpse Threads\u0026rsquo; future, and Threads is likely to follow a similar development path. No matter how much we analyze now, it\u0026rsquo;s all just speculation about Threads. I\u0026rsquo;m looking forward to seeing how Threads will develop. For now, we\u0026rsquo;ll just have to wait for its launch.\nFurther Reading Not that kind of \u0026ldquo;Open\u0026rdquo;: A reference for this article.\nIs Gmail killing independent email?: Discusses how Gmail is stifling independent email, offering a glimpse into Threads\u0026rsquo; potential future.\nFor more details, see https://zh.wikipedia.org/wiki/Facebook-剑桥分析数据丑闻\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2023-07-05T13:12:05Z","image":"https://blog-img.l3zc.com/20231216110724.webp","permalink":"/en/2023/07/opinions-on-meta-thread/","title":"Some Thoughts on \"Threads\""},{"content":"A year ago, there was one more place online where I could write for myself. A year later, I’ve written quite a bit after all. Looking back from the vaguely curious junior high school student I once was, fumbling through setting up my first blog, to now—my first year of truly recording things on this blog—I can’t help but sigh at how fast time passes.\nWhy launch the blog? Every journey has a reason to begin, though sometimes people just leave on a whim.\nTo answer that, I’d have to ask the clumsy version of myself who tried deploying my first static blog on Github Pages: there was no real reason. Because that\u0026rsquo;s cool.\nSo I learned a bit of HTML, JS, and CSS. Then I spent some time figuring out how Hexo themes actually worked. Stumbling along, I modified a Hexo theme and somehow managed to turn it into something close to what I wanted. Back then, I didn’t even know what Webpack was, yet I still spent ages editing .min.css and .min.js files one by one. Thinking about it now, all I can do is smile.\nYou don’t need a reason to do something. As long as there isn’t a strong reason not to, just go ahead and do it.\nSome things that happened this year 2022.04：\nlaunch the blog, and send out a Hello World on Github Pages.\n19 years old\n(with others) took part in some competitions: Challenge Cup, Chuang Qingchun, innovation and entrepreneurship\n2022.06：\nCET-6 scores came out; although the score wasn’t very pretty, passing it in the second semester of freshman year was still not bad\nattended a formalistic college student representative meeting\n2022.07：\ngot an Azure student server, and switched the blog framework to Wordpress\nwoke up at five in the morning to move to the new campus, it exhausted me\n2022.08：\ngot my driver’s license\nsemester started, watched the new recruits do military training\n2022.09：\ntook over a team passed down from senior students and cleaned up the mess\nbecame a deputy department head in the college student union\n2022.10：\norganized the opening ceremony for the new recruits new students\nPCR tests, PCR tests, PCR tests every day\ngot dragged into the school sports meet\n2022.11：\ngot a new computer; the old 1715 could finally rest for a while\n11.24, may the dead rest in peace; do not stay silent, do not become numb\n2022.12:\nto prevent larger-scale protests, the government sent us home\ngot COVID once\n2022.01:\nlong-distance trip, hiked Tiger Leaping Gorge, visited Lijiang\u0026hellip;\ngoofy red packet relay in the department WeChat group during the New Year\n2022.02：\nbought a new monitor and controller; now this really is an esports dorm\nStatistics I switched statistics platforms three times, so I don’t have continuous data. We can only make do with what’s available.\nBecause Google Analytics is easily blocked, I can only make a rough estimate. Using Umami’s monthly traffic first, then roughly estimating my own traffic, I can say that over the past year I got about 8,000 visitors and around 10,000 PV.\nFor a site that has only been up for a year, has no ICP filing, and has not been indexed by Baidu, that’s already pretty good.\nThis year, I wrote 27 articles in total and received 18 new comments. On average, that comes to 2.25 articles per month.\nThe changes I went through In the early days after launching the blog, for SEO and to avoid sensitive topics, everything I posted was half-baked technical blog content. Looking at it now, I really did myself a disservice.\nAs for self-censorship, I wrote a post. Since then, I no longer censor myself. Only then did this blog finally return to its original purpose—not writing for the sake of writing, but writing when I genuinely feel moved to. If even that impulse has to be worn away by self-censorship, then there’s no point talking about writing anything else.\nI personally believe the purpose of language is to convey the most accurate information with the most concise words. Writing is a kind of cultivation. The goal of that cultivation is proper balance between detail and brevity, clear structure, and refined language. I used to be obsessed with making every blog post long enough, but after writing plenty of verbose nonsense, I finally let that go.\n……\nA few other things This year my blog moved from Hexo to Wordpress, and recently I’ve wanted to move it to Hugo as well. But since it has already been running for so long and I’ve already spent a year on SEO, switching to yet another blog system would be too much tinker, so I just Stick with Wordpress.\nI also tried hosting the blog in many different places: Hong Kong Azure, Cloudflare, Racknerd, BandWagon. After going in circles and stepping into plenty of pitfalls, I ended up back on Hong Kong Azure. Maybe plain and steady really is best.\nI really regret not trying more technology stacks from the very beginning, and that’s something I want to do this year. To make a better-looking theme and the features I wanted, I learned how to write PHP. To deploy Umami, I learned how to use Docker Compose\u0026hellip; Building a blog is a learning process to begin with: the more you learn, the more you write; the more you write, the more you learn.\nFinally Looking back on this year, I always have this feeling of “How did I only get this little done?”\nI really spent far too much time on public affairs, and far too little on improving myself. I’m becoming more and more uninterested in the student union. When I first entered university, I was full of enthusiasm for serving classmates and improving myself, but that passion was worn away little by little by one meaningless formalistic activity after another. Looking back, maybe I really was too naive at the time\u0026hellip; Even if I genuinely wanted to accomplish something in the student union, the formalism and bureaucracy that fill this university would turn every passionate person into a gloomy shell who only wants to coast through life and wait for the days to pass. In today’s terms, that’s called “lying flat.”\nWhen poor, cultivate oneself; when successful, help the world. Given where I am now, I really should seriously think about how to cultivate myself first.\n","date":"2023-05-02T22:28:18Z","permalink":"/en/2023/05/1st-anniversary/","title":"One Year Since Launching the Blog"}]