<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Distorted Scribbles</title>
        <link>https://blog.l3zc.com/en/</link>
        <description>Recent content on Distorted Scribbles</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language><atom:link href="https://blog.l3zc.com/en/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>Understanding the Regulations on the Administration of Internet Information Services (Draft Amendment for Comment) — Is a Blanket Ban on Circumvention Tools Finally Coming?</title>
            <link>https://blog.l3zc.com/en/2026/07/understanding-the-viral-cac-legislation/</link>
            <pubDate>Sun, 05 Jul 2026 21:44:28 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2026/07/understanding-the-viral-cac-legislation/</guid>
            <description>&lt;p&gt;The Cyberspace Administration of China (CAC)&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; recently opened another round of public consultation on the Regulations on the Administration of Internet Information Services (Draft Amendment for Comment)&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; (hereafter &amp;rsquo;the Regulations&amp;rsquo;), and several of the revised provisions inside it have got a lot of people worried — particularly the language around &amp;lsquo;penetrating or circumventing technical measures&amp;rsquo; and &amp;lsquo;information lawfully blocked&amp;rsquo;. Almost overnight, cries of &amp;lsquo;circumvention is now flatly illegal&amp;rsquo; and &amp;lsquo;here comes the blanket ban&amp;rsquo; started flooding the internet.&lt;/p&gt;&#xA;&lt;p&gt;Discussions about &amp;lsquo;blanket bans&amp;rsquo; and &amp;lsquo;whitelisting&amp;rsquo; crop up online almost every year, and if you can&amp;rsquo;t quite gauge how big an audience this line of argument has, just look back at how the &amp;lsquo;ban on exporting personal data&amp;rsquo; provision was interpreted a few years ago. Right then, let&amp;rsquo;s have a proper look at whether this round of the Regulations really is &amp;lsquo;an escalation in control&amp;rsquo;, or just a bit of routine technical legislative tidying up.&lt;/p&gt;&#xA;&lt;h2 id=&#34;article-26-does-it-explicitly-make-circumvention-illegal&#34;&gt;Article 26: Does it explicitly make &amp;lsquo;circumvention&amp;rsquo; illegal?&#xA;&lt;/h2&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Article 26&lt;/strong&gt; No organisation or individual shall carry out any of the following acts that disrupt the order of internet information services:&lt;/p&gt;&#xA;&lt;p&gt;(1) Publishing, deleting, blocking, disconnecting links to, replacing, demoting, or algorithmically recommending information, or providing such services to others;&lt;/p&gt;&#xA;&lt;p&gt;(2) Falsely registering or registering internet accounts in bulk, or illegally hoarding or trading internet accounts;&lt;/p&gt;&#xA;&lt;p&gt;(3) Manipulating or using multiple internet accounts in bulk to publish information containing content prohibited by laws and administrative regulations and other harmful information, engaging in false clicks, votes, rankings, leaderboards, comments, transactions, or reviews, fabricating traffic, hijacking traffic, manufacturing false public opinion hotspots, or manipulating leaderboards and trending topics and other key elements;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;(4) Carrying out unlawful acts of penetrating or circumventing the technical measures of relevant state institutions;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;(5) Other acts prohibited by laws and administrative regulations.&lt;/p&gt;&#xA;&lt;p&gt;Internet information service providers shall adopt measures such as monitoring for abnormal data and conducting manual review, and strengthen the prevention and monitoring of the acts specified in the preceding paragraph.&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;At first glance this looks genuinely alarming — doesn&amp;rsquo;t this explicitly make circumvention illegal? The drafting of laws and regulations demands close parsing of every word; almost every legal provision goes through repeated scrutiny and wording. Read that clause again, slowly:&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;Carrying out &lt;strong&gt;unlawful&lt;/strong&gt; acts of penetrating or circumventing the technical measures of relevant state institutions&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;&amp;lsquo;Unlawful&amp;rsquo; is the modifier here, qualifying the act of &amp;lsquo;penetrating or circumventing the technical measures of relevant state institutions&amp;rsquo;. In other words, not all penetration or circumvention is prohibited — only the &amp;lsquo;unlawful&amp;rsquo; kind is.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-counts-as-unlawful&#34;&gt;What counts as &amp;lsquo;unlawful&amp;rsquo;&#xA;&lt;/h3&gt;&lt;p&gt;So what does &amp;lsquo;unlawful&amp;rsquo; (违法) actually mean here?&lt;/p&gt;&#xA;&lt;p&gt;The NPC Standing Committee&amp;rsquo;s Legislative Affairs Commission&amp;rsquo;s Legislative Technical Specifications gives a clear definition of the concept of &amp;lsquo;unlawful&amp;rsquo;: &lt;strong&gt;&amp;lsquo;unlawful&amp;rsquo; generally refers to conduct that violates a mandatory rule of law&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. In other words, to determine that an act is &amp;lsquo;unlawful&amp;rsquo;, there has to be a clear, mandatory legal rule that it actually violates — you can&amp;rsquo;t just assert it out of thin air. Here are two reference examples given in that specification:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Example 1: If an inspected unit or individual refuses to stop unlawful conduct, causing serious soil erosion, and this is reported to and approved by the water administration authority, the tools, construction machinery, equipment, etc. used to carry out the unlawful conduct may be seized or impounded. (Article 43, Soil and Water Conservation Law)&lt;/li&gt;&#xA;&lt;li&gt;Example 2: Where a villagers&amp;rsquo; committee fails to promptly publicise matters that should be publicised, or the matters publicised are untrue, villagers have the right to report this to the township, ethnic township, or town government, or to the county-level government and its relevant competent department, and the relevant government or competent department shall be responsible for investigating and verifying the matter and ordering publication in accordance with the law; where unlawful conduct is confirmed upon investigation, the relevant persons shall bear responsibility in accordance with the law. (Article 31, Organic Law of Villagers&amp;rsquo; Committees)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If that&amp;rsquo;s still not clicking, it helps to compare &amp;lsquo;unlawful&amp;rsquo; (违法) with &amp;lsquo;illegal&amp;rsquo; (非法). The Legislative Technical Specifications explains &amp;lsquo;illegal&amp;rsquo; as: usually also a form of unlawfulness, but the emphasis is on conduct that lacks a legal basis. What does that mean? It doesn&amp;rsquo;t mean &amp;rsquo;there&amp;rsquo;s no law that permits you to do this&amp;rsquo; — it means: this type of conduct inherently requires some basis, permit, qualification, authorisation, or lawful source under the law, and the person carrying it out simply doesn&amp;rsquo;t have it. Take &amp;lsquo;practising medicine illegally&amp;rsquo; (非法行医) — you&amp;rsquo;re supposed to hold a medical practitioner&amp;rsquo;s licence, but you don&amp;rsquo;t, so you&amp;rsquo;re practising medicine illegally. Apply the same structure to &amp;lsquo;practising medicine unlawfully&amp;rsquo; (违法行医), and it starts to sound like you do hold a licence, but you&amp;rsquo;re practising in the wrong way — which is a much less common phrasing.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Term&lt;/th&gt;&#xA;          &lt;th&gt;Focus&lt;/th&gt;&#xA;          &lt;th&gt;Implication&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Practising medicine illegally (非法行医)&lt;/td&gt;&#xA;          &lt;td&gt;You have no medical qualification at all&lt;/td&gt;&#xA;          &lt;td&gt;You shouldn&amp;rsquo;t be practising medicine&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Practising medicine unlawfully (违法行医)&lt;/td&gt;&#xA;          &lt;td&gt;(Uncommon phrasing) implies you&amp;rsquo;re qualified but operating in violation of rules&lt;/td&gt;&#xA;          &lt;td&gt;You&amp;rsquo;re practising the wrong way&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Illegally possessing firearms (非法持有枪支)&lt;/td&gt;&#xA;          &lt;td&gt;You have no lawful basis to possess them&lt;/td&gt;&#xA;          &lt;td&gt;You shouldn&amp;rsquo;t have this at all&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Unlawful detention (非法拘禁)&lt;/td&gt;&#xA;          &lt;td&gt;You have no legal authority to detain anyone&lt;/td&gt;&#xA;          &lt;td&gt;You have no right to do this&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Unlawful penetration (违法穿透)&lt;/td&gt;&#xA;          &lt;td&gt;A relevant law or administrative order has explicitly prohibited &amp;lsquo;penetration&amp;rsquo;&lt;/td&gt;&#xA;          &lt;td&gt;There&amp;rsquo;s a law or ban in place, and you&amp;rsquo;ve violated it&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;What this specification and its examples show is that determining whether an act is &amp;lsquo;unlawful&amp;rsquo; requires a clear, pre-existing legal basis. The trouble is, where exactly would you go looking for one? As everyone knows, the GFW blocks Google, YouTube, Twitter, and so on, but that blocking has never once been backed by a court order, a signed administrative document, or even so much as an informal, official blocklist&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. Take our hypothetical lawless everyman Zhang San&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;, who sets up his own ShadowSocks server to access Google, YouTube, Twitter, and the like: for a start, his conduct doesn&amp;rsquo;t even meet the precondition for the rule to apply — disrupting the order of internet information services — and second, there&amp;rsquo;s no law or administrative document explicitly blocking or prohibiting access to those sites either. So, in the strict legal sense, Zhang San is not acting &amp;lsquo;unlawfully&amp;rsquo;.&lt;/p&gt;&#xA;&lt;p&gt;Which is to say: Article 26(4) simply doesn&amp;rsquo;t apply to Zhang San, because his conduct fails to meet the precondition of being &amp;lsquo;unlawful&amp;rsquo; in the first place.&lt;/p&gt;&#xA;&lt;h3 id=&#34;who-is-this-clause-actually-aimed-at&#34;&gt;Who is this clause actually aimed at&#xA;&lt;/h3&gt;&lt;p&gt;This clause sits alongside the following types of conduct:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Manipulating information publication/deletion/algorithmic recommendation&lt;/li&gt;&#xA;&lt;li&gt;Bulk fake account registration&lt;/li&gt;&#xA;&lt;li&gt;Fake engagement, fake rankings, fake traffic, fake public sentiment&lt;/li&gt;&#xA;&lt;li&gt;Unlawfully penetrating or circumventing the technical measures of relevant state institutions&lt;/li&gt;&#xA;&lt;li&gt;Other&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The targets of the first three items are pretty clear — black-market operators, astroturfing outfits, SEO/GEO manipulation, that sort of thing. Item (4) sits alongside them, and in that context it implies the target is organised, commercialised conduct that disrupts the order of information services — not Zhang San having a look at Google Scholar from his living room.&lt;/p&gt;&#xA;&lt;p&gt;Plausible targets:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Commercial proxy providers&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; and VPN services (operating telecoms business and circumvention technical measures without a licence — this used to sit in a grey area under the guise of &amp;lsquo;accelerators&amp;rsquo;, and in theory can now be caught here too)&lt;/li&gt;&#xA;&lt;li&gt;Anyone using circumvention tools for overseas public-opinion manipulation, cross-border fraud, or gambling-referral schemes&lt;/li&gt;&#xA;&lt;li&gt;Anyone helping a lawfully shut-down website come back to life via technical means&lt;/li&gt;&#xA;&lt;li&gt;Attacks that breach the security measures of government or corporate intranets and private networks&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;article-30-strangling-the-entire-grey-market-supply-chain&#34;&gt;Article 30: Strangling the entire grey-market supply chain?&#xA;&lt;/h2&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Article 30&lt;/strong&gt; Where anyone knowingly aware that another person is violating this Regulation by carrying out any of the following acts, &lt;strong&gt;no organisation or individual shall provide that person with support, assistance, or other help in the form of data, technology, programs, tools, software, advertising, services, payment settlement, or otherwise:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;(1) Producing, copying, publishing, or disseminating information that violates Article 24 or Article 25, Paragraphs 1 and 2 of this Regulation;&lt;/p&gt;&#xA;&lt;p&gt;(2) Disrupting the order of internet information services in violation of Article 26, Paragraph 1 of this Regulation;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;(3) Enabling another person to obtain or disseminate information lawfully blocked.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;This one looks alarming too — does merely helping someone else see lawfully blocked information at any point in the chain now count as unlawful? Wouldn&amp;rsquo;t that sweep in proxy resellers, risky payment processors, review/promotion channels, and even the developers of circumvention cores, all under the jurisdiction of this one clause?&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-counts-as-information-lawfully-blocked&#34;&gt;What counts as &amp;lsquo;information lawfully blocked&amp;rsquo;&#xA;&lt;/h3&gt;&lt;p&gt;In practice, &amp;rsquo;lawful blocking&amp;rsquo; tends to fall into a few categories: court rulings, administrative penalties, police case-handling, and ministry-led special operations.&lt;/p&gt;&#xA;&lt;p&gt;Court rulings are most common in the intellectual property field. A rights holder sues a pirate website, the court finds infringement, and orders the ISP to block the domain. There&amp;rsquo;s a judgment, a case number, a defendant, and an effective date.&lt;/p&gt;&#xA;&lt;p&gt;Then there&amp;rsquo;s administrative penalties — say, the telecoms regulator issues an administrative penalty decision against an unregistered&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; website, orders it to rectify, and when it refuses to comply, notifies the access provider to stop service. There&amp;rsquo;s a penalty decision number, proof of service, a citation of legal basis, and notice of remedies (e.g. &amp;lsquo;if dissatisfied with this penalty decision, you may apply for administrative reconsideration to XX authority within sixty days of receiving this decision, or file an administrative lawsuit with the XX People&amp;rsquo;s Court within six months&amp;rsquo;).&lt;/p&gt;&#xA;&lt;p&gt;Police case-handling is easy enough to picture — think anti-vice sweeps: for websites involved in fraud, gambling, or pornography, the police can open an investigation and require ISPs to cooperate in cutting off access. This usually comes with a formal case-opening decision and a written notice of assistance sent to the operator, which the operator keeps on file.&lt;/p&gt;&#xA;&lt;p&gt;Ministry-led special operations are a slightly different case — for example, the National Copyright Administration&amp;rsquo;s annual &amp;lsquo;Sword Net&amp;rsquo; campaign, which publishes a list of infringing websites each year and instructs ISPs to block them. There&amp;rsquo;s a public notice document, a list of websites, and a cited legal basis. The procedure is streamlined, but there&amp;rsquo;s still a paper trail from an actual government department.&lt;/p&gt;&#xA;&lt;h3 id=&#34;does-helping-someone-else-circumvent-the-wall-count-as-helping-them-obtain-or-disseminate-lawfully-blocked-information&#34;&gt;Does helping someone else &amp;lsquo;circumvent the wall&amp;rsquo; count as helping them obtain or disseminate lawfully blocked information?&#xA;&lt;/h3&gt;&lt;p&gt;The logic here mirrors what came before. In practice, there is no publicly available administrative document or court order anywhere that says anything like &amp;lsquo;pursuant to Article X of Law Y, it is hereby decided that access to Google/YouTube/Telegram shall be blocked&amp;rsquo;. You won&amp;rsquo;t find a stamped decision, you won&amp;rsquo;t find the name of the specific administrative body that made the blocking decision, you won&amp;rsquo;t find a stated duration for the block, and you won&amp;rsquo;t find any avenue for appeal.&lt;/p&gt;&#xA;&lt;p&gt;Legally speaking, the GFW&amp;rsquo;s blocking of overseas websites has always been a factual act rather than a legal act. It exists, it functions, but it has never gone through the procedure of &amp;lsquo;issuing an administrative decision → notifying the party concerned → allowing appeal&amp;rsquo;. It&amp;rsquo;s not hard to see that the GFW&amp;rsquo;s blocking of platforms like Google and Twitter isn&amp;rsquo;t the &amp;rsquo;lawful blocking&amp;rsquo; this clause is talking about.&lt;/p&gt;&#xA;&lt;p&gt;At this point someone might ask: Article 8 requires an ICP licence&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt; to provide internet information services within Mainland China → Google doesn&amp;rsquo;t have an ICP licence → Google is therefore providing services unlawfully → blocking an unlawful service = lawful blocking — doesn&amp;rsquo;t that add up to lawful blocking after all? It sounds plausible, but it runs into serious trouble on closer inspection.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;First, the question of jurisdiction.&lt;/strong&gt; Article 2 refers to &amp;rsquo;engaging in internet information services within the People&amp;rsquo;s Republic of China&amp;rsquo;. Google&amp;rsquo;s servers aren&amp;rsquo;t in China, and neither is the company (it retained some R&amp;amp;D staff after exiting the Chinese market years ago, but that R&amp;amp;D arm doesn&amp;rsquo;t run any of Google&amp;rsquo;s public-facing services). Google hasn&amp;rsquo;t proactively conducted business within Chinese territory — a Chinese user visiting Google, and Google proactively conducting business within China, are two entirely different things. Article 92 does say that &amp;lsquo;where an organisation or individual outside the People&amp;rsquo;s Republic of China provides internet information services to the People&amp;rsquo;s Republic of China, it shall comply with the law&amp;rsquo; — but that&amp;rsquo;s just a statement of obligation, not an automatic grant of jurisdiction. You&amp;rsquo;d first have to establish that it is &amp;lsquo;providing services within Chinese territory&amp;rsquo;, and that determination itself requires a formal administrative process.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Second, failing to obtain an ICP licence doesn&amp;rsquo;t carry &amp;lsquo;blocking&amp;rsquo; as its legal consequence.&lt;/strong&gt; Article 74 is quite clear: for anyone providing internet information services without a licence — &amp;rsquo;the telecoms authority shall order rectification; where rectification is refused, it shall order suspension of business for rectification.&amp;rsquo; Note the legal consequences here are &amp;lsquo;ordering rectification&amp;rsquo; and &amp;lsquo;ordering suspension of business for rectification&amp;rsquo;, not blocking. And these penalty measures presuppose that you can actually serve notice and enforce them — that works fine against a domestic entity, but how do you &amp;lsquo;order&amp;rsquo; a foreign one to do anything? You don&amp;rsquo;t even have an address to serve the notice to — are you going to mail the legal papers to Mountain View?&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Third, Article 67 has no reach over overseas websites that never held a licence in the first place.&lt;/strong&gt; &amp;lsquo;Where a licence or approval number is revoked, rescinded, or cancelled by the telecoms authority, the telecoms authority shall notify the relevant internet access service provider and domain name resolution service provider to stop providing services to it.&amp;rsquo; This clause comes closest to describing lawful blocking — notifying an ISP to cut off service. But it applies to entities that have been &amp;lsquo;revoked, rescinded, or cancelled&amp;rsquo; — you have to &lt;strong&gt;have held&lt;/strong&gt; a licence first, and then had it taken away, for this clause to be triggered at all. It simply has no reach over an overseas website that has &lt;strong&gt;never held a licence in the first place&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In fact, you can work this out in reverse just by looking at the current state of the internet since the filing system came into force. We know that if &amp;rsquo;lawful blocking&amp;rsquo; were applied uniformly to every unregistered website, the effect would be identical to a whitelisting system — only registered websites could be accessed, and everything else would be blocked outright. But the reality is that the domestic internet is clearly not in a whitelist state — plenty of unregistered overseas websites remain perfectly accessible. From this we can conclude that the GFW&amp;rsquo;s blocking of specific websites isn&amp;rsquo;t &amp;rsquo;lawful blocking&amp;rsquo; in the sense the clause intends. And since information obtained through circumvention doesn&amp;rsquo;t count as &amp;rsquo;lawfully blocked information&amp;rsquo;, Article 30(3) simply doesn&amp;rsquo;t apply either.&lt;/p&gt;&#xA;&lt;h2 id=&#34;article-15-officially-signing-the-death-warrant-for-dedicated-line-proxy-resellers&#34;&gt;Article 15: Officially signing the death warrant for dedicated-line proxy resellers?&#xA;&lt;/h2&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Article 15&lt;/strong&gt; The state implements a filing management system for the allocation and use of internet protocol addresses. IP address allocation bodies shall promptly report IP address information to the telecoms authority for filing.&lt;/p&gt;&#xA;&lt;p&gt;Before providing access services, internet access service providers shall verify the corresponding IP address filing information, and shall not provide services to IP addresses that are unregistered or falsely registered.&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;This kind of regulatory provision targeting IDCs and ISPs has been around for a long time already, and proxy resellers have carried on operating regardless, under the guise of &amp;lsquo;accelerators&amp;rsquo; or by piggybacking on other licensed entities. Exactly when and how vigorously it gets enforced is something the Ministry of Industry and Information Technology (MIIT) adjusts on the fly, depending on domestic political and economic needs. The recent wave of mass line-cutting happens to coincide with a leadership reshuffle at MIIT — whether this level of enforcement is a &amp;rsquo;new broom sweeps clean&amp;rsquo;&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; style temporary campaign, or signals a longer-term tightening, remains to be seen.&lt;/p&gt;&#xA;&lt;h2 id=&#34;penalties-and-other-details&#34;&gt;Penalties and other details&#xA;&lt;/h2&gt;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Article 84&lt;/strong&gt; Violations of Article 26, Article 30, Article 32, Article 48, or Article 49 of this Regulation shall be punished in accordance with the relevant laws and administrative regulations. Where laws and administrative regulations do not so provide, the cyberspace, telecoms, public security, and other relevant competent authorities shall, according to their duties, order rectification within a specified period, and may additionally impose a fine of no less than RMB 100,000 and no more than RMB 1,000,000; where rectification is refused or the circumstances are serious, a fine of no less than RMB 1,000,000 and no more than RMB 5,000,000 shall be imposed, and suspension of relevant business or suspension of business for rectification may additionally be ordered.&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;Note that the fine here starts at RMB 100,000, caps out at RMB 5,000,000, and can come with an order to &amp;lsquo;suspend relevant business or suspend business for rectification&amp;rsquo; — this wording is clearly pointed at &lt;strong&gt;organisations and commercialised entities&lt;/strong&gt;, not individual users. An individual using a proxy at home to look at Google doesn&amp;rsquo;t involve any &amp;lsquo;business&amp;rsquo; or need for &amp;lsquo;rectification&amp;rsquo; — and the way the penalty clause is designed backs this up: it&amp;rsquo;s aimed at organised commercial conduct, not ordinary internet users.&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s more, the penalty clause only applies where Article 26 or Article 30 has actually been violated — and as already analysed above, an individual&amp;rsquo;s circumvention conduct doesn&amp;rsquo;t satisfy the elements required to trigger either clause. However severe the penalty, if the elements aren&amp;rsquo;t met, it never gets to you in the first place.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-circumvention-actually-gets-punished-in-practice&#34;&gt;How &amp;lsquo;circumvention&amp;rsquo; actually gets punished in practice&#xA;&lt;/h2&gt;&lt;h3 id=&#34;the-sword-of-damocles&#34;&gt;The Sword of Damocles&#xA;&lt;/h3&gt;&lt;p&gt;The analysis so far has looked at this purely from the angle of legal text: an individual circumventing the wall doesn&amp;rsquo;t meet the elements required under the new Regulation&amp;rsquo;s relevant clauses. But legal text is one thing, and real-world enforcement is another.&lt;/p&gt;&#xA;&lt;p&gt;In fact, administrative penalties against individuals using VPNs have existed all along — just not under this new Regulation, but under Article 6 of the Interim Provisions on the Management of International Connections to Computer Information Networks&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;, enacted back in 1997 — &amp;lsquo;any direct international connection of a computer information network must use the international access channels provided by the state public telecommunications network under the Ministry of Posts and Telecommunications&amp;rsquo; — along with the penalties set out in Article 14: a warning, confiscation of unlawful gains, a fine of up to RMB 5,000 for individuals, and up to RMB 15,000 for organisations.&lt;/p&gt;&#xA;&lt;p&gt;This provision has been sitting on the books for nearly thirty years, dormant for most of that time. But it&amp;rsquo;s never been repealed, which means enforcement authorities can choose to activate it at any moment — and the timing, intensity, and choice of target depend entirely on the political climate and local enforcement appetite at the time.&lt;/p&gt;&#xA;&lt;p&gt;Worth flagging: the concept of an &amp;lsquo;international access channel&amp;rsquo; is itself vaguely defined. The Interim Provisions require use of &amp;rsquo;the international access channel provided by the state public telecommunications network&amp;rsquo;, but many dedicated-line proxy resellers buy precisely the IPLC (International Private Leased Circuit) or IEPL (International Ethernet Private Line) services run by the three major state carriers — China Telecom, China Unicom, and China Mobile. These are legitimate international bandwidth products that the three carriers openly sell to corporate customers. If a user accesses overseas networks through a legally purchased carrier international leased line, does that satisfy the requirement of &amp;lsquo;using the international access channel provided by the state public telecommunications network&amp;rsquo;? The provision offers no answer, and enforcement practice has never settled the question either. This ambiguity only widens the scope for selective enforcement.&lt;/p&gt;&#xA;&lt;h3 id=&#34;real-cases&#34;&gt;Real cases&#xA;&lt;/h3&gt;&lt;p&gt;Based on publicly searchable administrative penalty records&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;, the number of documented penalty cases involving individuals using VPNs far exceeds what most people would assume. Here are a few well-documented examples:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Nanxiong, Shaoguan, Guangdong (2019)&lt;/strong&gt;: the party used Lantern to circumvent the wall 487 times in total, and was issued a warning and fined RMB 1,000 by the Nanxiong Public Security Bureau.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Jinhua, Zhejiang (2020)&lt;/strong&gt;: Decision No. Jingongdong (Cyber Police) Xingfajuezi [2020] No. 00751 — the party was administratively penalised for using an unauthorised international channel.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Suining, Sichuan (2019)&lt;/strong&gt;: Decision No. Suichuangong (Yong) Xingfajuezi [2019] No. 489 — a similar penalty.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Somewhere (2025)&lt;/strong&gt;: a party referred to as &amp;lsquo;Xiao Zhang&amp;rsquo; was found to have VPN usage records during a network security inspection and was fined RMB 15,000 (the maximum penalty).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Xiaogan, Hubei (March 2026)&lt;/strong&gt;: the Xiaogan Xinhua Police Station sent more than ten officers in person, and the party was fined RMB 500 for using a VPN.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Almost all of the above cases cite the same legal basis: Articles 6 and 14 of the Interim Provisions on the Management of International Connections to Computer Information Networks.&lt;/p&gt;&#xA;&lt;h3 id=&#34;problems-in-enforcement&#34;&gt;Problems in enforcement&#xA;&lt;/h3&gt;&lt;p&gt;A few things worth noting stand out from these cases:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Selective enforcement.&lt;/strong&gt; China&amp;rsquo;s circumvention-using population is conservatively estimated in the tens of millions&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;, while publicly documented penalty cases number only in the dozens. This means the overwhelming majority of people will never be pursued — but anyone could, in theory, be the one who is. Here, the law isn&amp;rsquo;t functioning as a generally applicable rule so much as a sword hanging over everyone&amp;rsquo;s heads, and when it falls depends entirely on the will of the enforcer. With economic growth slowing in recent years and fiscal pressure mounting at the local level, the &amp;lsquo;distant-water fishing&amp;rsquo;&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt; phenomenon that&amp;rsquo;s emerged in some regions is perhaps the most darkly ironic illustration of this kind of selective enforcement — when you&amp;rsquo;re determined to convict someone, there&amp;rsquo;s never a shortage of pretexts. Hand over your &amp;lsquo;unlawful gains&amp;rsquo; quietly, and hey, someone&amp;rsquo;s performance bonus is finally sorted.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Chaotic application of the law.&lt;/strong&gt; Different regions cite different legal provisions for identical conduct: some rely on Article 6 of the Interim Provisions, others on Article 27 or Article 63 of the Cybersecurity Law, and others still on provisions in the Public Security Administration Punishment Law&lt;sup id=&#34;fnref:14&#34;&gt;&lt;a href=&#34;#fn:14&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;14&lt;/a&gt;&lt;/sup&gt;. The same act can receive wildly different legal treatment depending on where it happens, which in itself shows just how inconsistent the standards for applying the law really are. It&amp;rsquo;s also worth pointing out that China is a civil law jurisdiction rather than a common law one, so court judgments carry no binding precedent — you might be fined RMB 500 for circumventing the wall in Region A, while the exact same conduct in Region B might draw a RMB 15,000 fine, or no action at all, and you have no way to invoke Region B&amp;rsquo;s inaction as a defence for what happened to you in Region A.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Enforcement action disproportionate to the offence.&lt;/strong&gt; In the Xiaogan, Hubei case, more than ten officers were sent out to deal with an administrative violation that ultimately drew a RMB 500 fine — this kind of enforcement posture looks a lot more like intimidation than routine administrative management.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Using it as a pretext for other catch-all charges.&lt;/strong&gt; In some cases, the penalty for circumventing the wall itself is negligible, but the police use it as an entry point to dig further into what the person has been saying on overseas platforms, and then pursue criminal liability under a catch-all charge like &amp;lsquo;picking quarrels and provoking trouble&amp;rsquo;&lt;sup id=&#34;fnref:15&#34;&gt;&lt;a href=&#34;#fn:15&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;15&lt;/a&gt;&lt;/sup&gt;. Circumvention here becomes the pretext for invoking some other catch-all charge — what actually gets punished isn&amp;rsquo;t the act of circumventing the wall itself, but the person&amp;rsquo;s speech or political stance, with circumvention providing the excuse.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-you-can-actually-do&#34;&gt;What you can actually do&#xA;&lt;/h2&gt;&lt;p&gt;After all that, you might be thinking: fine, it doesn&amp;rsquo;t hold up under the legal text, but there&amp;rsquo;s still real-world risk — so wasn&amp;rsquo;t this whole thing pointless?&lt;/p&gt;&#xA;&lt;p&gt;No — this is a &lt;strong&gt;draft for public comment&lt;/strong&gt;, and right now you have an actual, formally documented channel to participate in the legislative process.&lt;/p&gt;&#xA;&lt;h3 id=&#34;submit-your-comments-on-the-draft&#34;&gt;Submit your comments on the draft&#xA;&lt;/h3&gt;&lt;p&gt;The Regulations are currently open for public comment. Under Article 15 of the Regulations on the Procedures for the Formulation of Rules, every citizen has the right to submit comments on a draft for comment. This isn&amp;rsquo;t a favour being granted to you — it&amp;rsquo;s a legally established procedure. You can submit your comments formally through the consultation channel published by the CAC — for example, suggesting that the wording around &amp;lsquo;unlawfully penetrating or circumventing&amp;rsquo; be given a clearer definition of its elements, or that &amp;rsquo;lawfully blocked&amp;rsquo; be given a proper procedural definition to prevent the concept from being abused.&lt;/p&gt;&#xA;&lt;h3 id=&#34;contact-your-local-peoples-congress-deputy&#34;&gt;Contact your local People&amp;rsquo;s Congress deputy&#xA;&lt;/h3&gt;&lt;p&gt;Article 41 of the Constitution of the People&amp;rsquo;s Republic of China:&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Citizens of the People&amp;rsquo;s Republic of China have the right to criticise and make suggestions to any state organ or functionary.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;Article 4 of the Law on Deputies to the National People&amp;rsquo;s Congress and to the People&amp;rsquo;s Congresses at Various Local Levels of the People&amp;rsquo;s Republic of China:&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Deputies shall maintain close contact with the electors or the electoral units that originally elected them, listen to and reflect the opinions and demands of electors and electoral units, and strive to serve the people.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;The district/county-level People&amp;rsquo;s Congress deputy for your constituency has an obligation to hear your views, and district/county People&amp;rsquo;s Congress standing committee websites usually publish a list of deputies and their contact details. Find your deputy, give them a call or write them a letter, and tell them:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;that you believe internet legislation should follow the principle of clarity, and that vague provisions shouldn&amp;rsquo;t become a tool for selective enforcement;&lt;/li&gt;&#xA;&lt;li&gt;that you support governing the internet in accordance with the law, but oppose using the language of &amp;lsquo;in accordance with the law&amp;rsquo; to mask what is in fact &amp;rsquo;no law to rely on&amp;rsquo;;&lt;/li&gt;&#xA;&lt;li&gt;that you&amp;rsquo;d like to see the concept of &amp;rsquo;lawfully blocked&amp;rsquo; given a clear procedural definition in law.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Putting core socialist values into practice starts with contacting your People&amp;rsquo;s Congress deputy.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&#xA;&lt;/h2&gt;&lt;p&gt;This is routine technical legislative housekeeping. Carry on living your life as before, weigh your own risks for whatever you choose to do, and nothing to see here, folks.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: 网信办 (Wǎngxìnbàn) is the common short form for China&amp;rsquo;s Cyberspace Administration (CAC), the body that oversees internet content regulation and policy in Mainland China.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;The full text of the Regulations on the Administration of Internet Information Services (Draft Amendment for Comment) and the notice for public comment can be found on the CAC&amp;rsquo;s official website: &lt;a class=&#34;link&#34; href=&#34;https://www.cac.gov.cn/2025-04/15/c_1746821732498092.htm&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.cac.gov.cn/2025-04/15/c_1746821732498092.htm&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;See the Legislative Technical Specifications (Trial) (II): &lt;a class=&#34;link&#34; href=&#34;https://npcobserver.com/wp-content/uploads/2023/02/Technical-Specifications-for-Legislation-for-Trial-Implementation-II.pdf&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://npcobserver.com/wp-content/uploads/2023/02/Technical-Specifications-for-Legislation-for-Trial-Implementation-II.pdf&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;The Chinese government has never formally acknowledged the existence of the GFW, nor has it ever published a complete list of blocked websites. In 2015, when a State Council spokesperson was asked about this by foreign journalists, they said only that &amp;lsquo;China manages the internet in accordance with the law&amp;rsquo;, without specifying any actual legal basis or blocklist.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: 法外狂徒张三 (&amp;rsquo;lawless outlaw Zhang San&amp;rsquo;) is a running joke in Chinese legal commentary and internet culture — a stock hypothetical everyman used to test how a law would actually apply to an ordinary person&amp;rsquo;s mundane conduct, often deployed sarcastically to expose gaps between a law&amp;rsquo;s literal wording and its intended target.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: 机场 (literally &amp;lsquo;airport&amp;rsquo;) is Chinese internet slang for a commercial proxy/VPN reseller offering paid access to overseas nodes — the term has nothing to do with actual airports and is used throughout this post.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: &amp;lsquo;unregistered&amp;rsquo; here refers to a website that hasn&amp;rsquo;t obtained ICP filing (see the note below) — the Mainland China requirement to register a website with the authorities before it can legally operate.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: an ICP licence/filing (ICP 牌照/备案) is Mainland China&amp;rsquo;s mandatory registration and licensing regime for websites and online services. Any site operating &amp;lsquo;within&amp;rsquo; China is legally required to register with, or obtain a licence from, the telecoms authority; unregistered domestic sites can be ordered offline. This regime — and the question of whether it can be stretched to cover foreign sites that never registered in the first place — sits at the heart of the legal argument in this section.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: 新官上任三把火 is a Chinese idiom meaning a newly appointed official tends to make a show of forceful action early on to establish authority — roughly equivalent to &amp;rsquo;new broom sweeps clean&amp;rsquo;, but with a stronger implication that the initial burst of activity may not be sustained.&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:10&#34;&gt;&#xA;&lt;p&gt;The full text of the Interim Provisions on the Management of International Connections to Computer Information Networks (promulgated in 1997, most recently amended in 2024) can be found on the State Council&amp;rsquo;s official website: &lt;a class=&#34;link&#34; href=&#34;https://www.cac.gov.cn/1996-02/02/c_126468621.htm&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.cac.gov.cn/1996-02/02/c_126468621.htm&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:11&#34;&gt;&#xA;&lt;p&gt;Wang Yuyang (5 September 2020) searched the China Judgments Online database and administrative penalty disclosure platforms and found 51 relevant cases in total.&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:12&#34;&gt;&#xA;&lt;p&gt;A 2014 GlobalWebIndex survey found roughly 93 million VPN users in China, about 14% of internet users at the time. Given that the total number of internet users has kept growing since then, and circumvention tools have become steadily more widespread, the actual current figure can only be higher.&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:13&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: 远洋捕捞 (&amp;lsquo;distant-water fishing&amp;rsquo;) is Chinese internet slang describing local law enforcement authorities travelling out of their own jurisdiction — often to economically wealthier regions or even pursuing targets with only tenuous ties to their jurisdiction — to investigate, prosecute, and seize the assets of businesses or individuals, with the seized funds reportedly used to help plug local fiscal shortfalls. The term likens this practice to fishing boats trawling waters far from home once local stocks run low, and it&amp;rsquo;s widely discussed as a niche but important symptom of local government revenue pressure translating into aggressive, revenue-driven policing.&amp;#160;&lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:14&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: the Public Security Administration Punishment Law (治安管理处罚法) is China&amp;rsquo;s general law governing administrative (non-criminal) penalties for public order offences, enforced directly by police without going through the courts — things like warnings, fines, and short-term detention for minor offences.&amp;#160;&lt;a href=&#34;#fnref:14&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:15&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: &amp;lsquo;picking quarrels and provoking trouble&amp;rsquo; (寻衅滋事) is a notoriously broad and vaguely defined charge under Chinese criminal and public security law, frequently criticised for functioning as a catch-all that can be applied to almost any conduct authorities want to punish — including, in practice, online speech — precisely because its scope is so poorly defined.&amp;#160;&lt;a href=&#34;#fnref:15&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Clearing Up Misunderstandings About DNS Leaks</title>
            <link>https://blog.l3zc.com/en/2026/05/dns-leak-misunderstanding/</link>
            <pubDate>Mon, 18 May 2026 00:46:00 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2026/05/dns-leak-misunderstanding/</guid>
            <description>&lt;p&gt;Anyone clicking into this article has probably already heard of the concept of DNS leaks, and plenty of people go pale at the mere mention of it. Since several people have raised related questions in the comments and issues, I&amp;rsquo;m writing this explainer to try to lay the problem out as clearly as possible, clear up some common misunderstandings about the concept, and give everyone a reference point for writing their own proxy configurations.&lt;/p&gt;&#xA;&lt;p&gt;This concept has already been done to death by various VPN vendors and a whole pile of YouTubers. The basic idea is: while you&amp;rsquo;re using a proxy, your computer instead sends a DNS request over the local network to your local DNS server for the address you&amp;rsquo;re actually visiting, which lets your ISP work out your browsing history from its DNS query logs.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart LR&#xA;    U[User device&lt;br/&gt;Browser / App] --&gt; VPN[VPN client&lt;br/&gt;encrypted tunnel]&#xA;&#xA;    VPN --&gt;|Normal: DNS request via VPN| VPNDNS[VPN-provided DNS server]&#xA;    VPNDNS --&gt; NET[Target website / Internet]&#xA;&#xA;    U -.-&gt;|Abnormal: DNS leak| ISP[Local ISP DNS server]&#xA;    ISP -.-&gt; LOG[ISP / third party can log&lt;br/&gt;visited domains]&#xA;&#xA;    NET --&gt; SITE[example.com]&#xA;&#xA;    subgraph Normal[Normal DNS resolution path]&#xA;        VPN&#xA;        VPNDNS&#xA;        NET&#xA;    end&#xA;&#xA;    subgraph Leak[DNS leak path]&#xA;        ISP&#xA;        LOG&#xA;    end&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    classDef user fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1;&#xA;&#xA;    class U user;&#xA;    class VPN,VPNDNS,NET,SITE safe;&#xA;    class ISP,LOG danger;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;getting-clear-on-what-we-actually-need&#34;&gt;Getting clear on what we actually need&#xA;&lt;/h2&gt;&lt;p&gt;The first thing worth thinking through properly is what we&amp;rsquo;re actually trying to achieve by going to such lengths to prevent DNS leaks. Most people&amp;rsquo;s gut reaction is probably: &amp;ldquo;Obviously, so the censors can&amp;rsquo;t see which sites we&amp;rsquo;re visiting through the proxy!&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s not entirely accurate, actually, and it depends on the situation. Think about it for a moment: would there be any real harm in the censors knowing you&amp;rsquo;re scrolling through Douyin&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; or chatting on WeChat? Probably not. But if they find out you&amp;rsquo;re browsing Wikipedia, checking X (Twitter), or looking at other content with a clear political slant that&amp;rsquo;s flagged as sensitive, then you&amp;rsquo;ve got a real problem. The point of preventing DNS leaks was never to score a full row of green ticks on some so-called &amp;ldquo;DNS leak test&amp;rdquo; website, nor to hide the fact that you use commonly-used domestic services. It&amp;rsquo;s to stop the censors from finding out that you&amp;rsquo;re accessing blocked sites and services that are considered sensitive.&lt;/p&gt;&#xA;&lt;p&gt;For those of us living behind the Wall, working out what counts as sensitive content is actually pretty simple: just check the GFWList&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; directly. Whatever the Great Firewall (GFW) specifically bothers to block is, by definition, sensitive content.&lt;/p&gt;&#xA;&lt;p&gt;The random domains used by these DNS leak test sites don&amp;rsquo;t even come close to touching the GFWList; they carry zero sensitivity to begin with. With a well-optimised proxy configuration, that kind of domain doesn&amp;rsquo;t get a second glance, it&amp;rsquo;s typically skipped straight through to the final catch-all rule (something like &lt;code&gt;MATCH, DIRECT&lt;/code&gt;), and that process naturally triggers a local DNS resolution. So the test site lights up red as if it&amp;rsquo;s struck gold, warning you that &amp;ldquo;a DNS leak exists&amp;rdquo;. But once you understand the actual mechanism, it&amp;rsquo;s obvious that this so-called &amp;ldquo;leak&amp;rdquo; is often completely inconsequential. As long as your configuration is sound, the sensitive access history you actually need to protect has already matched an earlier routing rule and been safely sent down the encrypted tunnel.&lt;/p&gt;&#xA;&lt;p&gt;What&amp;rsquo;s worse, commercial overseas VPN vendors like Surfshark and ExpressVPN, along with a bunch of unscrupulous YouTubers cashing in on sponsorships, keep using these utterly meaningless test results to whip up privacy anxiety. The ironic part is that these traditional commercial VPN clients often don&amp;rsquo;t even have the most basic domain-based routing rule functionality, and instead crudely shove all network traffic through the virtual network adapter in one go. Even more ironic still: even with that crude blanket-proxy approach, they&amp;rsquo;ve genuinely caused real DNS leaks in the past due to flaws in their own client software. So rather than taking these anxiety-peddling marketing pitches at face value, you&amp;rsquo;re better off understanding how proxy software actually works.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-leaks-actually-happen&#34;&gt;Where leaks actually happen&#xA;&lt;/h2&gt;&lt;p&gt;Mainstream proxy software these days is quite mature, and with a properly set-up configuration there&amp;rsquo;s really nothing to worry about regarding DNS leaks. The problem is that a lot of configurations simply aren&amp;rsquo;t set up properly. Next I&amp;rsquo;ll briefly explain where DNS leaks can occur in proxy software under system proxy mode and transparent proxy (virtual network adapter) mode.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-proxy-software-itself&#34;&gt;The proxy software itself&#xA;&lt;/h3&gt;&lt;p&gt;When using a system proxy (say the proxy address is &lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;) and visiting &lt;code&gt;https://www.example.com&lt;/code&gt; in a browser, the request goes through the following process:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Step one: the browser packages up the network request and sends it to &lt;code&gt;socks5://127.0.0.1:7890&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Step two: the proxy software receives the request and matches it against a node using the configured routing rules&lt;/li&gt;&#xA;&lt;li&gt;Step three: the proxy software forwards the entire network request to the matched node&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant B as Browser&#xA;    participant P as Proxy software&lt;br/&gt;(127.0.0.1:7890)&#xA;    participant R as Routing rule engine&#xA;    participant N as Remote proxy node&#xA;&#xA;    B-&gt;&gt;P: ① Packages and sends the request&lt;br/&gt;(carries the domain, no DNS resolution)&#xA;    Note over B,P: Browser doesn&#39;t issue a DNS query&lt;br/&gt;No leak here&#xA;    P-&gt;&gt;R: ② Matches routing rules&lt;br/&gt;based on domain and other info&#xA;    Note over P,R: Rule-matching process&lt;br/&gt;may trigger a DNS query ⚠️&#xA;    R--&gt;&gt;P: Returns match result&lt;br/&gt;(PROXY / DIRECT / REJECT)&#xA;    P-&gt;&gt;N: ③ Encrypts and forwards the request to the node&#xA;    Note over P,N: Transmitted over encrypted tunnel&lt;br/&gt;No leak here&#xA;    N--&gt;&gt;P: Returns response&#xA;    P--&gt;&gt;B: Returns response&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Throughout the whole request process, the browser hands the network request off to the proxy without sending a request to the local DNS, so no leak occurs at this stage. The proxy software forwards the network request to the server, and that whole process is encrypted, so no leak occurs there either. The stage where the actual DNS leak occurs is during routing rule matching.&lt;/p&gt;&#xA;&lt;p&gt;Issuing a domain lookup to local DNS isn&amp;rsquo;t necessarily a bad thing in itself, but a poorly configured routing rule can make the proxy software prematurely query local DNS the moment it gets the domain, leaking privacy information that shouldn&amp;rsquo;t have been leaked. Suppose, in some extreme hypothetical scenario, the Gestapo&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; pulled everyone&amp;rsquo;s DNS query logs from the ISP, and anyone who&amp;rsquo;d queried a domain on the GFW&amp;rsquo;s blocklist got taken out and shot. In that kind of scenario, to protect ourselves, we&amp;rsquo;d need to configure our routing rules properly.&lt;/p&gt;&#xA;&lt;p&gt;Take the widely-used Mihomo core as an example: when it&amp;rsquo;s acting as a client in system proxy mode, and Chrome is at the second step described above, the information the core already has on hand is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Host&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;: &lt;code&gt;example.com&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Target port: 443&lt;/li&gt;&#xA;&lt;li&gt;Network protocol: TCP&lt;/li&gt;&#xA;&lt;li&gt;Name and full path of the requesting process: &lt;code&gt;C:\Application\chrome.exe&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Inbound IP: &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Inbound name: &lt;code&gt;DEFAULT-MIXED&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Inbound port: a random high port&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The core doesn&amp;rsquo;t need to make any extra DNS requests. Based on the information it already has, it can match all routing rules based on domain, port, process name, and network protocol without triggering any DNS resolution at all:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  # Domain-based matching rules&#xA;  - DOMAIN,ad.com,REJECT&#xA;  - DOMAIN-SUFFIX,google.com,auto&#xA;  - DOMAIN-KEYWORD,google,auto&#xA;  - DOMAIN-WILDCARD,*.google.com,auto&#xA;  - DOMAIN-REGEX,^abc.*com,PROXY&#xA;  - GEOSITE,youtube,PROXY&#xA;  # Port-based matching rules&#xA;  - DST-PORT,80,DIRECT&#xA;  - SRC-PORT,7777,DIRECT&#xA;  # Process-name-based matching rules&#xA;  - PROCESS-PATH,/usr/bin/wget,PROXY&#xA;  - PROCESS-PATH,C:\Application\chrome.exe,PROXY&#xA;  - PROCESS-NAME,curl,PROXY&#xA;  - PROCESS-NAME,chrome.exe,PROXY&#xA;  # Network-protocol-based matching rules&#xA;  - NETWORK,UDP,REJECT&#xA;  # Rules that don&#39;t depend on any of the above&#xA;  - MATCH,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So none of the rule types above cause DNS leaks, because the core has no need to trigger DNS resolution to match against them.&lt;/p&gt;&#xA;&lt;p&gt;For the remaining rule types, the target IP information needs to be known at step two before matching can proceed:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOIP,cn,DIRECT&#xA;  - IP-CIDR,127.0.0.0/8,DIRECT&#xA;  - IP-SUFFIX,8.8.8.8/24,PROXY&#xA;  - IP-ASN,13335,DIRECT&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, to ensure matching accuracy, when the core reaches step two it doesn&amp;rsquo;t know the target IP, only the target host address &lt;code&gt;www.example.com&lt;/code&gt;, so it has no choice but to look up the IP address for &lt;code&gt;www.example.com&lt;/code&gt;, and that&amp;rsquo;s what causes the DNS leak. If you append &lt;code&gt;no-resolve&lt;/code&gt;&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; to the end of the rule, matching skips straight over the IP-based rule entirely, which makes &lt;code&gt;no-resolve&lt;/code&gt; well suited to routing bare-IP requests from software like Telegram.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-problem-with-transparent-proxying-tun-mode&#34;&gt;The problem with transparent proxying (TUN mode&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;)&#xA;&lt;/h3&gt;&lt;p&gt;Transparent proxying is, as the name suggests, a way of proxying where the system/software being proxied has no idea it&amp;rsquo;s being proxied at all. This approach is very convenient. Flip on TUN mode and everything just works, no need to laboriously configure every single piece of software that doesn&amp;rsquo;t respect the system proxy setting one by one. The cost is that very convenience: since the target system/software has no idea it&amp;rsquo;s being proxied, it carries on behaving exactly as it would on a normal network, and that naturally includes DNS resolution. But the target system/software doesn&amp;rsquo;t know it&amp;rsquo;s being proxied, and it still needs to get hold of an IP before it can establish a connection to the remote end. To solve this, Mihomo, for example, offers two DNS hijacking modes: &lt;code&gt;redir-host&lt;/code&gt; and &lt;code&gt;fake-ip&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;&lt;code&gt;redir-host&lt;/code&gt; mode:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    A1[Target software issues a DNS query] --&gt; A2[Proxy software queries the real IP&lt;br/&gt;from upstream DNS]&#xA;    A2 --&gt; A3[Upstream DNS returns the real IP]&#xA;    A3 --&gt; A4[Real IP returned to the target software]&#xA;    A4 --&gt; A5[Target software connects using the real IP]&#xA;    A5 --&gt; A6[Proxy software intercepts and routes]&#xA;&#xA;    A2 -.- LEAK[&#34;⚠️ The real DNS request leaks to the outside network&#34;]&#xA;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    class LEAK danger;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;fake-ip&lt;/code&gt; mode:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    B1[Target software issues a DNS query] --&gt; B2[Proxy software immediately returns a fake IP&lt;br/&gt;e.g. 198.18.0.x]&#xA;    B2 --&gt; B3[Target software connects using the fake IP]&#xA;    B3 --&gt; B4[Proxy software intercepts, checks the mapping table&lt;br/&gt;and recovers the real domain]&#xA;    B4 --&gt; B5[Forwards the request over the encrypted tunnel]&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    class B2,B4,B5 safe;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;redir-host&lt;/code&gt; is the honest one of the two: since the target system/software issued a DNS query, it dutifully goes off and queries upstream DNS, and returns the real IP it gets back to the target system/software. Because this mode is bound to trigger a genuine DNS resolution lookup, it carries an inherent structural flaw that inevitably causes DNS leaks. The original Clash core actually dropped &lt;code&gt;redir-host&lt;/code&gt; mode entirely in a later version.&lt;/p&gt;&#xA;&lt;p&gt;That said, Mihomo, which inherited from the Clash Meta core, chose to keep it around, and bolstered it with &lt;code&gt;sniffer&lt;/code&gt;&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;. Under &lt;code&gt;redir-host&lt;/code&gt; mode, &lt;code&gt;sniffer&lt;/code&gt; inspects features within the data packets (such as the SNI field&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt; in TLS) to try to recover the connected domain from traffic that&amp;rsquo;s already been resolved down to a bare IP, then runs that recovered domain back through the routing rules for matching, which patches up many of &lt;code&gt;redir-host&lt;/code&gt; mode&amp;rsquo;s shortcomings when it comes to routing. But it&amp;rsquo;s worth being very clear on this point: &lt;code&gt;sniffer&lt;/code&gt; solves the problem of routing accuracy, it &lt;strong&gt;does not eliminate the DNS leak itself&lt;/strong&gt;. The DNS query has already gone out before &lt;code&gt;sniffer&lt;/code&gt; even gets involved; whatever the ISP was going to log, it&amp;rsquo;s already logged.&lt;/p&gt;&#xA;&lt;p&gt;One thing worth flagging here: if you still insist on using &lt;code&gt;redir-host&lt;/code&gt; mode in a transparent proxy setup, you must set up a set of uncontaminated DNS servers. See my &lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/02/what-i-have-done-on-my-dns/&#34; &gt;other article&lt;/a&gt; for a concrete example, otherwise your DNS access history will still leak.&lt;/p&gt;&#xA;&lt;p&gt;By contrast, &lt;code&gt;fake-ip&lt;/code&gt; mode takes an entirely different approach to solving this, and not only does it neatly sidestep the DNS leak problem under transparent proxying, it can even improve browser page load responsiveness as a side benefit.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-to-avoid-dns-leaks&#34;&gt;How to avoid DNS leaks&#xA;&lt;/h2&gt;&lt;h3 id=&#34;optimise-the-routing-rules-fed-to-the-proxy-software&#34;&gt;Optimise the routing rules fed to the proxy software&#xA;&lt;/h3&gt;&lt;p&gt;As mentioned earlier, poorly designed routing rules cause the proxy software to trigger resolution prematurely. Here&amp;rsquo;s a classic example of what not to do:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOIP,cn,DIRECT&#xA;  - GEOSITE,telegram,Telegram&#xA;  - GEOIP,telegram,Telegram&#xA;  - MATCH,Proxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The problem is in the first line, &lt;code&gt;GEOIP,cn,DIRECT&lt;/code&gt;. When the device requests the domain &lt;code&gt;telegram.org&lt;/code&gt;, the core matches from top to bottom, and the very first entry is an IP rule. But a domain isn&amp;rsquo;t an IP, so to work out whether it falls within a mainland Chinese IP range, the core has no choice but to resolve &lt;code&gt;telegram.org&lt;/code&gt; first. And that resolution is exactly when the DNS query goes out, even though the second rule down, &lt;code&gt;GEOSITE,telegram&lt;/code&gt;, could have matched directly without needing to know the IP at all.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    Start[Request for telegram.org] --&gt; Rule1{Rule 1&lt;br/&gt;GEOIP,cn?}&#xA;    Rule1 --&gt; Leak[⚠️ An IP rule meets a domain&lt;br/&gt;telegram.org must be resolved first&lt;br/&gt;DNS query leaks right here]&#xA;    Leak --&gt; Judge{Is the resolved IP&lt;br/&gt;in mainland China?}&#xA;    Judge -- No --&gt; Rule2{Rule 2&lt;br/&gt;GEOSITE,telegram?}&#xA;    Rule2 -- Yes --&gt; ProxyTG[Routed via Telegram proxy&lt;br/&gt;but the DNS has already leaked]&#xA;&#xA;    classDef danger fill:#fdecea,stroke:#c62828,stroke-width:2px,color:#7f0000;&#xA;    class Leak danger;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once the resolution request goes out, the ISP has already logged it. Whichever rule it ends up matching, or whether it goes through the proxy at all after that, no longer matters, the ISP already knows you&amp;rsquo;re accessing Telegram.&lt;/p&gt;&#xA;&lt;p&gt;Rule matching proceeds top to bottom in order: every time the core receives a request, it checks each rule one by one until it finds a match. In other words, once a domain matches an earlier rule, none of the rules after it, whether it&amp;rsquo;s the second, the third, or even the ten-millionth, ever get a chance to run.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s exactly why, when writing rules, you should follow the principle of &lt;strong&gt;domain rules first, IP rules last&lt;/strong&gt;. Even for a dedicated rule set targeting a specific service, any IP rules placed near the front should be tagged with &lt;code&gt;no-resolve&lt;/code&gt; to tell the core to skip that rule if it doesn&amp;rsquo;t match, rather than resolving to try it. A classic example:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;DOMAIN-SUFFIX,e-hentai.org&#xA;DOMAIN-SUFFIX,ehgt.org&#xA;DOMAIN-SUFFIX,ehwiki.org&#xA;DOMAIN-SUFFIX,exhentai.org&#xA;DOMAIN-SUFFIX,hath.network&#xA;DOMAIN-SUFFIX,hentaiverse.org&#xA;IP-CIDR,178.175.128.0/21,no-resolve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same logic applies when using GeoSite and GeoIP databases: any IP rule near the front should carry &lt;code&gt;no-resolve&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;rules:&#xA;  - GEOSITE,telegram,Telegram&#xA;  - GEOIP,telegram,Telegram,no-resolve&#xA;  - GEOSITE,gfw,Proxy&#xA;  - GEOIP,cn,DIRECT&#xA;  - Match,Proxy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compare this rule set against the earlier bad example, and the difference comes down to exactly when DNS resolution gets triggered:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;    Start[Request for telegram.org] --&gt; Rule1{Rule 1&lt;br/&gt;GEOSITE,telegram?}&#xA;    Rule1 -- Yes --&gt; ProxyTG[Routed via Telegram proxy&lt;br/&gt;✅ No resolution the whole way]&#xA;    Rule1 -- No --&gt; Rule2{Rule 2&lt;br/&gt;GEOIP,telegram,no-resolve?}&#xA;&#xA;    Rule2 -- Yes,&lt;br/&gt;only matches bare-IP requests --&gt; ProxyTG&#xA;    Rule2 -- No --&gt; Rule3{Rule 3&lt;br/&gt;GEOSITE,gfw?}&#xA;&#xA;    Rule3 -- Yes --&gt; ProxyGFW[Routed via Proxy&lt;br/&gt;✅ No resolution the whole way]&#xA;    Rule3 -- No --&gt; NeedIP[Only now does it need an IP,&lt;br/&gt;triggering local DNS resolution]&#xA;&#xA;    NeedIP --&gt; Rule4{Rule 4&lt;br/&gt;GEOIP,cn?}&#xA;    Rule4 -- Yes --&gt; Direct[DIRECT connection]&#xA;    Rule4 -- No --&gt; ProxyOther[Match,Proxy&lt;br/&gt;routed via Proxy]&#xA;&#xA;    classDef safe fill:#e8f7ee,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;&#xA;    classDef warn fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,color:#8a4b00;&#xA;    class ProxyTG,ProxyGFW safe;&#xA;    class NeedIP warn;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Same request for &lt;code&gt;telegram.org&lt;/code&gt;, but this time the very first rule is &lt;code&gt;GEOSITE,telegram&lt;/code&gt;, the domain matches directly, the request gets handed to the proxy, and DNS never comes into it at all. Visiting other sites on the GFW&amp;rsquo;s blocklist works the same way, hitting &lt;code&gt;GEOSITE,gfw,Proxy&lt;/code&gt; before ever reaching that non-negotiable &lt;code&gt;GEOIP,cn,DIRECT&lt;/code&gt; rule, sending the request off to the node well before then. Anything that should go through the proxy already has, long before the IP-based rules further down ever get a chance to run, so DNS never gets an opportunity to leak.&lt;/p&gt;&#xA;&lt;h3 id=&#34;using-fake-ip-to-sidestep-the-transparent-proxy-problem&#34;&gt;Using fake-ip to sidestep the transparent proxy problem&#xA;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;fake-ip&lt;/code&gt; mode solves the DNS leak problem under transparent proxying, and also improves browser page load responsiveness as a side benefit.&lt;/p&gt;&#xA;&lt;p&gt;When using transparent proxying in &lt;code&gt;fake-ip&lt;/code&gt; mode, the moment the target software issues a DNS query, the proxy software doesn&amp;rsquo;t naively go off and query the real IP from a local or remote upstream server. Instead, it immediately returns a fake reserved private-range IP locally on the spot (for example, &lt;code&gt;198.18.0.2&lt;/code&gt;&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;). The target software takes this fake IP at face value and promptly tries to establish a connection using it.&lt;/p&gt;&#xA;&lt;p&gt;On the proxy software&amp;rsquo;s side, all it needs to do is intercept every request heading to one of the fake IPs it&amp;rsquo;s issued. Because the proxy software maintains a detailed mapping table of &amp;ldquo;fake IP to real domain&amp;rdquo; internally, even when the target software sends packets addressed to a fake IP, the proxy software still knows perfectly well what domain you&amp;rsquo;re actually trying to reach. So it extracts the original domain and forwards the network request down the encrypted proxy tunnel to the remote node for processing.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;sequenceDiagram&#xA;    participant App as Target software&lt;br/&gt;(browser, etc.)&#xA;    participant TUN as TUN virtual adapter&#xA;    participant Proxy as Proxy software&lt;br/&gt;(Mihomo)&#xA;    participant Node as Remote proxy node&#xA;    participant Web as Target website&#xA;&#xA;    App-&gt;&gt;TUN: DNS query: what&#39;s the IP for google.com?&#xA;    TUN-&gt;&gt;Proxy: Forwards the DNS query&#xA;    Note over Proxy: Doesn&#39;t issue any external DNS request!&lt;br/&gt;Allocates a fake IP from the 198.18.0.0/15 pool&lt;br/&gt;Records the mapping: 198.18.0.5 → google.com&#xA;    Proxy--&gt;&gt;TUN: Returns fake IP: 198.18.0.5&#xA;    TUN--&gt;&gt;App: DNS response: 198.18.0.5&#xA;&#xA;    App-&gt;&gt;TUN: Connects to 198.18.0.5:443 (HTTPS)&#xA;    TUN-&gt;&gt;Proxy: Forwards the connection request&#xA;    Note over Proxy: Checks mapping table:&lt;br/&gt;198.18.0.5 → google.com&lt;br/&gt;Matches routing rules → PROXY&#xA;    Proxy-&gt;&gt;Node: Encrypted forward: please connect to google.com:443&#xA;    Node-&gt;&gt;Web: Establishes the real connection&#xA;    Web--&gt;&gt;Node: Returns page content&#xA;    Node--&gt;&gt;Proxy: Encrypted return&#xA;    Proxy--&gt;&gt;TUN: Returns content&#xA;    TUN--&gt;&gt;App: Receives the response&#xA;&#xA;    Note over App,Web: No real DNS request leaks to the outside network the whole way through ✓&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This mechanism solves the structural flaw whereby &lt;code&gt;redir-host&lt;/code&gt; mode is bound to expose domain query records to the outside network under transparent proxying. But it&amp;rsquo;s worth pointing out that &lt;code&gt;fake-ip&lt;/code&gt; only bypasses the leak risk that arises when the target software itself issues a DNS query. As mentioned earlier, if the routing rules are poorly configured, the proxy core itself will still proactively issue a genuine DNS resolution upstream, and that will still cause a leak. Given properly configured routing rules, though, &lt;code&gt;fake-ip&lt;/code&gt; mode not only leaves censors with no way to peek at your real destinations, it also cuts out the time the local device would otherwise spend waiting for a real DNS response, substantially reducing Time To First Byte (TTFB&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;) for page loads as a bonus. Two birds, one stone.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tldr-summary&#34;&gt;TL;DR (summary)&#xA;&lt;/h2&gt;&lt;p&gt;A DNS leak itself isn&amp;rsquo;t something to fear. What&amp;rsquo;s actually worth worrying about is being led around by the nose by anxiety-driven marketing without understanding what&amp;rsquo;s really going on. Once you understand the mechanics, the way forward is quite clear:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;System proxy mode&lt;/strong&gt;: follow the principle of &amp;ldquo;domain rules first, IP rules last&amp;rdquo;, tag every IP-type rule with &lt;code&gt;no-resolve&lt;/code&gt;, and sensitive domains will already have been routed away before they ever trigger DNS resolution.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;TUN transparent proxy mode&lt;/strong&gt;: favour &lt;code&gt;fake-ip&lt;/code&gt;, which eliminates DNS query exposure at the root and also gets you lower TTFB as a bonus. If you insist on &lt;code&gt;redir-host&lt;/code&gt;, you must pair it with uncontaminated upstream DNS, and be clear that sniffer can only improve routing accuracy, it can&amp;rsquo;t prevent the DNS request itself from leaking.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t put blind faith in DNS leak test sites&lt;/strong&gt;: the random domains they use aren&amp;rsquo;t on the GFW&amp;rsquo;s blocklist at all, so triggering local resolution for them is normal and harmless behaviour. A row of green ticks isn&amp;rsquo;t a measure of configuration quality.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: Douyin is ByteDance&amp;rsquo;s domestic Chinese short-video app, distinct from TikTok (also ByteDance-owned) which serves international markets. The two are separate apps with separate content and user bases.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: GFWList is a publicly maintained list of domains blocked by China&amp;rsquo;s Great Firewall, commonly used by proxy software to determine which sites need routing through a proxy.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;A list of domains blocked by mainland China&amp;rsquo;s Great Firewall (GFW).&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;SOCKS5 is a network proxy protocol (RFC 1928) that operates at the session layer, supports both TCP and UDP forwarding, and allows the client to pass the domain name straight to the proxy server for resolution, rather than resolving it locally first and then connecting. This is precisely the key feature that makes it better than a plain HTTP proxy (outside of CONNECT tunnel mode) at preventing DNS leaks.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;Translator&amp;rsquo;s note: this is a deliberately absurd, over-the-top hypothetical for comic effect, not a genuine comparison; the author is mocking VPN marketing fearmongering by exaggerating the stakes to an extreme.&#xA;&lt;/content&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;You can think of this as the domain name.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;no-resolve&lt;/code&gt; is a flag appended to the end of an IP-type rule. Its job is to tell the core: when the traffic&amp;rsquo;s target is a domain rather than an IP, skip this rule rather than proactively triggering DNS resolution just to try to match it. But if the traffic itself is a direct connection to an IP (such as a Telegram client connecting straight to a server IP), the rule will still participate in matching normally.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;What we normally call virtual network adapter mode.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:9&#34;&gt;&#xA;&lt;p&gt;Traffic sniffing (Sniffer) refers to a technique where proxy software inspects the protocol characteristics of data packets to recover the target domain. For example, HTTPS connections send the target domain in plaintext during the handshake stage (i.e. the SNI), and HTTP request headers also carry a Host field; proxy software can extract the real target domain from these characteristics.&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:10&#34;&gt;&#xA;&lt;p&gt;Server Name Indication, an extension field in the TLS protocol. Since a single IP might host multiple HTTPS sites, the client needs to state in plaintext during the ClientHello stage of the TLS handshake which domain it wants to reach, so the server can return the correct certificate. This is also why, even over HTTPS, a man-in-the-middle can still see which domain you&amp;rsquo;re visiting (though not the specific path or content).&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:11&#34;&gt;&#xA;&lt;p&gt;By default, Mihomo uses the &lt;code&gt;198.18.0.0/15&lt;/code&gt; address block to allocate fake IPs. This block is reserved by IANA under RFC 2544 specifically for network device benchmarking, and never appears in normal internet routing tables, so it never conflicts with any real public address. The full /15 block provides around 131,072 addresses for mapping.&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:12&#34;&gt;&#xA;&lt;p&gt;Time To First Byte, the time elapsed between the client sending a request and receiving the first byte of the server&amp;rsquo;s response. Under traditional setups, the browser has to wait for DNS resolution to complete (typically 20-120ms) before it can even open a TCP connection; under &lt;code&gt;fake-ip&lt;/code&gt; mode, the DNS response is essentially instant (&amp;lt;1ms), which noticeably shortens overall page load time.&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>First impressions of Terraform IaC</title>
            <link>https://blog.l3zc.com/en/2026/04/iac-with-terraform/</link>
            <pubDate>Thu, 09 Apr 2026 07:13:35 +0000</pubDate>
            <guid>https://blog.l3zc.com/en/2026/04/iac-with-terraform/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/cover_hu_a8e83f97ed61569c.webp&#34; alt=&#34;Featured image of post First impressions of Terraform IaC&#34; /&gt;&lt;p&gt;Terraform is an IaC tool. IaC stands for ‘Infrastructure as Code’: we write our infrastructure as declarative code, then use &lt;code&gt;terraform apply&lt;/code&gt; to deploy it. With the same configuration, you will always get exactly the same infrastructure every time (Nix OS users rejoice).&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-use-terraform&#34;&gt;Why use Terraform&#xA;&lt;/h2&gt;&lt;p&gt;Traditional infrastructure management mostly relies on manual work and the dashboards provided by various cloud vendors, which brings the following pain points:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;Pain point&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: left&#34;&gt;Explanation&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Hard to reproduce&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Configuring things by clicking around in a dashboard makes it easy to miss or misconfigure something, and hard to reproduce later&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Environment drift&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Manual changes gradually cause production and test environments to diverge, so in extreme cases testing is fine but production falls over&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Hard to scale&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;Adding a new environment requires repeating lots of manual steps, which is time-consuming and error-prone&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Hard to audit&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;There is no change history, &lt;del&gt;so when things go wrong it is harder to pass the buck&lt;/del&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;&lt;strong&gt;Hard to collaborate&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: left&#34;&gt;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&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;To solve these problems, the concept of ‘Infrastructure as Code’ &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; was introduced, and Terraform is one of the best-known solutions.&lt;/p&gt;&#xA;&lt;p&gt;Take 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 &lt;code&gt;terraform apply&lt;/code&gt;. In a few minutes, you can recreate exactly the same machines, VPCs, S3, firewall rules, and so on as in your previous account.&lt;/p&gt;&#xA;&lt;p&gt;Another 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 &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;installing-terraform&#34;&gt;Installing Terraform&#xA;&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;winget install Hashicorp.Terraform&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you do not want to use Winget, you can also use Scoop or another package manager, or place the precompiled binary into &lt;code&gt;$PATH&lt;/code&gt; to complete the installation.&lt;/p&gt;&#xA;&lt;p&gt;If you are on Linux, just use the appropriate package manager. If you install it by placing the binary into &lt;code&gt;$PATH&lt;/code&gt;, remember to use &lt;code&gt;sudo chmod +x terraform&lt;/code&gt; to make the file executable.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-basic-terraform-concepts&#34;&gt;Two basic Terraform concepts&#xA;&lt;/h2&gt;&lt;h3 id=&#34;providers&#34;&gt;Provider(s)&#xA;&lt;/h3&gt;&lt;p&gt;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 &lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/browse/providers&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Terraform’s Registry&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image_hu_463ebc576c22c691.webp&#34; alt=&#34;Terraform has a rich Provider(s) ecosystem&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 id=&#34;state-management&#34;&gt;State management&#xA;&lt;/h3&gt;&lt;p&gt;Terraform stores the state information from each infrastructure change operation in a state file. By default, this is saved as the &lt;code&gt;terraform.tfstate&lt;/code&gt; file in the current working directory &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, though you can also configure a different backend such as S3 or Postgres. Every time you run &lt;code&gt;terraform apply&lt;/code&gt;, 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraforms-resource-import-problem&#34;&gt;Terraform’s resource import problem&#xA;&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Time&lt;/th&gt;&#xA;          &lt;th&gt;Version&lt;/th&gt;&#xA;          &lt;th&gt;Progress&lt;/th&gt;&#xA;          &lt;th&gt;Problem&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2014–2022&lt;/td&gt;&#xA;          &lt;td&gt;v0.x–v1.4&lt;/td&gt;&#xA;          &lt;td&gt;Only &lt;code&gt;terraform import&lt;/code&gt;, one resource at a time, and it did not generate any configuration&lt;/td&gt;&#xA;          &lt;td&gt;After importing, you still had to hand-write the HCL &lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2023.06&lt;/td&gt;&#xA;          &lt;td&gt;v1.5&lt;/td&gt;&#xA;          &lt;td&gt;Introduced the &lt;code&gt;import&lt;/code&gt; block and the &lt;code&gt;-generate-config-out&lt;/code&gt; parameter, so it could generate configuration&lt;/td&gt;&#xA;          &lt;td&gt;But you still had to write them one by one, and still had to provide the existing resource IDs yourself&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;2024.01&lt;/td&gt;&#xA;          &lt;td&gt;v1.7&lt;/td&gt;&#xA;          &lt;td&gt;The &lt;code&gt;import&lt;/code&gt; block gained support for &lt;code&gt;for_each&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Batch import at last, but you still had to obtain the IDs yourself&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Second half of 2024&lt;/td&gt;&#xA;          &lt;td&gt;v1.12&lt;/td&gt;&#xA;          &lt;td&gt;Introduced the &lt;code&gt;terraform query&lt;/code&gt; and &lt;code&gt;list&lt;/code&gt; blocks, finally enabling automatic resource discovery&lt;/td&gt;&#xA;          &lt;td&gt;But this feature has to be implemented by each Provider, and many Providers simply have not caught up&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;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 &lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;. 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 &lt;code&gt;terraform query&lt;/code&gt; in &lt;code&gt;v1.12&lt;/code&gt; was really the first time the official tooling took the issue seriously — though as for Provider ecosystem support&amp;hellip; well, it has been rough.&lt;/p&gt;&#xA;&lt;h2 id=&#34;terraform-file-structure&#34;&gt;Terraform file structure&#xA;&lt;/h2&gt;&lt;p&gt;Terraform’s file structure is very simple. When it runs, the main program blindly reads all &lt;code&gt;.tf&lt;/code&gt; 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:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;❯ tree -a -I .git&#xA;.&#xA;├── .editorconfig&#xA;├── .github&#xA;│   ├── dependabot.yml&#xA;│   └── workflows&#xA;│       ├── terraform-apply.yml&#xA;│       ├── terraform-plan.yml&#xA;│       └── your-fork.yml&#xA;├── .gitignore&#xA;├── .terraform.lock.hcl&#xA;├── cf_dns_zones.tf&#xA;├── cf_tunnel.tf&#xA;├── dns_example_com.tf&#xA;├── dns_example_net.tf&#xA;├── dns_example_top.tf&#xA;├── dns_example_cn.tf&#xA;├── main.tf                 # Basic configuration (terraform block)&#xA;├── moved.tf                # Moved resources&#xA;├── provider.tf             # Configuration for each Provider&#xA;├── README.md&#xA;├── rename_resources.ps1&#xA;└── variables.tf            # Custom variables&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;main.tf&lt;/code&gt; is used to store the basic configuration:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  required_providers {&#xA;    cloudflare = {&#xA;      source  = &#34;cloudflare/cloudflare&#34;&#xA;      version = &#34;~&gt; 5&#34;&#xA;    }&#xA;&#xA;    tencentcloud = {&#xA;      source  = &#34;tencentcloudstack/tencentcloud&#34;&#xA;      version = &#34;&gt;= 1.81.43&#34;&#xA;    }&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;provider.tf&lt;/code&gt; is used to store the configuration for each Provider:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;provider &#34;cloudflare&#34; {}&#xA;provider &#34;tencentcloud&#34; {}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;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 &lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt; as an alternative to the &lt;code&gt;api_token&lt;/code&gt; variable.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt; is used to declare custom variables:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;variable &#34;cloudflare_zone_example_com&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.com&#34;&#xA;  type        = string&#xA;}&#xA;&#xA;variable &#34;cloudflare_zone_example_top&#34; {&#xA;  description = &#34;Cloudflare zone ID for example.top&#34;&#xA;  type        = string&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These variables can be passed in through environment variables prefixed with &lt;code&gt;TF_VAR_&lt;/code&gt;. Terraform will also automatically read variables from the &lt;code&gt;terraform.tfvars&lt;/code&gt; file. The main purpose of variables is to be referenced from other configuration files, for example:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;example_cname&#34; {&#xA;  content = &#34;${cloudflare_zero_trust_tunnel_cloudflared.Production_Tunnel.id}.cfargotunnel.com&#34;&#xA;  name    = &#34;example.example.com&#34;&#xA;  proxied = true&#xA;  tags    = []&#xA;  ttl     = 1&#xA;  type    = &#34;CNAME&#34;&#xA;  zone_id = var.cloudflare_zone_id_example_com  # the cloudflare_zone_example_com variable is referenced here&#xA;  settings = {&#xA;    flatten_cname = false&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With these basic settings in place, we can run &lt;code&gt;terraform init&lt;/code&gt; to initialise the Terraform environment and lock dependency versions. After that, the rest is just declaring our resources.&lt;/p&gt;&#xA;&lt;h2 id=&#34;importing-existing-resources-into-terraform&#34;&gt;Importing existing resources into Terraform&#xA;&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;p&gt;At 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 &lt;code&gt;terraform query&lt;/code&gt;, 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.&lt;/p&gt;&#xA;&lt;p&gt;Fortunately, 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 &lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cf-terraforming&lt;/a&gt;, which saves a lot of manual effort. First, install &lt;code&gt;cf-terraforming&lt;/code&gt;. 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 &lt;code&gt;$PATH&lt;/code&gt; and make it executable.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;go install github.com/cloudflare/cf-terraforming/cmd/cf-terraforming@latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The tool is fairly straightforward to use. First, you need the following environment variables:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# If you use an API Token&#xA;export CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&#xA;&#xA;# If you use an API Key&#xA;export CLOUDFLARE_EMAIL=&#39;user@example.com&#39;&#xA;export CLOUDFLARE_API_KEY=&#39;1150bed3f45247b99f7db9696fffa17cbx9&#39;&#xA;&#xA;# Specify the zone ID of the domain to import; this is not needed for account-level resources (such as Cloudflare Tunnel)&#xA;export CLOUDFLARE_ZONE_ID=&#39;81b06ss3228f488fh84e5e993c2dc17&#39;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote class=&#34;alert alert-tip&#34;&gt;&#xA;        &lt;div class=&#34;alert-header&#34;&gt;&#xA;            &lt;span class=&#34;alert-icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; width=&#34;24&#34; height=&#34;24&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;&gt;&lt;polygon points=&#34;13 2 3 14 12 14 11 22 21 10 12 10 13 2&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&#xA;            &lt;span class=&#34;alert-title&#34;&gt;Tip&lt;/span&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;alert-body&#34;&gt;&#xA;            &lt;p&gt;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:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-pwsh&#34;&gt;$env:CLOUDFLARE_API_TOKEN=&#39;Hzsq3Vub-7Y-hSTlAaLH3Jq_YfTUOCcgf22_Fs-j&#39;&lt;/code&gt;&lt;/pre&gt;&#xA;        &lt;/div&gt;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;Usually, you only need to set &lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt; and &lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt;. 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.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-1_hu_15298d4bd366a377.webp&#34; alt=&#34;Grant the permissions required to operate on the resources&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;Right, all the preparation is done. Now we can start generating the configuration files.&lt;/p&gt;&#xA;&lt;p&gt;First, import the domain configuration in the account, namely &lt;code&gt;cloudflare_zone&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_zone&#34; &gt; zone.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This step generates a file called &lt;code&gt;zone.tf&lt;/code&gt; in the current directory, containing content in the following format:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&#xA;&#xA;resource &#34;cloudflare_zone&#34; &#34;REDACTED&#34; {&#xA;  name                = &#34;REDACTED&#34;&#xA;  paused              = false&#xA;  type                = &#34;full&#34;&#xA;  vanity_name_servers = []&#xA;  account = {&#xA;    id   = &#34;REDACTED&#34;&#xA;    name = &#34;REDACTED&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point, the domain resources have been imported, but their internal configuration has not. Next, import the DNS records under the domain:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming generate \&#xA;  --zone $CLOUDFLARE_ZONE_ID \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; &gt;&gt; dns.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This step generates a configuration file called &lt;code&gt;dns.tf&lt;/code&gt; in the current directory, containing content in the following format:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_5deb14xxxxxb629bf123xxxxxxxc8f_0&#34; {&#xA;  content  = &#34;67.24.33.108&#34;&#xA;  name     = &#34;example.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;81c7f2de8dfxxxxxx52629xxxxxxfc&#34;&#xA;  settings = {}&#xA;}&#xA;&#xA;resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89xxxxx0bf9cxxxxxx9a_1&#34; {&#xA;  content  = &#34;35.27.108.33&#34;&#xA;  name     = &#34;terraform.example.com&#34;&#xA;  proxied  = true&#xA;  tags     = []&#xA;  ttl      = 1&#xA;  type     = &#34;A&#34;&#xA;  zone_id  = &#34;8xxxxxx7644e428526xxxxxx&#34;&#xA;  settings = {}&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you need to import multiple domains, just set the &lt;code&gt;CLOUDFLARE_ZONE_ID&lt;/code&gt; environment variable separately each time and rerun the command.&lt;/p&gt;&#xA;&lt;p&gt;The 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 &lt;code&gt;terraform apply&lt;/code&gt; 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 &lt;code&gt;terraform.tfstate&lt;/code&gt; state.&lt;/p&gt;&#xA;&lt;p&gt;Terraform introduced the &lt;code&gt;import&lt;/code&gt; 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 &lt;code&gt;.tf&lt;/code&gt; file containing &lt;code&gt;import&lt;/code&gt; blocks. The next time you run &lt;code&gt;terraform apply&lt;/code&gt;, Terraform will automatically perform the import for you.&lt;/p&gt;&#xA;&lt;p&gt;Generate the &lt;code&gt;import&lt;/code&gt; blocks for &lt;code&gt;cloudflare_zone&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_zone&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Generate the &lt;code&gt;import&lt;/code&gt; blocks for &lt;code&gt;cloudflare_dns_record&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;cf-terraforming import \&#xA;  --resource-type &#34;cloudflare_dns_record&#34; \&#xA;  --modern-import-block \&#xA;  --key $CLOUDFLARE_API_KEY \&#xA;  --zone $CLOUDFLARE_ZONE_ID &gt;&gt; import.tf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This step generates &lt;code&gt;import.tf&lt;/code&gt; in the current directory. It contains the import information needed to tell Terraform which cloud-provider resource ID corresponds to each &lt;code&gt;resource&lt;/code&gt; 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.&lt;/p&gt;&#xA;&lt;p&gt;Right, now let us run &lt;code&gt;terraform plan&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform plan&#xA;cloudflare_dns_record.minio_a: Refreshing state... [id=xxxxxxxxxxx53]&#xA;cloudflare_zero_trust_tunnel_cloudflared_config.raspberrypi: Refreshing state...&#xA;......&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;5deb14c21xxxxxxx20f1c8f&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_89c149exxxxxxxxxxxba13xxxxxa_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;89cxxxxxxxxxxxxxxxxxx09a&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;81xxxxxxxxxxxxxxxxxxxxxfc&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;&#xA;────────────────────────────────────────────────────────────────────────────────────────────────────────&#xA;&#xA;Note: You didn&#39;t use the -out option to save this plan, so Terraform can&#39;t guarantee to take exactly&#xA;these actions if you run &#34;terraform apply&#34; now.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the numbers for Add, Change, and Destroy are all 0, then the import has gone correctly. Just run &lt;code&gt;terraform apply --auto-approve&lt;/code&gt;, and the resources will be imported.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ terraform apply --auto-approve&#xA;cloudflare_dns_record.push_a: Refreshing state... [id=REDACTED]&#xA;&#xA;Terraform will perform the following actions:&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_0 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_0&#34; {&#xA;        content     = &#34;67.24.33.108&#34;&#xA;        created_on  = &#34;2026-04-08T10:18:12Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:18:12Z&#34;&#xA;        name        = &#34;example.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;  # cloudflare_dns_record.terraform_managed_resource_REDACTED_1 will be imported&#xA;    resource &#34;cloudflare_dns_record&#34; &#34;terraform_managed_resource_REDACTED_1&#34; {&#xA;        content     = &#34;35.27.108.33&#34;&#xA;        created_on  = &#34;2026-04-08T10:17:54Z&#34;&#xA;        id          = &#34;REDACTED&#34;&#xA;        meta        = jsonencode({})&#xA;        modified_on = &#34;2026-04-08T10:17:54Z&#34;&#xA;        name        = &#34;terraform.example.com&#34;&#xA;        proxiable   = true&#xA;        proxied     = true&#xA;        settings    = {}&#xA;        tags        = []&#xA;        ttl         = 1&#xA;        type        = &#34;A&#34;&#xA;        zone_id     = &#34;REDACTED&#34;&#xA;    }&#xA;&#xA;Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_1: Import complete [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Importing... [id=REDACTED/REDACTED]&#xA;cloudflare_dns_record.terraform_managed_resource_REDACTED_0: Import complete [id=REDACTED/REDACTED]&#xA;&#xA;Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;state-storage-and-continuous-integration&#34;&gt;State storage and continuous integration&#xA;&lt;/h2&gt;&lt;p&gt;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 &lt;code&gt;terraform.tfstate&lt;/code&gt; live? Nobody wants to painstakingly import state for each environment only to lose it every time they switch.&lt;/p&gt;&#xA;&lt;p&gt;Terraform currently supports the following state backends:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;local&lt;/li&gt;&#xA;&lt;li&gt;remote&lt;/li&gt;&#xA;&lt;li&gt;azurerm&lt;/li&gt;&#xA;&lt;li&gt;consul&lt;/li&gt;&#xA;&lt;li&gt;cos&lt;/li&gt;&#xA;&lt;li&gt;gcs&lt;/li&gt;&#xA;&lt;li&gt;http&lt;/li&gt;&#xA;&lt;li&gt;Kubernetes&lt;/li&gt;&#xA;&lt;li&gt;oci&lt;/li&gt;&#xA;&lt;li&gt;oss&lt;/li&gt;&#xA;&lt;li&gt;pg&lt;/li&gt;&#xA;&lt;li&gt;s3&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If you do not have any special requirements, you can choose &lt;code&gt;s3&lt;/code&gt; as I do. Cloudflare R2 has a free tier, after all, so you might as well use it.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;terraform {&#xA;  backend &#34;s3&#34; {&#xA;    bucket = &#34;terraform&#34;&#xA;    key    = &#34;terraform.tfstate&#34;&#xA;    region = &#34;auto&#34;&#xA;    endpoints = {&#xA;      s3 = &#34;https://REDACTED.r2.cloudflarestorage.com&#34;&#xA;    }&#xA;&#xA;    # R2 does not need this AWS validation&#xA;    skip_credentials_validation = true&#xA;    skip_metadata_api_check     = true&#xA;    skip_region_validation      = true&#xA;    skip_requesting_account_id  = true&#xA;    use_path_style              = true&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the S3 backend, it is recommended to store credentials in the two environment variables &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;export AWS_ACCESS_KEY_ID=&#39;REDACTED&#39;&#xA;export AWS_SECRET_ACCESS_KEY=&#39;REDACTED&#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once configured, run &lt;code&gt;terraform init -migrate-state&lt;/code&gt; and the state will be stored successfully in the cloud. After that, no matter where you edit the configuration or run &lt;code&gt;terraform apply&lt;/code&gt;, you will not need to worry about Terraform state getting out of sync.&lt;/p&gt;&#xA;&lt;p&gt;Next comes the GitHub CI configuration. It is actually very simple: on each &lt;code&gt;git push&lt;/code&gt;, just trigger &lt;code&gt;terraform init&lt;/code&gt;, &lt;code&gt;terraform fmt&lt;/code&gt;, and &lt;code&gt;terraform apply&lt;/code&gt;. Here is my &lt;code&gt;.github/workflows/apply.yml&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: &#34;Terraform Apply&#34;&#xA;&#xA;on:&#xA;  push:&#xA;    branches:&#xA;      - main&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: &#34;${{ secrets.CLOUDFLARE_API_TOKEN }}&#34;&#xA;  AWS_ACCESS_KEY_ID: &#34;${{ secrets.AWS_ACCESS_KEY_ID }}&#34;&#xA;  AWS_SECRET_ACCESS_KEY: &#34;${{ secrets.AWS_SECRET_ACCESS_KEY }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  terraform:&#xA;    name: &#34;Terraform Apply&#34;&#xA;    runs-on: ubuntu-latest&#xA;    permissions:&#xA;      contents: read&#xA;    concurrency:&#xA;      group: terraform-apply&#xA;      cancel-in-progress: false&#xA;    steps:&#xA;      - name: Checkout&#xA;        uses: actions/checkout@v6&#xA;&#xA;      - name: Setup Terraform&#xA;        uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform Init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Apply&#xA;        run: terraform apply -input=false -auto-approve&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For PRs, CI should automatically attach the output of &lt;code&gt;terraform plan&lt;/code&gt; to each PR:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: Terraform Plan&#xA;&#xA;on:&#xA;  pull_request:&#xA;    paths:&#xA;      - &#34;**/*.tf&#34;&#xA;      - &#34;.github/workflows/terraform-plan.yml&#34;&#xA;&#xA;permissions:&#xA;  contents: read&#xA;  pull-requests: write&#xA;&#xA;env:&#xA;  TF_IN_AUTOMATION: &#34;true&#34;&#xA;  CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}&#xA;  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}&#xA;  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}&#xA;  TF_VAR_cloudflare_zone_id_example_com: &#34;${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_COM }}&#34;&#xA;  TF_VAR_cloudflare_zone_id_example_top: ${{ vars.TF_VAR_CLOUDFLARE_ZONE_ID_EXAMPLE_TOP }}&#xA;&#xA;jobs:&#xA;  plan:&#xA;    name: Terraform Plan&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      - uses: actions/checkout@v6&#xA;&#xA;      - uses: hashicorp/setup-terraform@v4&#xA;&#xA;      - name: Terraform fmt&#xA;        id: fmt&#xA;        run: terraform fmt -check -recursive&#xA;        continue-on-error: true&#xA;&#xA;      - name: Terraform Init&#xA;        id: init&#xA;        run: terraform init -input=false&#xA;&#xA;      - name: Terraform Validate&#xA;        id: validate&#xA;        run: terraform validate -no-color&#xA;&#xA;      - name: Terraform Plan&#xA;        id: plan&#xA;        run: terraform plan -input=false -no-color&#xA;        continue-on-error: true&#xA;&#xA;      - name: Post Plan to PR&#xA;        uses: actions/github-script@v8&#xA;        with:&#xA;          github-token: ${{ secrets.GITHUB_TOKEN }}&#xA;          script: |&#xA;            const { data: comments } = await github.rest.issues.listComments({&#xA;              owner: context.repo.owner,&#xA;              repo: context.repo.repo,&#xA;              issue_number: context.issue.number,&#xA;            });&#xA;            const botComment = comments.find(c =&gt;&#xA;              c.user.type === &#39;Bot&#39; &amp;&amp; c.body.includes(&#39;&lt;!-- terraform-plan --&gt;&#39;)&#xA;            );&#xA;&#xA;            const planOutput = `${{ steps.plan.outputs.stdout }}`.substring(0, 65000);&#xA;&#xA;            const body = `&lt;!-- terraform-plan --&gt;&#xA;            #### Terraform Plan&#xA;&#xA;            | Step     | Result                            |&#xA;            | -------- | --------------------------------- |&#xA;            | fmt      | \`${{ steps.fmt.outcome }}\`      |&#xA;            | init     | \`${{ steps.init.outcome }}\`     |&#xA;            | validate | \`${{ steps.validate.outcome }}\` |&#xA;            | plan     | \`${{ steps.plan.outcome }}\`     |&#xA;&#xA;            &lt;details&gt;&lt;summary&gt;Expand Plan details&lt;/summary&gt;&#xA;&#xA;            \`\`\`terraform&#xA;            ${planOutput}&#xA;            \`\`\`&#xA;            &lt;/details&gt;`;&#xA;&#xA;            if (botComment) {&#xA;              await github.rest.issues.updateComment({&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                comment_id: botComment.id,&#xA;                body&#xA;              });&#xA;            } else {&#xA;              await github.rest.issues.createComment({&#xA;                issue_number: context.issue.number,&#xA;                owner: context.repo.owner,&#xA;                repo: context.repo.repo,&#xA;                body&#xA;              });&#xA;            }&#xA;&#xA;      - name: Fail if plan failed&#xA;        if: steps.plan.outcome == &#39;failure&#39;&#xA;        run: exit 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2026/04/iac-with-terraform/image-2_hu_c922b7d4de19a009.webp&#34; alt=&#34;Every PR will have Plan output&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;h2 id=&#34;ongoing-workflow&#34;&gt;Ongoing workflow&#xA;&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Create new resources&lt;/li&gt;&#xA;&lt;li&gt;Modify existing resources&lt;/li&gt;&#xA;&lt;li&gt;Delete resources that are no longer needed&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;There are only two common operations: &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt;. 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.&lt;/p&gt;&#xA;&lt;h3 id=&#34;creating-infrastructure&#34;&gt;Creating infrastructure&#xA;&lt;/h3&gt;&lt;p&gt;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:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  C1[Create a new branch&#xA;such as feat/add-minio-record] --&gt; C2[Add a new resource block]&#xA;  C2 --&gt; C3[terraform fmt + validate]&#xA;  C3 --&gt; C4[terraform plan]&#xA;  C4 --&gt; C5{Only the expected new resources?}&#xA;  C5 -- No --&gt; C6[Fix the configuration and rerun plan]&#xA;  C6 --&gt; C4&#xA;  C5 -- Yes --&gt; C7[Submit PR]&#xA;  C7 --&gt; C8[After merge, CI apply]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ideal &lt;code&gt;plan&lt;/code&gt; output is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;X to add&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to change&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;0 to destroy&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If you only meant to add something but &lt;code&gt;to destroy&lt;/code&gt; 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.&lt;/p&gt;&#xA;&lt;h3 id=&#34;modifying-and-deleting-infrastructure&#34;&gt;Modifying and deleting infrastructure&#xA;&lt;/h3&gt;&lt;p&gt;The process for modifying resources is similar to creating them, but there is one extra step: evaluate whether the change will trigger a rebuild.&lt;/p&gt;&#xA;&lt;p&gt;That is because many Provider fields are &lt;code&gt;ForceNew&lt;/code&gt;. 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.&lt;/p&gt;&#xA;&lt;p&gt;It is best to follow this order:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  M1[Modify .tf] --&gt; M2[terraform plan]&#xA;  M2 --&gt; M3{replace/destroy appears?}&#xA;  M3 -- No --&gt; M7[Confirm the scope of impact]&#xA;  M7 --&gt; M8[terraform apply]&#xA;  M3 -- Yes --&gt; M4[Pause and review the change]&#xA;  M4 --&gt; M5[Add lifecycle protection if needed]&#xA;  M5 --&gt; M6[Schedule a maintenance window]&#xA;  M6 --&gt; M8&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For 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.&lt;/p&gt;&#xA;&lt;p&gt;If you are deleting resources instead (for example, retiring a DNS record or cleaning up an abandoned Tunnel), follow the process below &lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-mermaid&#34;&gt;flowchart TD&#xA;  D1[Confirm the resource is no longer needed; check dependencies in services, monitoring, and scripts] --&gt; D2[Delete the resource block or adjust count/for_each]&#xA;  D2 --&gt; D3[terraform plan]&#xA;  D3 --&gt; D4{Does to destroy match expectations?}&#xA;  D4 -- No --&gt; D5[Revert the change and continue checking dependencies]&#xA;  D5 --&gt; D1&#xA;  D4 -- Yes --&gt; D6[Prepare a rollback plan and choose a low-traffic window]&#xA;  D6 --&gt; D7[PR approved]&#xA;  D7 --&gt; D8[terraform apply]&#xA;  D8 --&gt; D9[Availability check after deletion]&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;suggestions-for-day-to-day-collaboration&#34;&gt;Suggestions for day-to-day collaboration&#xA;&lt;/h3&gt;&lt;ul&gt;&#xA;&lt;li&gt;Put credentials in environment variables or CI secrets; do not write them into &lt;code&gt;.tf&lt;/code&gt; or the repository&lt;/li&gt;&#xA;&lt;li&gt;Enable protection policies for critical resources to prevent accidental deletion&lt;/li&gt;&#xA;&lt;li&gt;Split directories by resource type&lt;/li&gt;&#xA;&lt;li&gt;Run &lt;code&gt;terraform plan&lt;/code&gt; regularly to check for and correct infrastructure drift &lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;, so you do not end up with manual dashboard changes by mistake&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://candinya.com/posts/manage-cloudflare-dns-with-terraform/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Using Terraform to manage DNS records on CloudFlare - Candinya&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/tutorials/automation/github-actions&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Automate Terraform with GitHub Actions&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/files/tfquery&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Query configuration files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developer.hashicorp.com/terraform/language/block/tfquery/list&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;list block reference&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/cloudflare/cf-terraforming&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;cloudflare/cf-terraforming&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Import Cloudflare resources&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Cloudflare Provider - Terraform Registry&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Infrastructure as Code&lt;/a&gt; refers to a method of defining and deploying the required infrastructure using machine-readable configuration files.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Each provider uses different field names and formats in its configuration files, but these can be converted fairly easily with a script.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;One 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 &lt;code&gt;.tfstate&lt;/code&gt; file to a public code repository.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;HashiCorp Configuration Language, a declarative configuration language developed by HashiCorp, designed to balance machine readability with human readability.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;Idempotence 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.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;If you only want Terraform to stop managing a resource, rather than actually destroying it in the cloud, you should use the &lt;code&gt;terraform state rm&lt;/code&gt; command instead of deleting the resource block from the code and then running &lt;code&gt;apply&lt;/code&gt;, otherwise the real resource in the cloud will be destroyed as well.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;Infrastructure 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.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Perpetually Busy</title>
            <link>https://blog.l3zc.com/en/2026/03/busy-for-eternity/</link>
            <pubDate>Fri, 13 Mar 2026 23:14:26 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2026/03/busy-for-eternity/</guid>
            <description>&lt;p&gt;Work, work, until the day we die.&lt;/p&gt;&#xA;&lt;p&gt;Tutoring others during the winter break (see &lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/en/2026/02/buns-soaked-in-human-blood/&#34; &gt;here&lt;/a&gt;), 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&amp;rsquo;t remember what they&amp;rsquo;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&amp;rsquo;t really blame them; if anyone&amp;rsquo;s at fault, it&amp;rsquo;s the educational environment they&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;Initially, 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&amp;rsquo;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&amp;rsquo; extremely poor foundational knowledge, I can frankly say I earn every single penny with a clear conscience. Honestly, it is incredibly hard-earned cash.&lt;/p&gt;&#xA;&lt;p&gt;Getting 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 &amp;ldquo;resting&amp;rdquo; is essentially just swapping to a slightly less taxing activity. If I&amp;rsquo;m exhausted from teaching, I&amp;rsquo;ll go home and unwind with a bit of Vibe Coding; if staring at the screen drives me up the wall, I&amp;rsquo;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 &amp;ldquo;practical perpetual motion machines&amp;rdquo; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The pressure-cooker educational system we’ve been put through since childhood has genuinely warped us. There&amp;rsquo;s a famous quote by a well-known intensive tuition teacher here: &amp;ldquo;How on earth can you sleep at your age?&amp;rdquo; 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&amp;rsquo;s almost pathological. In life, there is no ultimate &amp;lsquo;finish line&amp;rsquo;. 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&amp;rsquo;s spot, a PhD, a stable civil service role, or simply a respectable corporate job. And even after you&amp;rsquo;ve finished your education and entered the workforce, you&amp;rsquo;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&amp;rsquo;m afraid that’s simply impossible.&lt;/p&gt;&#xA;&lt;p&gt;Despite 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 &amp;ldquo;break&amp;rdquo; 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&amp;rsquo;t be changing for a very long time.&lt;/p&gt;&#xA;&lt;p&gt;Looking on the bright side, however, this recent stint of hard graft has meant I&amp;rsquo;ve earned enough to cover my own living expenses for the entire term. I&amp;rsquo;ve even got enough left over to buy my mum a new mobile phone for her birthday. Suddenly, being perpetually busy doesn&amp;rsquo;t seem quite so terrible after all.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;I am borrowing the engineering distinction between ideal and practical current sources here to draw a parallel between an &amp;ldquo;ideal perpetual motion machine&amp;rdquo; 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.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Buns Soaked in Human Blood</title>
            <link>https://blog.l3zc.com/en/2026/02/buns-soaked-in-human-blood/</link>
            <pubDate>Wed, 11 Feb 2026 17:06:57 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2026/02/buns-soaked-in-human-blood/</guid>
            <description>&lt;p&gt;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 &lt;strong&gt;they pay me 200 RMB (approx. £22).&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The fact that the pay is abysmal isn&amp;rsquo;t even the biggest issue. The real problem lies in how they charge the students. They offer two main packages: the &amp;ldquo;Excellence Plan,&amp;rdquo; which costs a staggering 120,000 RMB (£13,000) but includes unlimited one-on-one sessions; and the &amp;ldquo;Contract Class,&amp;rdquo; which costs 70,000 RMB (£7,500), but with a catch—&lt;strong&gt;students must pay an additional 450 RMB (£50) for every one-on-one session.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Yesterday, I arranged a private session with one of the students in the afternoon. My plan was to fly under the agency&amp;rsquo;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&amp;rsquo;t quite catch on. When they asked the coordinator for leave, they explicitly mentioned they were &amp;ldquo;getting extra tutoring from me.&amp;rdquo; The agency immediately &amp;ldquo;benevolently&amp;rdquo; stepped in to help me collect the 450 RMB fee from the parents.&lt;/p&gt;&#xA;&lt;p&gt;It didn&amp;rsquo;t stop there. When it came time to calculate my earnings, the agency classified this as an &amp;ldquo;extra lesson.&amp;rdquo; They claimed I had &amp;ldquo;misused&amp;rdquo; my afternoon lesson-preparation time to teach a student not enrolled in the &amp;ldquo;Excellence Plan&amp;rdquo;. For those three and a half hours of work, they only gave me 120 RMB.&lt;/p&gt;&#xA;&lt;p&gt;To put it simply:&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&lt;strong&gt;Student pays 450 → Agency pockets 330 → I receive 120&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;Naturally, 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 &amp;ldquo;cure&amp;rdquo; 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.&lt;/p&gt;&#xA;&lt;p&gt;In Chinese literature, we call this &amp;ldquo;eating buns soaked in human blood&amp;rdquo;—profiting off the desperation and suffering of others.&lt;/p&gt;&#xA;&lt;p&gt;On 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 &amp;ldquo;flexible&amp;rdquo; 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.&lt;/p&gt;&#xA;&lt;p&gt;That said, this agency had better watch its step. The joke I made during &amp;ldquo;lesson prep&amp;rdquo;—about the teaching assistants forming a temporary union and going on strike for better pay—might just become a self-fulfilling prophecy one day.&lt;/p&gt;&#xA;&lt;p&gt;I can’t help but wonder who the real &amp;ldquo;ruthless capitalists&amp;rdquo; are meant to be. This agency seems to have mastered the art of the blood-soaked business quite effectively.&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>2025 Year in Review</title>
            <link>https://blog.l3zc.com/en/2025/12/2025-end-of-the-year-summary/</link>
            <pubDate>Wed, 31 Dec 2025 19:28:17 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2025/12/2025-end-of-the-year-summary/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/cover_hu_546f5de4e94beb95.webp&#34; alt=&#34;Featured image of post 2025 Year in Review&#34; /&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-grid-the-final-destination-for-electrical-engineers&#34;&gt;The Grid: The Final Destination for Electrical Engineers&#xA;&lt;/h2&gt;&lt;h3 id=&#34;running-ragged-for-a-job&#34;&gt;Running Ragged for a Job&#xA;&lt;/h3&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250920_154353_hu_11efd88f784ccf12.webp&#34; alt=&#34;Attended a job fair&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20251220_153620_hu_7fe2627d8f597cfd.webp&#34; alt=&#34;Hainan Power Grid interview at the Furama Hotel&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;After a full year of suspension, I returned to my studies on schedule to begin my final year of university, officially becoming a &amp;ldquo;fresh graduate&amp;rdquo;. 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&amp;rsquo;t exactly impossible. However, saying it&amp;rsquo;s easy wouldn&amp;rsquo;t be accurate either. given the current climate: slowing economic growth, an uncertain international situation, reduced hiring demand, and the &amp;ldquo;rat race&amp;rdquo; becoming increasingly intense everywhere.&lt;/p&gt;&#xA;&lt;p&gt;In 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 &amp;ldquo;former Power Ministry affiliated university&amp;rdquo; is really only recognised within the power system itself&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. 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 &amp;ldquo;standard universities&amp;rdquo; (non-elite institutions) like ours. But in comparison, at least they still accept our CVs; for some other majors, companies won&amp;rsquo;t even look at them.&lt;/p&gt;&#xA;&lt;h3 id=&#34;written-exams-for-the-two-grid-giants&#34;&gt;Written Exams for the Two Grid Giants&#xA;&lt;/h3&gt;&lt;p&gt;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: &lt;em&gt;Circuit Theory&lt;/em&gt;, &lt;em&gt;Electrical Machines&lt;/em&gt;, &lt;em&gt;Power System Analysis&lt;/em&gt;, &lt;em&gt;Power System Protection&lt;/em&gt;, &lt;em&gt;Power Electronics&lt;/em&gt;, &lt;em&gt;High Voltage Engineering&lt;/em&gt;, &lt;em&gt;Electrical Equipment &amp;amp; Main Systems&lt;/em&gt; (called &amp;ldquo;Electrical Part of Power Plants&amp;rdquo; at our uni), and the &lt;em&gt;Administrative Aptitude Test&lt;/em&gt; (essentially a civil service competency test).&lt;/p&gt;&#xA;&lt;p&gt;The 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 &lt;em&gt;Qiushi&lt;/em&gt; (the Party&amp;rsquo;s&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; theoretical journal) just days ago appeared directly in the exam a few days later. I was honestly speechless.&lt;/p&gt;&#xA;&lt;p&gt;This year&amp;rsquo;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&amp;rsquo;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&amp;rsquo;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&amp;rsquo;s just a corporate recruitment test; outsiders can never guess how they&amp;rsquo;ll set the questions. I did what I had to do, and the final hiring outcome was excellent, which is all that matters.&lt;/p&gt;&#xA;&lt;h3 id=&#34;interviews-interviews-and-more-interviews&#34;&gt;Interviews, Interviews, and More Interviews&#xA;&lt;/h3&gt;&lt;p&gt;In December, I rushed to four interviews. For the CSG Guangxi Power Grid Ltd., the only interview location was Guilin, forcing me to &lt;del&gt;skive off classes&lt;/del&gt; travel there. This resulted in my final trip of the year. Visiting Guilin twice in two years felt quite nice.&lt;/p&gt;&#xA;&lt;p&gt;The State Grid interview made me a bit nervous. To prevent interview questions from leaking to subsequent candidates&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, 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 &lt;em&gt;The Battle at Lake Changjin&lt;/em&gt; 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&amp;rsquo;s a flavour of anxiety you only understand if you&amp;rsquo;ve been there. Looking back, it was actually quite interesting.&lt;/p&gt;&#xA;&lt;p&gt;With 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&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-bittersweet-grid-training&#34;&gt;The Bittersweet Grid Training&#xA;&lt;/h3&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250819_182040_hu_9f035c63414ae82d.webp&#34; alt=&#34;Evening stroll&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250819_184119_hu_f173b6fa7aa0237a.webp&#34; alt=&#34;Clouds encountered by chance&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250819_181921_hu_1055fee83bee50f7.webp&#34; alt=&#34;Nearby high-voltage lines&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/Screenshot_2025-10-10-20-57-03-452_com.tencent.mm_hu_200838b0f8909812.webp&#34; alt=&#34;Grinding questions, doing papers, repeat&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/screenshot_2025-11-27_23-35-58_hu_63f5f0b6afaf6e93.webp&#34; alt=&#34;Corporate culture must be memorised by heart&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;p&gt;The 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.&lt;/p&gt;&#xA;&lt;p&gt;Actually, during that period, I found a rare chance to focus entirely on one thing. Apart from classes, I didn&amp;rsquo;t have to worry about anything else. So, while my body was tired, my &amp;ldquo;mind&amp;rdquo; wasn&amp;rsquo;t actually that weary. Now that I&amp;rsquo;m back at university, having to worry about big and small matters alike, my body isn&amp;rsquo;t as tired, but my &amp;ldquo;mind&amp;rdquo; is far more exhausted than before. Coupled with a recent cold and cough, sleeping 12 hours a day hasn&amp;rsquo;t brought much improvement. I can only wait until everything is sorted, then take leave, go home, and get some proper rest.&lt;/p&gt;&#xA;&lt;h2 id=&#34;retracing-steps&#34;&gt;Retracing Steps&#xA;&lt;/h2&gt;&lt;h3 id=&#34;yongzhou&#34;&gt;Yongzhou&#xA;&lt;/h3&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250122_121431_hu_9139d942ff443734.webp&#34; alt=&#34;Yongzhou&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250122_153223_hu_119bbf370aade398.webp&#34; alt=&#34;Yinlong Computer City—childhood memories&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250122_153407_hu_75f5c2193137f130.webp&#34; alt=&#34;Twenty years and barely changed&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;p&gt;As for Yongzhou itself, it’s still the same; nothing has changed. After all, this isn&amp;rsquo;t an era of economic explosion, so how could a small city like this change much? Stagnation isn&amp;rsquo;t necessarily a tragedy, and change isn&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;h3 id=&#34;hong-kong&#34;&gt;Hong Kong&#xA;&lt;/h3&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/DSCF0187.JPG_hu_80338f753b923972.webp&#34; alt=&#34;Government Headquarters&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/DSCF0189.JPG_hu_cb13da2068e9876.webp&#34; alt=&#34;Central&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250422_112641_hu_1e96cecdbc2d2313.webp&#34; alt=&#34;Hotel exterior&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/DSCF0156.JPG_hu_63e4a420e01095c5.webp&#34; alt=&#34;Alleyway&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/DSCF0169.JPG_hu_49d9f960a8af333c.webp&#34; alt=&#34;Street sign&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/DSCF0142.JPG_hu_3fc15df218b0937c.webp&#34; alt=&#34;Tram&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250424_101442_hu_fb51beb94ea422c5.webp&#34; alt=&#34;Bank of China efficiency&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250423_182408_hu_870bb99e461edf1b.webp&#34; alt=&#34;Blood is thicker than wine (jk)&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250421_174511_hu_b0f44927a1040192.webp&#34; alt=&#34;Hong Kong flat&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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&amp;rsquo;s business trip. Of course, I opened a Bank of China account&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; 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 (&lt;del&gt;or eat &amp;ldquo;two-dish rice&amp;rdquo; takeouts&lt;/del&gt;), experiencing a slice of working-class life.&lt;/p&gt;&#xA;&lt;h3 id=&#34;guilin&#34;&gt;Guilin&#xA;&lt;/h3&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20251224_194924_hu_15f0e9b608e95be5.webp&#34; alt=&#34;Nice lighting on this sign&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20251224_112927_hu_4fd7a7bc2479dd30.webp&#34; alt=&#34;Chunji Roast Goose&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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 &lt;em&gt;Chunji Roast Goose&lt;/em&gt; and &lt;em&gt;Haitian Rice Noodle Rolls&lt;/em&gt;. 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;old-for-new&#34;&gt;Old for New&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250425_155402_hu_64b9ab0f62f7b107.webp&#34; alt=&#34;New computer, yay&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250425_180234_hu_95f6f3aea77b7f19.webp&#34; alt=&#34;Bloody Windows 11&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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 Crucial&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; memory to upgrade it to 32GB. Frankly, there was no issue continuing to use it.&lt;/p&gt;&#xA;&lt;p&gt;However, 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&amp;rsquo;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 card&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;. I&amp;rsquo;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&amp;rsquo;t obscure the virtues. With storage prices skyrocketing and government subsidies tapering off, I doubt we&amp;rsquo;ll see laptops this cheap again anytime soon.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-cat&#34;&gt;My Cat&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250427_125807_hu_5b61bb8c30db752f.webp&#34; alt=&#34;Crawling into any space he finds&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250208_141725_hu_c37c72856d52fb99.webp&#34; alt=&#34;What’s this? A cat head! Pat Pat&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250626_152438_hu_827db49001ba7222.webp&#34; alt=&#34;Living a better life than humans&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;My cat turned one year old this year (probably)&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;. 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:&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/mmexport1745478106719_hu_aa931e8ed6a41965.webp&#34; alt=&#34;M Y  D U V E T ~~&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;While I was away in Hong Kong, this stinky cat actually climbed onto my bed to pee and poop ~~ (probably because my dad didn&amp;rsquo;t scoop the litter)~~. Unforgivable. He had to get the snip!&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/mmexport1749356267417_hu_48575c3a7508956e.webp&#34; alt=&#34;Feels good man&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20250610_105723_hu_d5f83d680ed53460.webp&#34; alt=&#34;That’ll teach you to act up&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;The 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&amp;rsquo;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&amp;rsquo;ve started to love even his flaws. Because without them, he wouldn&amp;rsquo;t be a complete cat.&lt;/p&gt;&#xA;&lt;h2 id=&#34;still-lifting-still-on-meds&#34;&gt;Still Lifting, Still on Meds&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/IMG_20251231_181040_hu_ada4c10cba556476.webp&#34; alt=&#34;Some empty medication boxes&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;I am still taking medication every day. I really don&amp;rsquo;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&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;This period is continuously filled with possibilities and opportunities, but also pressure. The money spent on medicine is a small price to pay, but the &amp;ldquo;shield&amp;rdquo; it provides for emotional stability plays a crucial role.&lt;/p&gt;&#xA;&lt;h2 id=&#34;no-time-for-gaming&#34;&gt;No Time for Gaming&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/steam-2025-recap_hu_9c34df2ccc0aaf15.webp&#34; alt=&#34;My Steam Year in Review&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;As you can tell from &lt;a class=&#34;link&#34; href=&#34;https://s.team/y25/gqhwfdqc?l=schinese&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;my Steam Year in Review&lt;/a&gt;, I really didn&amp;rsquo;t have much time for games this year. &amp;ldquo;Stealing moments of leisure from a busy life&amp;rdquo; was the theme. At the end of the year, I downloaded &lt;em&gt;Delta Force&lt;/em&gt;. sometimes, after Grid training finished at 10 PM, I&amp;rsquo;d go home and play until midnight. I&amp;rsquo;ve clocked 75 hours so far, and it feels pretty good.&lt;/p&gt;&#xA;&lt;p&gt;My &lt;em&gt;osu!&lt;/em&gt; 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, &amp;ldquo;I simply cannot do it&amp;rdquo;. Ideally, mid-year, my feel for the mouse was at its peak—I could snap to anything. I thought, &amp;ldquo;No one can stop me on my road to 5 digit rank&amp;rdquo;&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;, yet here I am, still hovering on the edge of 5 digits. Quite ironic, really.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-online-presence&#34;&gt;My Online Presence&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/12/2025-end-of-the-year-summary/image_hu_1b13d8cf59e9184e.webp&#34; alt=&#34;Blog statistics for this year&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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).&lt;/p&gt;&#xA;&lt;p&gt;My personal override rules on GitHub garnered 194 Stars, and I gained some new Followers.&lt;/p&gt;&#xA;&lt;p&gt;One 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&amp;rsquo; understanding.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-new-year-approaches-wishes-for-myself-and-everyone&#34;&gt;A New Year Approaches: Wishes for Myself and Everyone&#xA;&lt;/h2&gt;&lt;p&gt;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&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;I 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&amp;rsquo;t have to win every step, as long as you know where you&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;As for me, I will continue learning where I should learn, and hit the road when it&amp;rsquo;s time to move. See you next year.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Refers 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.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Communist Party of China, obviously.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;The 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.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;Bank 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.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;Referring to Crucial memory, which recently axed its entire consumer product line.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;I bought a &lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/&#34; &gt;Mechrevo Aurora X Pro&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:7&#34;&gt;&#xA;&lt;p&gt;When I picked him up last year, he was still a &lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2024/12/2024-end-of-the-year-summary-public/#%e5%a4%a9%e4%b8%8a%e4%b8%8d%e4%bc%9a%e6%8e%89%e9%a6%85%e9%a5%bc%e4%bd%86%e4%bc%9a%e6%8e%89%e5%b0%8f%e7%8c%ab&#34; &gt;tiny kitten&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:8&#34;&gt;&#xA;&lt;p&gt;Refers 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&amp;rsquo;s skill level.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>My LLM Confidant, and My Writing in Suspension</title>
            <link>https://blog.l3zc.com/en/2025/09/satisfactory-llm/</link>
            <pubDate>Mon, 08 Sep 2025 23:53:48 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2025/09/satisfactory-llm/</guid>
            <description>&lt;p&gt;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 &amp;ldquo;overtime&amp;rdquo;, 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 &lt;em&gt;osu!&lt;/em&gt; was out of the question; I didn&amp;rsquo;t even have the energy to click through a single chapter of a visual novel.&lt;/p&gt;&#xA;&lt;p&gt;The only viable activity was to engage in small talk with an LLM (Large Language Model).&lt;/p&gt;&#xA;&lt;p&gt;I must confess, although I maintain a blog, I have never managed to articulate my views or philosophies (or perhaps simply my &amp;ldquo;ideas&amp;rdquo;) 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 &amp;ldquo;slice open&amp;rdquo; the tangled mess of my thoughts and depict their internal structure.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; 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&amp;rsquo;t come, I resorted to writing simple technical posts to garner some search engine traffic.&lt;/p&gt;&#xA;&lt;p&gt;The 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.&lt;/p&gt;&#xA;&lt;p&gt;There 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 &amp;ldquo;confidant&amp;rdquo;. 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.&lt;/p&gt;&#xA;&lt;p&gt;LLMs 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&amp;rsquo;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&amp;rsquo;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.&lt;/p&gt;&#xA;&lt;p&gt;When a tool stimulates a &amp;ldquo;confidant&amp;rdquo; 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 &lt;em&gt;New York Times&lt;/em&gt; report &amp;ldquo;&lt;a class=&#34;link&#34; href=&#34;https://archive.is/ALVeI&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Chatbots Can Go Into a Delusional Spiral. Here’s How It Happens.&lt;/a&gt;&amp;rdquo; serves as a prime example. Although OpenAI has begun to address the issue of &amp;ldquo;sycophancy&amp;rdquo; in its models, I must acknowledge that while I can treat the model as a confidant and receive so-called &amp;ldquo;understanding and affirmation&amp;rdquo;, 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.&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; 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.&lt;/p&gt;&#xA;&lt;p&gt;(To Be Continued)&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2024/06/the-worst-way-to-spend-a-day/&#34; &gt;The Worst Way To Spend A Day&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.engadget.com/ai/joint-studies-from-openai-and-mit-found-links-between-loneliness-and-chatgpt-use-193537421.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Joint studies from OpenAI and MIT found links between loneliness and ChatGPT use&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Ditch the Client Applications: Using the Mihomo Core Directly</title>
            <link>https://blog.l3zc.com/en/2025/07/switch-to-pure-mihomo-kernel/</link>
            <pubDate>Thu, 03 Jul 2025 18:30:00 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2025/07/switch-to-pure-mihomo-kernel/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/07/switch-to-pure-mihomo-kernel/cover_hu_1a93b6f26c22bbfe.webp&#34; alt=&#34;Featured image of post Ditch the Client Applications: Using the Mihomo Core Directly&#34; /&gt;&lt;p&gt;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 &lt;em&gt;config overrides&lt;/em&gt;. Every configuration file obtained or subscribed to through a client goes through various override steps—such as changing the &lt;code&gt;mixed-port&lt;/code&gt;, adding &lt;code&gt;sniffer&lt;/code&gt; settings, and so on—before it’s handed over to the Mihomo core for startup.&lt;/p&gt;&#xA;&lt;p&gt;However, 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&amp;rsquo;t even reliably update and tweak config files, it hardly deserves to be called a decent client.&lt;/p&gt;&#xA;&lt;p&gt;Instead 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-you-need-to-know&#34;&gt;What You Need to Know&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;Basic Linux skills&lt;/li&gt;&#xA;&lt;li&gt;Familiarity with CLI editors, like &lt;code&gt;nano&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Have a Substore instance set up (optional)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;installing-the-mihomo-core&#34;&gt;Installing the Mihomo Core&#xA;&lt;/h2&gt;&lt;p&gt;On Debian-based systems, you can install precompiled &lt;code&gt;.deb&lt;/code&gt; packages. For other &lt;code&gt;systemd&lt;/code&gt;-enabled distributions, just download the &lt;a class=&#34;link&#34; href=&#34;https://github.com/MetaCubeX/mihomo/releases&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;compiled binary&lt;/a&gt;, rename it to &lt;code&gt;mihomo&lt;/code&gt;, and place it in &lt;code&gt;/usr/local/bin&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;sudo curl -o /usr/local/bin/mihomo &lt;download_link&gt;&#xA;sudo chmod +x /usr/local/bin/mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, create &lt;code&gt;/etc/systemd/system/mihomo.service&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-systemd&#34;&gt;[Unit]&#xA;Description=mihomo Daemon, Another Clash Kernel.&#xA;After=network.target NetworkManager.service systemd-networkd.service iwd.service&#xA;&#xA;[Service]&#xA;Type=simple&#xA;LimitNPROC=500&#xA;LimitNOFILE=1000000&#xA;CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE&#xA;Restart=always&#xA;ExecStartPre=/usr/bin/sleep 1s&#xA;ExecStart=/usr/local/bin/mihomo -d /etc/mihomo&#xA;ExecReload=/bin/kill -HUP $MAINPID&#xA;&#xA;[Install]&#xA;WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run &lt;code&gt;systemctl daemon-reload&lt;/code&gt; to refresh &lt;code&gt;systemd&lt;/code&gt;. Since there’s no config file yet, you can’t actually start the core, but you can enable it to start on boot using &lt;code&gt;systemctl enable mihomo&lt;/code&gt;—ready for when your config is set up.&lt;/p&gt;&#xA;&lt;h2 id=&#34;configuration-files&#34;&gt;Configuration Files&#xA;&lt;/h2&gt;&lt;p&gt;When starting, the core reads &lt;code&gt;/etc/mihomo/config.yaml&lt;/code&gt;. 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 &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For subscription management, I’m currently using Substore. I’ve previously shared a &lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/03/clash-subscription-convert/&#34; &gt;Quickstart Guide to Substore Subscription Management&lt;/a&gt; if you want to refer to that for custom subscription workflows. To support a pure-core setup, my Substore &lt;a class=&#34;link&#34; href=&#34;https://github.com/powerfullz/override-rules/blob/main/convert.js&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;custom override script&lt;/a&gt; now includes a &lt;code&gt;full&lt;/code&gt; parameter, generating a standalone config file with all necessary ports, unified delay, external-controller settings, and more—ready to use out of the box.&lt;/p&gt;&#xA;&lt;p&gt;Once you’ve set up Substore, just download your config file and start the core:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;curl -o /etc/mihomo/config.yaml &lt;your-config-link&gt;&#xA;systemctl start mihomo&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;custom-override-rules&#34;&gt;Custom Override Rules&#xA;&lt;/h3&gt;&lt;p&gt;If my example config doesn’t work for your needs, that’s no problem—just tweak or add any overrides you like.&lt;/p&gt;&#xA;&lt;p&gt;Over 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.&lt;/p&gt;&#xA;&lt;p&gt;The good thing is that Substore supports chaining multiple override scripts. All you need to do is add your custom script during config generation.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;function main(config) {&#xA;  config[&#34;rules&#34;].unshift(&#34;DOMAIN-SUFFIX,xxx,DIRECT&#34;)&#xA;  return config&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note: When overriding rules, always use &lt;code&gt;.unshift()&lt;/code&gt; to add to the top of the list—don’t use &lt;code&gt;.push()&lt;/code&gt; to add at the end, or they’ll never match (since anything after &lt;code&gt;MATCH&lt;/code&gt; is ignored).&lt;/p&gt;&#xA;&lt;h3 id=&#34;building-your-own-config-from-scratch&#34;&gt;Building Your Own Config From Scratch&#xA;&lt;/h3&gt;&lt;p&gt;Don’t like my override rules, or prefer not to use Substore at all? No problem! Just refer to the &lt;a class=&#34;link&#34; href=&#34;https://wiki.metacubex.one/config/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Mihomo Docs&lt;/a&gt; and build your own config from the ground up:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;mode: rule&#xA;mixed-port: 7890&#xA;redir-port: 7892&#xA;tproxy-port: 7893&#xA;allow-lan: true&#xA;log-level: info&#xA;ipv6: true&#xA;external-controller: 127.0.0.1:8000&#xA;# secret: yoursecret&#xA;unified-delay: true&#xA;routing-mark: 7894&#xA;tcp-concurrent: true&#xA;disable-keep-alive: true # Recommended when proxying mobile devices to prevent excessive standby drain&#xA;&#xA;dns:&#xA;  # Your DNS configuration&#xA;&#xA;sniffer:&#xA;  # Your domain sniffing config&#xA;&#xA;geodata-mode: true&#xA;geox-url:&#xA;  # Custom GeoData file URL&#xA;&#xA;proxy-providers:&#xA;  # Your proxy subscriptions&#xA;&#xA;rule-providers:&#xA;  # External routing rules&#xA;&#xA;rules:&#xA;  # Proxy rules&#xA;&#xA;proxy-groups:&#xA;  # Custom proxy groups&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;control-panel--dashboard&#34;&gt;Control Panel / Dashboard&#xA;&lt;/h2&gt;&lt;p&gt;Choose whichever dashboard you like. For example, I ran into some odd issues with Mihomo&amp;rsquo;s built-in &lt;code&gt;external-ui&lt;/code&gt;. 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).&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ mkdir zashboard &amp;&amp; cd zashboard&#xA;$ nvim compose.yml&#xA;$ docker compose up -d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;compose.yml&lt;/code&gt; example:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;services:&#xA;  zashboard:&#xA;    image: ghcr.io/zephyruso/zashboard:latest&#xA;    ports:&#xA;      - &#34;8899:80&#34;&#xA;    restart: &#34;unless-stopped&#34;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;automatic-maintenance&#34;&gt;Automatic Maintenance&#xA;&lt;/h2&gt;&lt;p&gt;With the core running, how do you handle auto-updates for your subscription configs?&lt;/p&gt;&#xA;&lt;p&gt;Simple—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 &lt;code&gt;/etc/mihomo/auto_update.sh&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;#!/bin/bash&#xA;&#xA;# === Config Info ===&#xA;CONFIG_URL=&#34;&#34;&#xA;CONFIG_PATH=&#34;/etc/mihomo/config.yaml&#34;&#xA;BACKUP_DIR=&#34;/etc/mihomo&#34;&#xA;BACKUP_PREFIX=&#34;config.yaml&#34;&#xA;MAX_BACKUPS=7&#xA;TMP_PATH=&#34;/tmp/config.yaml.tmp&#34;&#xA;LOG_FILE=&#34;/var/log/mihomo_update.log&#34;&#xA;&#xA;# === Logging ===&#xA;log() {&#xA;    echo &#34;$(date &#39;+%F %T&#39;) $1&#34; | tee -a &#34;$LOG_FILE&#34;&#xA;}&#xA;&#xA;# === Backup existing config and clean old backups ===&#xA;backup_config() {&#xA;    if [ -f &#34;$CONFIG_PATH&#34; ]; then&#xA;        backup_file=&#34;$BACKUP_DIR/${BACKUP_PREFIX}.$(date &#39;+%Y%m%d_%H%M%S&#39;).bak&#34;&#xA;        cp &#34;$CONFIG_PATH&#34; &#34;$backup_file&#34;&#xA;        log &#34;Config backed up to $backup_file&#34;&#xA;        # Retain only the latest $MAX_BACKUPS backups&#xA;        old_backups=$(ls -1t $BACKUP_DIR/${BACKUP_PREFIX}.*.bak 2&gt;/dev/null | tail -n +$(($MAX_BACKUPS+1)))&#xA;        for f in $old_backups; do&#xA;            rm -f &#34;$f&#34; &amp;&amp; log &#34;Deleted old backup $f&#34;&#xA;        done&#xA;    else&#xA;        log &#34;No existing config found, skipping backup&#34;&#xA;    fi&#xA;}&#xA;&#xA;# === Download new config ===&#xA;download_config() {&#xA;    log &#34;Downloading new config...&#34;&#xA;    curl -fsSL -o &#34;$TMP_PATH&#34; &#34;$CONFIG_URL&#34;&#xA;    if [ $? -ne 0 ]; then&#xA;        log &#34;Download failed—check network or URL&#34;&#xA;        return 1&#xA;    fi&#xA;    # Basic validation: check file size&#xA;    if [ ! -s &#34;$TMP_PATH&#34; ]; then&#xA;        log &#34;Config file is empty—update aborted&#34;&#xA;        return 2&#xA;    fi&#xA;    log &#34;Config downloaded&#34;&#xA;    return 0&#xA;}&#xA;&#xA;# === Update config file ===&#xA;replace_config() {&#xA;    mv &#34;$TMP_PATH&#34; &#34;$CONFIG_PATH&#34;&#xA;    log &#34;Config updated&#34;&#xA;}&#xA;&#xA;# === Restart Mihomo service ===&#xA;restart_service() {&#xA;    systemctl restart mihomo&#xA;    if [ $? -eq 0 ]; then&#xA;        log &#34;Mihomo restarted&#34;&#xA;    else&#xA;        log &#34;Failed to restart Mihomo—please check manually&#34;&#xA;    fi&#xA;}&#xA;&#xA;main() {&#xA;    backup_config&#xA;&#xA;    download_config&#xA;    DL_STATUS=$?&#xA;    if [ &#34;$DL_STATUS&#34; -ne 0 ]; then&#xA;        log &#34;Aborted: config not updated, keeping old config&#34;&#xA;        exit 1&#xA;    fi&#xA;&#xA;    replace_config&#xA;&#xA;    restart_service&#xA;&#xA;    log &#34;=== Update complete ===&#34;&#xA;}&#xA;&#xA;main &#34;$@&#34;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use &lt;code&gt;crontab -e&lt;/code&gt; to edit your crontab and schedule auto-updates at 3am daily:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-crontab&#34;&gt;0 3 * * * /etc/mihomo/update_config.sh&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;firewall-configuration&#34;&gt;Firewall Configuration&#xA;&lt;/h2&gt;&lt;p&gt;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 “&lt;a class=&#34;link&#34; href=&#34;https://blog.l3zc.com/2025/04/tailscale-setup-recap/#%E5%9C%A8-exit-node-%E5%8A%AB%E6%8C%81%E6%B5%81%E9%87%8F&#34; &gt;From Beginner to Advanced: Tailscale + ShellCrash for Remote Networking and Bypassing Internet Censorship&lt;/a&gt;”. Here, I’ll just summarise the practical steps.&lt;/p&gt;&#xA;&lt;p&gt;For my setup, I want all traffic from the &lt;code&gt;tailscale0&lt;/code&gt; interface to be transparently proxied by Mihomo. If all you need is TCP interception, &lt;code&gt;iptables&lt;/code&gt; REDIRECT is sufficient; but for UDP, QUIC, etc., you’ll need TPROXY. &lt;strong&gt;Don’t forget IPv6!&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Here’s my firewall config:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# Create custom chain&#xA;iptables -t mangle -N MIHOMO&#xA;&#xA;# Exclude local traffic as needed&#xA;iptables -t mangle -A MIHOMO -d 127.0.0.1/8 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 100.64.0.0/10 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 192.168.1.0/24 -j RETURN&#xA;iptables -t mangle -A MIHOMO -d 172.17.0.0/16 -j RETURN&#xA;&#xA;# Mark TCP and UDP for proxy&#xA;iptables -t mangle -A MIHOMO -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;iptables -t mangle -A MIHOMO -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# Hook into the interface&#xA;iptables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO&#xA;&#xA;# Routing table&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip rule add fwmark 233 lookup mihomo&#xA;ip route add local 0.0.0.0/0 dev lo table mihomo&#xA;&#xA;# IPv6&#xA;# Create chain&#xA;ip6tables -t mangle -N MIHOMO6&#xA;&#xA;# Skip local addresses&#xA;ip6tables -t mangle -A MIHOMO6 -d ::1/128 -j RETURN&#xA;ip6tables -t mangle -A MIHOMO6 -d fd7a:115c:a1e0::/48 -j RETURN&#xA;&#xA;# Mark TCP/UDP&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p tcp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;ip6tables -t mangle -A MIHOMO6 -i tailscale0 -p udp -j TPROXY --on-port 7893 --tproxy-mark 233&#xA;&#xA;# Interface hook&#xA;ip6tables -t mangle -A PREROUTING -i tailscale0 -j MIHOMO6&#xA;&#xA;# Routing table&#xA;echo &#34;233 mihomo&#34; | tee -a /etc/iproute2/rt_tables&#xA;ip -6 rule add fwmark 233 lookup mihomo&#xA;ip -6 route add local ::/0 dev lo table mihomo&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most 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.&lt;/p&gt;&#xA;&lt;h3 id=&#34;how-do-i-configure-local-proxying&#34;&gt;How Do I Configure Local Proxying?&#xA;&lt;/h3&gt;&lt;p&gt;Most proxy clients, such as the default for ShellCrash, use REDIRECT as standard, which is usually sufficient for most use cases. Example for REDIRECT:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;# IPv4, intercept eth0&#xA;iptables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&#xA;&#xA;# IPv6, intercept eth0&#xA;ip6tables -t nat -A PREROUTING -i eth0 -p tcp -j REDIRECT --to-ports 7892&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, 7892 should match the &lt;code&gt;redir-port&lt;/code&gt; in your Mihomo config, and &lt;code&gt;eth0&lt;/code&gt; is the interface you want to intercept. Compared to TPROXY, this is remarkably straightforward.&lt;/p&gt;&#xA;&lt;p&gt;Personally, I dislike forcing all local traffic through the firewall proxy route. Instead, I prefer to use environment variables, &lt;code&gt;proxychains&lt;/code&gt;, 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&amp;rsquo;s &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; and specify the proxy endpoints, rather than intercepting all network traffic at the interface level:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &#34;proxies&#34;: {&#xA;    &#34;http-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;https-proxy&#34;: &#34;http://127.0.0.1:7890&#34;,&#xA;    &#34;no-proxy&#34;: &#34;127.0.0.0/8&#34;&#xA;  }&#xA;}&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;why-go-to-all-this-trouble-isnt-using-a-gui-client-easier&#34;&gt;Why Go to All This Trouble? Isn’t Using a GUI Client Easier?&#xA;&lt;/h2&gt;&lt;p&gt;Don’t ask. Some of us just prefer living in the terminal void.&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>The World Is More Foolish Than You Think: A Brief Look at BNPL</title>
            <link>https://blog.l3zc.com/en/2025/06/on-buy-now-pay-later/</link>
            <pubDate>Thu, 05 Jun 2025 19:54:56 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2025/06/on-buy-now-pay-later/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/06/on-buy-now-pay-later/mobile-payments-1920x720_hu_4cbcee30d76215b2.webp&#34; alt=&#34;Featured image of post The World Is More Foolish Than You Think: A Brief Look at BNPL&#34; /&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;p&gt;As 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.&lt;/p&gt;&#xA;&lt;p&gt;But 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.&lt;/p&gt;&#xA;&lt;p&gt;To 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.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;However, there are three absolutely critical prerequisites:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Pay on time, every time—no excuses;&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Never fall into habitual overspending;&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Maintain at least basic financial literacy and management skills.&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Contrast this with what the headlines are pointing out—&lt;strong&gt;“Americans using BNPL for groceries.”&lt;/strong&gt; Skimming the survey data, I saw that &lt;strong&gt;one third of BNPL users have missed at least one repayment?? Some even defaulted on takeaway orders??&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The ideal is promising, but reality bites hard. Data shows that &lt;strong&gt;the number of people with poor financial self-management is greater than I had imagined.&lt;/strong&gt; 46% of BNPL users already carry existing credit card debt, and among them, 28% are aged just 18-24&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. This demographic typically has weaker credit, with 63% juggling loans from multiple BNPL platforms simultaneously&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;—a glaring sign of questionable spending habits.&lt;/p&gt;&#xA;&lt;p&gt;What’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.&lt;/p&gt;&#xA;&lt;p&gt;Inevitably, 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.&lt;/p&gt;&#xA;&lt;p&gt;To 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.&lt;/p&gt;&#xA;&lt;p&gt;Still, 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.&lt;/p&gt;&#xA;&lt;p&gt;The 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.&lt;/p&gt;&#xA;&lt;p&gt;A 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.&lt;/p&gt;&#xA;&lt;p&gt;So 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.&lt;/p&gt;&#xA;&lt;p&gt;In 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.&lt;/p&gt;&#xA;&lt;p&gt;One last piece of advice: borrowed money always needs to be repaid; don’t make needless offerings to financial middlemen.&lt;/p&gt;&#xA;&lt;p&gt;Alas, all told—the world really is more foolish than I had ever imagined.&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Source: &lt;a class=&#34;link&#34; href=&#34;https://archive.is/T3vGo&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;CFPB Research Reveals Heavy Buy Now, Pay Later Use Among Borrowers with High Credit Balances and Multiple Pay-in-Four Loans&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Source: &lt;a class=&#34;link&#34; href=&#34;https://archive.is/6HVCl&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;Buy now, pay later users pile on debt, CFPB finds&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;Cover image source: &lt;a class=&#34;link&#34; href=&#34;https://www.visa.com.tw/pay-with-visa/featured-technologies/mobile-payments.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;https://www.visa.com.tw/pay-with-visa/featured-technologies/mobile-payments.html&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
        </item><item>
            <title>Quick Review of the MECHREVO Aurora X Pro</title>
            <link>https://blog.l3zc.com/en/2025/05/new-laptop-briefing/</link>
            <pubDate>Sun, 04 May 2025 23:18:43 +0800</pubDate>
            <guid>https://blog.l3zc.com/en/2025/05/new-laptop-briefing/</guid>
            <description>&lt;img src=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/DSCF0267_hu_9e18f49bd8ce081.webp&#34; alt=&#34;Featured image of post Quick Review of the MECHREVO Aurora X Pro&#34; /&gt;&lt;h2 id=&#34;disappointing-right-out-of-the-box&#34;&gt;Disappointing Right Out of the Box&#xA;&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;specs-and-price&#34;&gt;Specs and Price&#xA;&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Screen&lt;/strong&gt;: 2560x1600@300Hz, 100% sRGB colour gamut, with decent colour accuracy. When playing &lt;em&gt;Cities: Skylines 2&lt;/em&gt;, the red of the brake lights in traffic jams is even more vivid than in real life.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Keyboard&lt;/strong&gt;: The key travel is rather short and the feel is average, but since I mostly use an external keyboard, I don’t really mind.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Wi-Fi Card&lt;/strong&gt;: 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.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SSD&lt;/strong&gt;: Comes with a 1TB Zhiti drive, which is average; the 2TB Crucial I added myself is the main workhorse.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Battery&lt;/strong&gt;: The 80Wh battery is fine for emergencies on a gaming laptop. I usually keep it in workstation mode to prolong battery life.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Discrete GPU Direct Connection&lt;/strong&gt;: Supports hot switching, which is great—no need to reboot to change modes.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;BIOS&lt;/strong&gt;: The AMI BIOS has a rather weird GUI, which is still much better than my previous Hasee Laptop.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;user-experience&#34;&gt;User Experience&#xA;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;Cities: Skylines 2&lt;/em&gt;—even with a population of 120,000, simulation speed could still be kept at 3x, though the fans were roaring by then. &lt;em&gt;Cyberpunk 2077&lt;/em&gt; runs with ray tracing on Ultra, and &lt;em&gt;Forza Horizon 5&lt;/em&gt; 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.&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/image_hu_8774b59db0175e9a.webp&#34; alt=&#34;Simulation speed in Cities: Skylines 2&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/image-1_hu_47a3feef604ad189.webp&#34; alt=&#34;Honestly, I can’t feel any difference&#34; /&gt;&#xA; &#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/DSCF0267_hu_9e18f49bd8ce081.webp&#34; alt=&#34;It just sits there, like a mute brick&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;The 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.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-meaning-of-tools&#34;&gt;The Meaning of Tools&#xA;&lt;/h2&gt;&lt;p&gt;&#xA;&lt;img src=&#34;https://blog.l3zc.com/2025/05/new-laptop-briefing/DSCF0273_hu_e62683739afe024c.webp&#34; alt=&#34;Desktop after switching to the new laptop&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;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.&lt;/p&gt;&#xA;&lt;p&gt;Perhaps 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.&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
