<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <follow_challenge>
    <feedId>57262945444554752</feedId>
    <userId>55082073592910848</userId>
  </follow_challenge>
  <title>Ken&#39;s Blog</title>
  
  <subtitle>折腾，记录，分享</subtitle>
  <link href="https://blog.kenxu.top/atom.xml" rel="self"/>
  
  <link href="https://blog.kenxu.top/"/>
  <updated>2025-11-27T17:25:55.126Z</updated>
  <id>https://blog.kenxu.top/</id>
  
  <author>
    <name>Ken</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>一般Linux服务器部署及优化指北</title>
    <link href="https://blog.kenxu.top/post/linux-server-optimization/"/>
    <id>https://blog.kenxu.top/post/linux-server-optimization/</id>
    <published>2025-11-27T17:20:00.000Z</published>
    <updated>2025-11-27T17:25:55.126Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><p>你拿到了一台Linux服务器，可能是从VPS厂商购入的，可能是你自己搭建的Homelab，<del>也有可能是某个好心的同学送你的</del>，你对Linux可能有一定了解，但你可能会感觉到一丝不安，觉得在对这台服务器大展身手之前，要先对系统做些“优化”。<br>其实与Windows不同，大部分Linux发行版本身在作为服务器使用的时候，已经基本上能做到“开箱即用”了。相比Windows到手后需要关闭一堆东西再打开一堆东西的操作，我认为对Linux的调优主要是安全性和功能性上的。当然，Linux系统也为我们提供了十分丰富的定制化选项，你也可以根据自己的使用场景定制化自己的系统。<br>本文涉及到的优化并不会局限于某一使用场景，而且与网上大部分“一键优化”脚本不同，本文除了提供一系列优化指南之外，还会尽可能解释这样做的理由，同时，还会强调一些有所变化&#x2F;不再适用的配置，因此如果你曾经配置过Linux，也可以来读一读。<br>如果你有其他的优化妙招，欢迎在评论区分享。</p><blockquote><p>写到一半发现<del>似乎有点太啰嗦了</del>本篇文章似乎带有一定科普性质，因此你可能不需要Linux基础也能读懂本文。  </p></blockquote><h1 id="系统"><a href="#系统" class="headerlink" title="系统"></a>系统</h1><h2 id="Debian-Ubuntu-Other"><a href="#Debian-Ubuntu-Other" class="headerlink" title="Debian ? Ubuntu ? Other ?"></a>Debian ? Ubuntu ? Other ?</h2><p>完成付款&#x2F;组装之后，你大概率首先要从一大堆花花绿绿的Linux发行版中挑一个作为你的服务器系统。一般而言你有两类选择：Red Hat系和Debian系。其中Red Hat系包含了商业化版本RHEL和社区化版本CentOS，而Debian系则有Debian和基于Debian开发的Ubuntu。<br>先说Red Hat系，其中的CentOS在相当长的一段时间内曾经是十分热门的选择，因为其与商业化的上游发行版本RHEL完全兼容，并且由社区驱动，开源且免费，这意味着使用CentOS可以在不需要向Red Hat付费的情况下，享受到RHEL的稳定与丰富的软件包。正因如此，国内外部分大厂也一度采用CentOS。但是在2020年年末，Red Hat宣布对CentOS进行重大调整，其单方面终止了对CentOS的开发并转而支持定位为RHEL上游发行版的CentOS Stream。这样一来CentOS的所有优势都不复存在了，因此在CentOS 7在2024年终止支持后，曾经红极一时的CentOS逐渐式微。<br>由于迁移问题，如今依然有部分开发者和小厂在使用着CentOS，这当然是不推荐的，毕竟这款操作系统已经EOL，随时可能有安全漏洞。对于RHEL，虽然其推出了针对个人开发者非商业化使用的<a href="https://developers.redhat.com/articles/faqs-no-cost-red-hat-enterprise-linux">免费许可证</a>，但我认为完全没有必要使用，毕竟接下来要介绍的Debian系在生态和稳定性上都与RHEL几乎无异，为什么大费周章地去注册并获取密钥呢。<br>说到Debian系，恐怕最出名的发行版就是Ubuntu了，笔者疫情期间首次尝试Linux的时候，安装的第一个发行版就是Ubuntu，也在自家的旧电脑上安装过Ubuntu Server作为Homelab。但我目前完全不推荐Ubuntu，原因很简单，面对巨大的使用量，Ubuntu背后的商业公司——Canonical，开始动起了歪心思，从Ubuntu 20.04开始，它们：  </p><ul><li>在SSH登录后打印的<code>motd</code>信息以及Debian系通用的包管理器<code>apt</code>中，加入了Ubuntu Pro的广告<br><img src="/post/linux-server-optimization/1.png" alt="motd中的广告">  </li><li>强行推广自行开发的软件包格式<code>snap</code>，与使用deb打包的软件不同，<code>snap</code>程序会将所需要的所有依赖打包在一起，这使得其体积巨大且启动速度极慢。并且在Ubuntu Desktop上，Canonical已经将部分软件从原先的deb格式换为了snap包。</li><li>更让人难以接受的是，在倡导自由开放的Linux中，Canonical为用户卸载snap设置了重重阻碍。在通过<code>apt</code>卸载snap实用程序后，在不经过特别配置的情况下，使用<code>apt update</code>指令的时候会再次将<code>snap</code>安装回来。</li></ul><p>与Ubuntu正相反，其上游发行版——Debian的开发完全由社区驱动，系统镜像中没有商业化的部分，而且占用的资源更少，又兼具稳定性和生态丰富度。<br><img src="/post/linux-server-optimization/2.png" alt="全新安装的Debian一般只会占用200MB左右内存"><br>总而言之，其简洁，快速，稳定的特性，使得我在包括Homelab在内的所有服务器上全部安装了Debian操作系统——当然，我也推荐你使用Debian，尤其是在你曾经使用过Ubuntu，对Debian系已经有一定了解的情况下，如果不是的话，就让Debian成为你第一个使用的Linux发行版吧！<br>本文也将使用最新的Debian 13系统作为示例，不过除了换源一节之外，其他的操作都是通用的，大可放心。</p><h2 id="DD"><a href="#DD" class="headerlink" title="DD ?"></a>DD ?</h2><p>你可能会在各大论坛见过各式各样的“DD脚本”，这个名字来源于Linux系统自带的<code>dd</code>指令，其可以对文件进行读取，写入和转换等操作，由于Linux“一切皆文件”的设计理念，<code>dd</code>也可以对块设备进行操作，例如删除磁盘分区或是刷写镜像。而“DD脚本”做的事与重装类似，其会从官方镜像源下载对应的镜像，使用<code>dd</code>指令刷写磁盘，并配置用户名密码等信息。<br>许多优化系统的教程一上来就会推荐你去dd（也就是重装）一份“纯净”的系统，这样真的有必要吗？笔者认为，如果你购入的VPS本身未提供最新版本的Linux发行版镜像（例如Debian 13或Ubuntu 24.04），在安装完厂商提供的系统后，确实可以考虑使用这些脚本重装为最新版本的系统，以获取更新的软件包和更长时间的系统更新。但如果厂商已经提供了最新版本的系统镜像，再使用这些脚本重装系统就显得没有那么必要了。对于各大云厂商的机器，则<strong>完全不推荐使用这些脚本重装</strong>，因为大厂提供的镜像很可能会包含一些优化（例如会将镜像源替换为内网地址以加速访问并节省出入站流量），使用脚本重装可能会导致一些问题。即使在部分情况下确要重装系统，也建议通过厂商提供的控制台进行。<br><img src="/post/linux-server-optimization/3.png" alt="Azure的Ubuntu镜像默认源地址为厂商内网地址"></p><h1 id="换源"><a href="#换源" class="headerlink" title="换源"></a>换源</h1><p>无论是<code>apt</code>，<code>dnf</code>，还是<code>pip</code>，<code>npm</code>，在中国大陆使用任何包管理器，除非你能容忍&lt;100kbps的下载速度以及时不时的阻断，首先应该考虑的一件事就是——换源。如果你的VPS来自于各大云厂商或位于境外，那你可以跳过这一步。此外，如果你的VPS来自于云厂商，那你大概率也可以跳过这一步，原因如上所述，厂商应该已经帮你换好源了。<br>对于Debian系的系统，长久以来，系统软件源的配置文件位于<code>/etc/apt/sources.list</code>中，但自从Debian 11开始，Debian官方引入了一种更易读的格式：DEB 822，这种格式在Debian 13中成为了推荐格式，官方亦表示原先的<code>sources.list</code>格式将会在将来的版本中废弃，所以建议在换源的时候使用DEB 822格式。<br>关于镜像源的选择，最有名的应该就是<a href="https://mirrors.tuna.tsinghua.edu.cn/">清华大学TUNA协会提供的镜像</a>了，无论是服务质量还是源的丰富度上TUNA镜像都是遥遥领先，不过可能是用的人太多的原因，TUNA的镜像时常不能跑满笔者家中的带宽。其实镜像源的选择还有很多，例如我同样比较喜欢的<a href="https://mirrors.ustc.edu.cn/">中科大镜像</a>。对于南方的朋友，我更推荐<a href="https://mirrors.sustech.edu.cn/">南方科技大学镜像站</a>，虽然源的数量较少，但延迟更低，速度更快。<br>选好了镜像源，接下来就是写入文件了，根据最新的DEB 822格式规范，系统软件源的配置应该位于<code>/etc/apt/sources.list.d/debian.sources</code>，以南科大的镜像源为例，用你喜欢的文本编辑器打开这个文件，向其中写入以下内容：  </p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs txt">Types: deb<br>URIs: https://mirrors.sustech.edu.cn/debian/<br>Suites: trixie trixie-updates<br>Components: main contrib non-free non-free-firmware<br>Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg<br><br>Types: deb<br>URIs: https://mirrors.sustech.edu.cn/debian-security/<br>Suites: trixie-security<br>Components: main contrib non-free non-free-firmware<br>Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg<br></code></pre></td></tr></table></figure><div class="note note-info">            <p>这等效于旧有的格式：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs txt">deb https://mirrors.sustech.edu.cn/debian/ trixie main contrib non-free non-free-firmware<br><br>deb https://mirrors.sustech.edu.cn/debian/ trixie-updates main contrib non-free non-free-firmware<br><br>deb https://mirrors.sustech.edu.cn/debian-security trixie-security main contrib non-free non-free-firmware<br></code></pre></td></tr></table></figure>          </div><p>然后执行<code>sudo apt update</code>刷新软件包缓存即可<br>顺带一提，如果你仍有部分配置文件为原先的<code>sources.list</code>格式，可通过<code>apt modernize-sources</code>指令将其转换为最新的DEB 822格式。</p><h1 id="用户"><a href="#用户" class="headerlink" title="用户"></a>用户</h1><h2 id="普通用户"><a href="#普通用户" class="headerlink" title="普通用户"></a>普通用户</h2><p>拿到VPS后，如果你发现系统内只有<code>root</code>用户，那么就需要手动创建一个普通用户作日常使用。原因很简单，根据最小权限原则，普通的指令应该使用非特权账户执行，这样可以降低手误输入错误指令时破坏系统的概率。同时，部分软件也会主动拒绝以root用户运行。如果你已经使用普通用户登录系统，可跳过这一小节。<br>方法很简单，直接在终端输入以下指令即可，将<code>$USERNAME</code>替换为你想起的用户名：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> adduser <span class="hljs-variable">$USERNAME</span><br></code></pre></td></tr></table></figure><p>会提示输入密码和其他信息，除密码外其他信息回车跳过即可<br>之后需要将用户加入sudo组以使用sudo进行提权  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> adduser <span class="hljs-variable">$USERNAME</span> <span class="hljs-built_in">sudo</span><br></code></pre></td></tr></table></figure><p><img src="/post/linux-server-optimization/5.png">  </p><div class="note note-info">            <p><code>adduser</code>指令是对系统自带指令<code>useradd</code>和<code>usermod</code>的包装，相较后者更加友好，其等效于这两条指令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> useradd -m -G <span class="hljs-built_in">sudo</span> -s /bin/bash <span class="hljs-variable">$USERNAME</span><br><span class="hljs-built_in">sudo</span> passwd <span class="hljs-variable">$USERNAME</span><br></code></pre></td></tr></table></figure>          </div><h2 id="密钥对"><a href="#密钥对" class="headerlink" title="密钥对"></a>密钥对</h2><p>创建完普通用户后，接下来需要创建用于SSH登录的密钥对，如果你的VPS来自云厂商，那你可能在设定服务器的时候已经下载了生成的私钥，可以跳过这一小节。<br>同样很简单，在终端中输入以下指令，一路回车确认即可：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">ssh-keygen<br></code></pre></td></tr></table></figure><p>完成之后，你应该能在<code>~/.ssh</code>下见到两个文件：<code>id_ed25519</code>和<code>id_ed25519.pub</code>，前者为私钥，后者为公钥，我们需要先将公钥文件写入同目录下的<code>authorized_keys</code>文件中，SSH会按行读取作为密钥对验证中的公钥值：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cat</span> ~/.ssh/id_ed25519.pub &gt;&gt; ~/.ssh/authorized_keys<br></code></pre></td></tr></table></figure><p>然后将私钥文件复制到本地即可。</p><h1 id="SSH"><a href="#SSH" class="headerlink" title="SSH"></a>SSH</h1><p>默认情况下，SSH服务端使用的是22端口，并且允许使用用户密码登录，这可能会导致大量的扫描与密码爆破，因此我们可以修改SSH服务端的配置文件增强安全性。<br>SSH默认的配置文件位于<code>/etc/ssh/sshd_config</code>，但不建议直接修改这个文件，因为随着OpenSSH版本的更新，默认配置文件可能会发生变化，导致在更新软件包的时候需要手动解决冲突。而SSH在读取默认配置文件之前，会先按字典顺序读取<code>/etc/ssh/sshd_config.d/</code>目录中以<code>.conf</code>为后缀的文件，<strong>对于重复设置的值，会使用首个获取到的值</strong>。因此更好的做法是：将配置文件放在<code>/etc/ssh/sshd_config.d/</code>中，并以<code>00</code>开头命名配置文件，这样就能让我们的配置拥有最高优先级了。<br>具体到落实上，在终端输入以下指令，注意将$PORT替换为任意大于1024的高位端口：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> <span class="hljs-built_in">tee</span> /etc/ssh/sshd_config.d/00-override.conf &gt;/dev/null &lt;&lt; <span class="hljs-string">&#x27;EOF&#x27;</span><br>Port <span class="hljs-variable">$PORT</span><br>PermitRootLogin no<br>PasswordAuthentication no<br>PermitEmptyPasswords no<br>PrintLastLog <span class="hljs-built_in">yes</span><br>ClientAliveInterval 30<br>ClientAliveCountMax 3<br>EOF<br></code></pre></td></tr></table></figure><p>前几行配置都很好理解，分别是设置端口，禁止root用户登录，以及禁止使用密码登录。<code>PrintLastLog</code>会在登录SSH之后打印上一次登录的时间与IP地址。而最后两项就比较有意思了：SSH使用TCP长连接与服务端保持持久连接，而部分运营商和&#x2F;或厂商的防火墙和NAT设备可能会限制闲置的TCP长连接，将一段时间（通常是数分钟）内未发送任何数据包的长连接直接掐断，因此部分耗时较长又没有输出的指令（例如复制大量文件）可能会被中断。这两行配置就是用于解决这个问题的，其含义是：客户端会在未收到来自服务端数据之后，每隔30秒通过SSH向服务端发送一次心跳包以尽可能地保持连接，在三次未收到来自服务端的反馈后就认为连接已断开。  </p><div class="note note-info">            <p>部分教程可能会设置以下项：  </p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs txt">TCPKeepAlive yes<br></code></pre></td></tr></table></figure><p>这样设置谈不上错误，但<strong>几乎没用</strong>，首先这个值默认就是yes，其次这个值设置的是TCP <code>Keep-Alive</code>心跳包的发送与否，这本身没有任何问题，TCP心跳包也确实可以用来保持TCP长连接。问题就在于，这个心跳包的发送间隔由内核参数<code>net.ipv4.tcp_keepalive_time</code>定义，至少在Debian系上，其默认值为：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ sysctl net.ipv4.tcp_keepalive_time<br>net.ipv4.tcp_keepalive_time = 7200<br></code></pre></td></tr></table></figure><p>整整2个小时！等内核的TCP协议栈开始发送心跳包，中间设备早就把长连接断开了，黄花菜都凉了。  </p>          </div>  <p>完成之后，可以使用<code>sudo sshd -t</code>测试当前配置，若没有输出则代表配置正确。<br>接下来，重载<code>sshd</code>服务  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> systemctl reload sshd<br></code></pre></td></tr></table></figure><p>在客户端开启一个新窗口，测试是否能用私钥连接至服务端  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">ssh -i /path/to/your/private/key <span class="hljs-variable">$USERNAME</span>@<span class="hljs-variable">$IP</span><br></code></pre></td></tr></table></figure><p>更进一步地，你还可以用<code>sudo sshd -T</code>查看目前SSH服务端正在使用的配置。<br>最后，每次都要打这么一长串指令才能连接到服务端实在是麻烦，除了使用各种第三方客户端外，也可以在客户端创建<code>~/.ssh/config</code>文件以简化操作，在其中写入以下内容：  </p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs txt">Host $NICKNAME<br>HostName $IP<br>User $USERNAME<br>Port $PORT<br>IdentityFile /path/to/your/private/key<br></code></pre></td></tr></table></figure><p>根据你的实际情况修改这些参数，之后就可以用<code>ssh $NICKNAME</code>连接至服务端了。</p><h1 id="防火墙"><a href="#防火墙" class="headerlink" title="防火墙"></a>防火墙</h1><h2 id="UFW"><a href="#UFW" class="headerlink" title="UFW"></a>UFW</h2><p>防火墙的重要性应该不必多说，当我们不小心让仅供本地访问的，没有鉴权的服务监听在<code>0.0.0.0</code>上的时候，防火墙就是最后一道防线。在Linux上，对所有网络流量进行管理的程序叫<code>iptables</code>，不过这个程序功能过于强大与复杂，因此我们选择基于<code>iptables</code>，但更加简洁易于配置的防火墙程序<code>ufw</code>（<strong>U</strong>ncomplicated <strong>F</strong>ire<strong>W</strong>all）<br>如果你使用的是云厂商的VPS，那么也可以跳过这一节，因为云厂商一般都配有更细粒度的网络管理面板，可自行在网页上调整出入站规则。<br>UFW已经包含在官方软件源内，如果系统内没有预装，首先通过<code>apt</code>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install ufw<br><span class="hljs-built_in">sudo</span> ufw status<br></code></pre></td></tr></table></figure><p>目前防火墙应该处于<code>inactive</code>状态，UFW的默认策略是放行全部出站流量+拦截全部入站流量，所以在启用防火墙之前，我们需要将SSH使用的端口添加到入站白名单中（不然就把自己档在外面了），将$PORT替换为你的SSH端口：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw allow <span class="hljs-variable">$PORT</span>/tcp<br></code></pre></td></tr></table></figure><blockquote><p>注意不要执行<code>sudo ufw allow ssh</code>，只会放行22端口的流量</p></blockquote><p>随后可以执行  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw status<br></code></pre></td></tr></table></figure><p>查看防火墙状态与添加的规则，正确的话即可激活防火墙，立即生效：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> ufw <span class="hljs-built_in">enable</span><br></code></pre></td></tr></table></figure><div class="note note-warning">            <p>UFW不能为Docker容器映射出来的端口提供防护，原因是UFW在<code>iptables</code>创建的规则位于Docker创建的规则之下。对于Docker容器，最佳实践是将映射出来的端口显式绑定在<code>127.0.0.1</code>，如<code>-p 127.0.0.1:3389:3389</code></p>          </div>  <h2 id="Fail2ban"><a href="#Fail2ban" class="headerlink" title="Fail2ban ?"></a>Fail2ban ?</h2><p>相当一部分教程还会推荐安装Fail2ban这个软件包用于防止对SSH服务进行暴力破解，有必要吗？<br>首先要说明Fail2ban的原理：作为一款第三方软件，Fail2ban其实是靠读取日志实现其功能的。以SSH为例，Fail2ban会读取SSH日志中有关登录失败的条目，并封禁其中记录的IP地址<br>对应到SSH服务端这一防护场景，考虑到我们在之前已经启用了密钥认证并完全关闭了密码认证，这意味着攻击者无论如何尝试都不可能暴力破解。虽然我们依然会在SSH的日志当中看到源源不断的认证失败记录，但不会对我们造成任何影响。<br><img src="/post/linux-server-optimization/7.png" alt="大部分自动化扫描脚本只会尝试常见的用户名+已有泄漏记录的密码，因此它们可能连用户名都猜不对就被断开连接了"><br>所以，在启用密钥对认证的情况下，<strong>完全没必要针对SSH配置Fail2ban</strong><br>但这并不意味着Fail2ban就一无是处了，如果你的主机上配置了其他服务，例如Postfix, Dovecot等通过用户名+密码认证的服务，Fail2ban还是能有效识别并封禁暴力破解的尝试的（在<a href="https://github.com/fail2ban/fail2ban/wiki/Best-practice">正确配置日志</a>的情况下），并且除了<a href="https://github.com/fail2ban/fail2ban/tree/master/config/filter.d">内置的一系列服务</a>，你还可以通过手写正则表达式为自己的服务创建封禁规则。所以，是否安装Fail2ban还是要看具体的情况。</p><h1 id="网速优化"><a href="#网速优化" class="headerlink" title="网速优化"></a>网速优化</h1><h2 id="BBR"><a href="#BBR" class="headerlink" title="BBR"></a>BBR</h2><p>由Google开发的，赫赫有名的拥塞控制算法，相较Linux内核默认的算法<code>CUBIC</code>，可以提供更加优秀的带宽与延迟。<br>调整这个值需要使用系统自带的程序<code>sysctl</code>，以往的做法是：将需要调整的值写入<code>/etc/sysctl.conf</code>，再使用<code>sysctl -p</code>加载配置文件。<br>但是，自从Debian 13开始，<code>sysctl</code><a href="https://www.debian.org/releases/trixie/release-notes/issues.html#etc-sysctl-conf-is-no-longer-honored">不再读取这个文件了</a>，其默认配置文件放在<code>/usr/lib/sysctl.d/50-default.conf</code>，我们不需要动这个文件，根据<code>sysctl</code>的man page，其还会从以下这几个地方读取配置：  </p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs txt">/etc/sysctl.d/*.conf<br>/run/sysctl.d/*.conf<br>/usr/local/lib/sysctl.d/*.conf<br>/usr/lib/sysctl.d/*.conf<br>/lib/sysctl.d/*.conf<br></code></pre></td></tr></table></figure><p><code>sysctl</code>会从所有这些目录中收集配置文件，然后对它们按文件名进行统一的字典序排序，再依次加载其中的设置。若出现同名文件，则使用目录优先级更高的文件替代优先级低的文件。<strong>如果同一个参数被多次设置，则后加载的值会覆盖前面的值</strong>。也就是说，与SSH服务端的配置相反，我们应该将配置文件放在<code>/etc/sysctl.d</code>中，并以<code>99</code>开头命名配置文件，这样才能让我们的配置拥有最高优先级。（Linux，很神奇吧~）<br>具体到落实上，在终端输入以下指令：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;net.ipv4.tcp_congestion_control=bbr&quot;</span> | <span class="hljs-built_in">sudo</span> <span class="hljs-built_in">tee</span> /etc/sysctl.d/99-bbr.conf<br></code></pre></td></tr></table></figure><p>之后使用<code>sudo sysctl --system</code>重载配置文件即可。</p><h2 id="fq"><a href="#fq" class="headerlink" title="fq ?"></a>fq ?</h2><p>细心的你可能会注意到，在网上一搜一大把的“启用BBR教程”中，一般还会附带这样一条<code>sysctl</code>配置：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">net.core.default_qdisc=fq<br></code></pre></td></tr></table></figure><p>这行配置用于设置默认的队列调度算法，默认值为<code>fq_codel</code>，究竟有没有必要将这个值改为<code>fq</code>呢？<br>通过查阅2017年<a href="https://groups.google.com/g/bbr-dev/c/4jL4ropdOV8">BBR开发者的讨论</a>可以得知，BBR算法需要搭配<a href="https://en.wikipedia.org/wiki/TCP_pacing">TCP Pacing</a>使用，否则可能会将“超过算法期望值”的包发送至互联网中，而<code>fq_codel</code>这一队列调度算法并没有实现TCP Pacing，所以在当时，BBR需要搭配<code>fq</code>这一算法使用。<br>但是！自从Linux内核版本4.13开始，内核的TCP栈增加了Pacing功能，作为在队列调度算法没有实现这项功能时的fallback，也就是说，自这个版本开始，BBR+<code>fq_codel</code>也能很好地工作，考虑到<code>fq_codel</code>相比<code>fq</code>额外增加的受控延迟算法，保持默认值就已经很好了。<br>总结一下，BBR+<code>fq</code>纯属历史遗留问题，<code>net.core.default_qdisc</code>的值<strong>无需额外设置</strong></p><h1 id="环境变量"><a href="#环境变量" class="headerlink" title="环境变量"></a>环境变量</h1><p>Ubuntu相对Debian一个“易用”的方面在于，其镜像中预配置了一些环境变量，使得在日常使用中更加得心应手。其实，我们也可以在Debian中自行设置这些环境变量（有些甚至已经在<code>~/.bashrc</code>中，只是默认被注释了）<br>打开<code>~/.bashrc</code>，加入以下行：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">alias</span> ll=<span class="hljs-string">&#x27;ls -l&#x27;</span><br><span class="hljs-built_in">export</span> PATH=<span class="hljs-variable">$PATH</span>:/sbin<br></code></pre></td></tr></table></figure><p>其中<code>ll</code>可以列出目录内的文件及详细信息，在快速查看目录的时候非常有用，而第二行的意思是：将<code>/sbin</code>添加到当前用户的<code>PATH</code>环境变量中。这是Debian独有的问题：系统默认不会在普通用户的<code>PATH</code>环境变量中添加这一目录，导致普通用户无法使用安装在<code>/sbin</code>下的软件（例如<code>ifconfig</code>，无需<code>root</code>权限也可以查看网卡信息）<br><img src="/post/linux-server-optimization/6.png" alt="Debian默认的.profile文件，不对uid != 0的用户（也就是非root用户）在PATH中添加/sbin"><br>设置完成后登出再登录即可生效。<br>笔者的习惯是只添加这两个，你可以自行添加你喜欢的环境变量，也可以看看<code>.bashrc</code>中还有哪些被注释掉的环境变量。</p><h1 id="常用软件"><a href="#常用软件" class="headerlink" title="常用软件"></a>常用软件</h1><p>最后，根据我的使用经验，推荐一些实用的软件，前三个可以直接从软件源获取，剩下的已给出对应的Github仓库链接：  </p><p><strong>htop</strong><br>TUI版的任务管理器，可展示CPU&#x2F;内存&#x2F;磁盘&#x2F;进程等信息，相比自带的<code>top</code>指令更加美观易读，支持直接通过鼠标操作。</p><p><strong>vnstat</strong><br>流量统计工具，可统计入站&#x2F;出站流量，并以分钟-年度的粒度展示数据，还可以通过<code>vnstat -l</code>展示实时流量数据。</p><p><strong>lrzsz</strong><br>直接在终端中发送与接收文件，使用Zmodem协议。与<code>scp</code>相比，其使用起来更为简单方便，特别是在使用跳板机或代理的情况下。（需要搭配支持Zmodem协议的终端使用）</p><p><strong><a href="https://github.com/ameshkov/dnslookup">dnslookup</a></strong><br>好用的终端DNS查询工具，除了传统的UDP查询，还支持DoT，DoH，DoQ和DNSCrypt等类型，支持各类资源类型与附加ECS</p><p><strong><a href="https://github.com/nxtrace/NTrace-core">nexttrace</a></strong><br>一个命令行traceroute工具，与原版traceroute不同的是支持显示IP地址对应的ASN与GeoIP信息，并会在最后附上包含数据包路径的地图。</p><p><strong><a href="https://github.com/RubyMetric/chsrc">chsrc</a></strong><br>一个用C编写的，支持为各类发行版和编程语言仓库换源的工具，支持对不同镜像源测速并自动将换源地址写入配置文件。作者甚至因为<a href="https://v2ex.com/t/1151802">熬夜重构代码进过ICU</a>，十分可敬。</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>写到这里已经5500字了，<del>我真能扯</del>也是破了笔者文章的长度记录了。<br>本篇文章结合了笔者自高一以来使用Linux的经验，与查阅的相关资料写作而成。对于一些指令和配置项，我也是在写作的途中才发现其具体含义的。虽然力求普适性与正确性，但毕竟只是我的使用经验，如果你有其他的优化方式，或是有好用的工具类软件推荐，又或是发现了文章中的错漏之处，十分欢迎在评论区中指出。<br>其实写到这里发现买大厂的VPS还是有许多好处的，无论是提前配置好的源，还是默认生成的强密码，还是点点鼠标就能用的控制面板，都让小白用户也能以较为安全省心的方式使用服务器。（当然也失去了亲手配置系统所获得的宝贵经验）<br>“安全”二字最终看的还是人，设计上再多的安全考量也架不住用户本身安全意识的淡薄。哪怕配置了Fail2ban，直接暴露在公网上的弱密码MySQL实例也难逃被攻破的宿命。我认为养成安全意识首先要做到的一点是：在将不知道什么地方复制过来的指令粘贴到终端中，发现运行失败的时候，首先要做的应该是上网搜索这条指令的具体含义，而不是直接在指令前面加个sudo</p><h1 id="注释与参考"><a href="#注释与参考" class="headerlink" title="注释与参考"></a>注释与参考</h1><p>除了已经使用超链接给出的资料以外，本文撰写过程中也参考了以下资料：<br><a href="https://en.wikipedia.org/wiki/CentOS">https://en.wikipedia.org/wiki/CentOS</a><br><a href="https://zhuanlan.zhihu.com/p/511438456">https://zhuanlan.zhihu.com/p/511438456</a><br><a href="https://wiki.debian.org/SourcesList#APT_sources_format">https://wiki.debian.org/SourcesList#APT_sources_format</a><br><a href="https://utcc.utoronto.ca/~cks/space/blog/sysadmin/OpenSSHConfigOrderMatters">https://utcc.utoronto.ca/~cks/space/blog/sysadmin/OpenSSHConfigOrderMatters</a><br><a href="https://serverfault.com/questions/879778/ssh-logs-i-dont-understand-maximum-authentication-attempts-exceeded">https://serverfault.com/questions/879778/ssh-logs-i-dont-understand-maximum-authentication-attempts-exceeded</a><br><a href="https://www.escapelife.site/posts/ec8a244b.html#">https://www.escapelife.site/posts/ec8a244b.html#</a>!<br><a href="https://en.wikipedia.org/wiki/Fail2ban">https://en.wikipedia.org/wiki/Fail2ban</a><br><a href="https://wiki.archlinux.org/title/Fail2ban">https://wiki.archlinux.org/title/Fail2ban</a><br>注：这篇ArchWiki关于Fail2ban的风险有这样一段警告：  </p><blockquote><p>If the attacker knows your IP address, they can send packets with a spoofed source header and get your IP address locked out of the server.</p></blockquote><p>这是不准确的，因为即使攻击者能伪造带有虚假IP Header的SYN数据包，其也绝无可能收到发往这个虚假地址的SYN+ACK确认包，也就是说连接在传输层就终止了，根本就不会被位于应用层的Fail2ban捕获，因此这种攻击方式理论上无法实现。  </p><p><a href="https://github.com/systemd/systemd/issues/5090">https://github.com/systemd/systemd/issues/5090</a><br><a href="https://groups.google.com/g/bbr-dev/c/4jL4ropdOV8">https://groups.google.com/g/bbr-dev/c/4jL4ropdOV8</a><br><a href="https://en.wikipedia.org/wiki/CoDel">https://en.wikipedia.org/wiki/CoDel</a><br>sshd_config, sysctl的man page<br>感谢V2EX的<a href="https://v2ex.com/member/bodayw">@bodayw</a>，启发了我搜索与阅读有关队列调度算法的讨论（不然这块我也糊弄过去了）</p>]]></content>
    
    
    <summary type="html">从厂商买到一台VPS，或者给自己的Homelab装好系统之后，该怎么做才能既让系统更安全，使用起来又更顺手？这篇文章整理了一些我的实践经验。</summary>
    
    
    
    <category term="研究分享" scheme="https://blog.kenxu.top/categories/%E7%A0%94%E7%A9%B6%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="linux" scheme="https://blog.kenxu.top/tags/linux/"/>
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
    <category term="homelab" scheme="https://blog.kenxu.top/tags/homelab/"/>
    
  </entry>
  
  <entry>
    <title>聊聊那些翻山越岭的神奇操作（中）</title>
    <link href="https://blog.kenxu.top/post/bypass-gfw-p2/"/>
    <id>https://blog.kenxu.top/post/bypass-gfw-p2/</id>
    <published>2025-08-16T18:30:00.000Z</published>
    <updated>2025-10-14T14:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><p>书接<a href="https://blog.kenxu.top/post/bypass-gfw-p1/">上回</a>，上回说到可以通过域前置的手段绕开防火墙的阻断，但缺点也很显而易见：支持的网站和设备都有限（而且似乎不是很优雅），那有没有什么别的翻山越岭的方法呢？</p><h1 id="劫持与污染"><a href="#劫持与污染" class="headerlink" title="劫持与污染"></a>劫持与污染</h1><p>众所周知，传统的DNS查询使用的是UDP协议，DNS服务器一般监听在53端口。这里有两个问题：一是查询的流量是明文的，任何中间人都可以监听并篡改DNS服务器返回的结果，二是DNS查询固定会使用53端口，流量特征明显，非常容易被识别并加以干扰。<br>DNS查询采用的是“递归解析”的方法，假如需要查询<code>blog.kenxu.top</code>的IP，需要先查询<code>.top</code>这个TLD对应的解析服务器，再由<code>.top</code>顶级域服务器查询<code>kenxu.top</code>的解析服务器，最终由这个一级域服务器查询<code>blog.kenxu.top</code>这个二级域名的IP。但一般而言，客户端不会自行完成递归解析的全部流程，而是将DNS请求直接发给设定的DNS服务器（运营商下发的Local DNS或是公共DNS），由它们完成剩下的流程。<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="可由 DNS Header 中的 `recursion-desired` Flag 控制">[1]</span></a></sup><br><img src="/post/bypass-gfw-p2/1.png" alt="手动对博客域名进行递归解析，由于域名采用CNAME方式接入，所以这里会查询到Netlify指派的的域名"><br>那么，防火墙是如何利用DNS的查询过程进行审查和阻断的呢？当DNS流量经过防火墙的时候，防火墙会识别DNS查询的流量并对查询的域名进行检测，一旦匹配到黑名单中的域名，便会伪装成目标DNS服务器返回虚假的解析结果。由于各级DNS服务器都会缓存查询结果，境内的DNS服务器除了会向用户返回错误的DNS解析结果，还会将这个错误的结果缓存下来，将来有其他用户查询时便直接返回这个错误的结果。<br>综上所述，在查询黑名单网站时，如果你使用的是境内的DNS服务器，你收到的是来自缓存污染后的结果；如果你使用的是境外的DNS服务器，你收到的是防火墙劫持后的结果。验证的方法很简单，随意查询一个黑名单内的地址即可：<br><img src="/post/bypass-gfw-p2/2.png"><br>使用Cloudflare提供的公共DNS举例，作为对比，如果同城有阿里云的机房，那么阿里云DNS的查询时间一般在10ms左右，而广州-香港IEPL端内的时延一般在6ms左右。如此短的查询耗时说明除非用的是比光更快的传输介质，不然一定是被防火墙劫持了（</p><h1 id="DoT与DoH"><a href="#DoT与DoH" class="headerlink" title="DoT与DoH"></a>DoT与DoH</h1><p>如何解决劫持和污染的问题？<a href="https://datatracker.ietf.org/doc/html/rfc7858">DNS over TLS (DoT)</a> 和 <a href="https://datatracker.ietf.org/doc/html/rfc8484">DNS over HTTPS (DoH)</a> 的相关规范于2016年和2018年制定完成，解决了传统DNS无加密和特征强的问题。<br>DoT的思路比较简单，即直接用TLS加密原先明文的DNS查询流量，通过TCP协议进行连接，并使用853端口作为常用端口。中间人虽然无法解密查询流量，但仍然可以通过连接目标服务器的端口判断用户正在使用DoT查询DNS，进而阻断流量。<br><img src="/post/bypass-gfw-p2/3.png" alt="友情出镜：`dot.pub`，通过端口即可判断出正在使用DoT"><br>除此之外还有一些别的特征，例如DoT流量在发送TLS ClientHello数据包时，一般不会包含ALPN (Application Layer Protocol Negotiation) 扩展，但端口号这个特征其实已经足够明显，此处按下不表。<br>而DoH在同样采用TCP连接与TLS加密的基础上，将DNS查询封装成了HTTP请求，并使用443端口，使其在流量特征上与一般浏览网页的HTTP流量无异，中间人除了无法监听和篡改DNS查询的结果，甚至无法得知用户正在进行DNS查询。<br><img src="/post/bypass-gfw-p2/4.png" alt="DoH使用443端口，与普通HTTP流量相同"><br>由于进行了HTTP封装，因此DoH的开销会略大于DoT，数据包的体积和数量会略微增大（例如ClientHello中的ALPN，数据包中的HTTP HEADER），但考虑到其相对DoT增强的安全性，以及HTTP&#x2F;2带来的多路复用等特性，这点开销也是可以接受的。</p><p>DoT和DoH并非完美无缺的解决方案，其缺点也很明显，其中绕不开的一点就是查询时间。TCP协议+TLS加密的方式，使得DoT和DoH查询DNS相较传统方案增加了两个RTT的时延（如果使用的是TLS1.2，甚至是3个RTT），查询耗时相比基于UDP的方案大大增加。<br><img src="/post/bypass-gfw-p2/5.png" alt="UDP DNS, ~13ms"><br><img src="/post/bypass-gfw-p2/6.png" alt="DoT, ~49ms"><br><img src="/post/bypass-gfw-p2/7.png" alt="DoH, ~51ms"></p><p>DoT和DoH是否可以完美突破审查？实则不然。首先，审查者可以记录境外目标DNS服务器的IP地址，在防火墙侧直接丢弃去往这个IP的数据包；同时，无论是DoT还是DoH，都使用了TLS进行加密，在上篇中有提到，TLS存在着SNI——服务器名称指示这一扩展，其中<strong>明文</strong>记载了访问网站的信息，也就是说，审查者完全可以像屏蔽其他网站一样，根据SNI中的信息屏蔽相应的DoH流量。<br>事实上，根据笔者的印象，在DoH还没有推广开来的时候（约2021年以前），仍然可以在境内访问大多数海外的DoH服务器，例如Chrome和Firefox自带的<code>OpenDNS</code>和<code>NextDNS</code>，而随着这两年审查力度的逐渐加大，这些位于境外的DoT和DoH服务器也无一例外地享受到了SNI阻断的待遇。  </p><p>使用OpenDNS的DoH实例测试，可以看出，ping所使用的ICMP协议可以正常通过防火墙并收到目标服务器的回应。<br><img src="/post/bypass-gfw-p2/8.png" alt="ICMP Ping"><br>而使用DoH查询时，笔者同时观测到了上述的两种封锁方法：对于TCP数据包的<strong>丢弃</strong>和对于TLS流量的SNI<strong>阻断</strong>。<br><img src="/post/bypass-gfw-p2/9.png" alt="丢弃TCP数据包"><br><img src="/post/bypass-gfw-p2/10.png" alt="SNI阻断，RST重置"><br>目前，除非自行在海外VPS上搭建DNS服务器，否则几乎所有公共DoT和DoH实例都是无法连接的。</p><h1 id="QUIC"><a href="#QUIC" class="headerlink" title="QUIC"></a>QUIC</h1><p>传统的TCP,UDP协议和TLS加密似乎都不足以解决眼下的问题。如同数十年前，HTTP&#x2F;2出现之前的HTTP&#x2F;1.1——与其在旧协议上缝缝补补，不如直接创造一个新的协议。早在2012年，Google就开始着手研发一种全新的协议，旨在替代传统浏览互联网所使用的HTTP协议。2015年，有关QUIC的草案交由IETF开始标准化，并逐渐转化为一种通用的传输协议。2021年，QUIC在<a href="https://datatracker.ietf.org/doc/html/rfc9000">RFC 9000</a>中正式标准化。<br><img src="/post/bypass-gfw-p2/11.png" alt="HTTP-1.1 vs. HTTP-2 vs. HTTP-3 Protocol Stack&lt;sup id=&quot;fnref:2&quot; class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn:2&quot; rel=&quot;footnote&quot;&gt;&lt;span class=&quot;hint--top hint--rounded&quot; aria-label=&quot;图片来源：https://en.wikipedia.org/wiki/File:HTTP-1.1_vs._HTTP-2_vs._HTTP-3_Protocol_Stack.svg , [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)&quot;&gt;[2]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;"><br>QUIC是一种基于UDP的传输层协议。其解决了传统基于TCP的协议面临的一个老大难问题——队头阻塞。HTTP&#x2F;2虽然增加了多路复用的特性，但多个数据包跑在一个同一个TCP连接之上，一旦发生丢包，整个连接就会阻塞，直到重传丢掉的包。在2%的丢包率下，HTTP&#x2F;2的表现甚至不如HTTP&#x2F;1。而基于UDP的QUIC在单个连接中的流是互相独立的，意味着即使发生丢包，也不会阻塞其他流，只要重传丢失的数据帧（例如STREAM frame, ACK frame, CRYPTO frame等等）即可。  </p><pre><code class=" mermaid">graph TD    A[QUIC Connection] --&gt; B[Stream 1]    A --&gt; C[Stream 2]    A --&gt; D[Stream 3]    B --&gt; B1[STREAM Frame 1]    B --&gt; B2[STREAM Frame 2]    B --&gt; B3[CRYPTO Frame]    C --&gt; C1[STREAM Frame 1]    C --&gt; C2[ACK Frame]    D --&gt; D1[STREAM Frame 1]    D --&gt; D2[STREAM Frame 2]    D --&gt; D3[STREAM Frame 3]    style A fill:#bbf,stroke:#333,stroke-width:2px    style B fill:#bfb,stroke:#333,stroke-width:1px    style C fill:#bfb,stroke:#333,stroke-width:1px    style D fill:#bfb,stroke:#333,stroke-width:1px    style B1 fill:#ffd,stroke:#333,stroke-width:1px    style B2 fill:#ffd,stroke:#333,stroke-width:1px    style B3 fill:#ffd,stroke:#333,stroke-width:1px    style C1 fill:#ffd,stroke:#333,stroke-width:1px    style C2 fill:#ffd,stroke:#333,stroke-width:1px    style D1 fill:#ffd,stroke:#333,stroke-width:1px    style D2 fill:#ffd,stroke:#333,stroke-width:1px    style D3 fill:#ffd,stroke:#333,stroke-width:1px</code></pre><p>此外，与TCP和UDP这类传统的传输层协议不同，QUIC的职能向上延伸到了TLS所在的层级（传输层之上，应用层之下）。QUIC协议强制要求使用TLS 1.3对流量进行加密，不仅进一步提升了安全性，更利用上了TLS 1.3带来的0-RTT特性，利用首次连接记录的预共享密钥直接向服务端发送数据，进一步降低时延。<br>那么，QUIC又是怎样对待在TLS握手阶段明文的Client Hello信息呢？QUIC同样对其进行了加密，但要如何在握手阶段就能够让服务端解密数据呢？其实这里使用的并非TLS加密，事实上无论协议如何变化，任何非对称加密都必须在密钥交换环节后才能进行。QUIC对于握手阶段的加密，其实是对称加密，这点在QUIC相关的<a href="https://datatracker.ietf.org/doc/html/rfc9001">RFC文档</a>中有详细定义，这里只截取其中的一小段：</p><blockquote><p>This secret is determined by using HKDF-Extract with a salt of <code>0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a</code> and the input keying material (IKM) of the Destination Connection ID field. This produces an intermediate pseudorandom key (PRK) that is used to derive two separate secrets for sending and receiving.<br>The current encryption level secret and the label “quic key” are input to the KDF to produce the AEAD key; the label “quic iv” is used to derive the Initialization Vector (IV). The header protection key uses the “quic hp” label. Using these labels provides key separation between QUIC and TLS.<br>Both “quic key” and “quic hp” are used to produce keys, so the Length provided to HKDF-Expand-Label along with these labels is determined by the size of keys in the AEAD or header protection algorithm.</p></blockquote><p>QUIC在建立连接阶段，会使用<code>Initial Packet</code>和<code>Handshake Packet</code>进行握手和密钥交换。对于<code>Initial Packet</code>中的握手信息，会先使用DCID（由客户端生成）<sup id="fnref:3" class="footnote-ref"><a href="#fn:3" rel="footnote"><span class="hint--top hint--rounded" aria-label="`DCID`即 Destination Connection ID，代表连接对方的ID，初次连接时由客户端随机生成，服务端接收连接后会生成 `SCID`(Source Connection ID)代表己方发回给客户端，之后的阶段客户端便会使用这个ID作为 `DCID`，在截图中亦有体现">[3]</span></a></sup>和一个固定密钥（与QUIC版本相关）派生出初始密钥，分别用于客户端和服务端，再根据这个初始密钥派生出对称加密和包头混淆所需要的<code>key</code>,<code>iv</code>和<code>hp</code>。服务端收到<code>Initial Packet</code>后，读取明文的DCID信息，并按同样的流程生成初始密钥<sup id="fnref:4" class="footnote-ref"><a href="#fn:4" rel="footnote"><span class="hint--top hint--rounded" aria-label="服务端需要生成两组初始密钥，一组用于解密客户端发来的包（`client_initial_secret`），另一组用于加密发给客户端的 `Handshake Packet`（`server_initial_secret`）">[4]</span></a></sup>并派生出解密所需要的密钥。<br><img src="/post/bypass-gfw-p2/12.png" alt="QUIC协议的握手流程，`Protected Payload`即代表建立连接阶段结束，开始发送数据"></p><p>也就是说，加密<code>Initial Packet</code>的信息，都是公开可推导的。这里的加密本身不保证机密性，但可以防止中间人篡改并保证QUIC包体在连接的不同阶段的一致性。那么，防火墙目前能否识别并阻断QUIC类型的流量呢？<br>我们使用QUIC协议访问位于防火墙黑名单中的网站<code>v2ex.com</code>，并抓包分析结果，首先使用传统TCP协议：<br><img src="/post/bypass-gfw-p2/13.png"><br>可以看出连接立刻被防火墙阻断，接下来试试QUIC：<br><img src="/post/bypass-gfw-p2/14.png"><br>这里Wireshark帮我们解密了<code>Initial Packet</code>，可以看出在包含了黑名单内SNI的情况下，防火墙并没有阻断连接。<br>当然，根据我们上述分析的结果，防火墙是完全有能力识别并阻断QUIC流量的，至于为何没有阻断连接，可能是因为QUIC协议自带加密，同时Client Hello结构与传统TLS的不同，防火墙暂时没有做这方面的适配。<br>也有<a href="https://gfw.report/publications/usenixsecurity25/en/">研究</a>指出，防火墙其实已经具备了识别并阻断QUIC协议的能力（很遗憾我暂时没有体验到）。但由于对<code>Initial Packet</code>进行解密造成的性能开销，防火墙目前对QUIC流量的处理能力较弱，在1000kpps的混合流量下，80%左右的包都能直接“穿墙而过”，这也算是加密所起到的一个作用吧。<br>最后我们还是得聊聊QUIC的缺点，在境内，基于UDP的协议基本难逃QoS的命运，基于UDP带来的特性看似美好，协议本身却终究难逃高峰时期运营商的限速与阻断。事实上，本文大多写于晚间，在这个时间段，笔者使用家中的宽带始终无法与网站建立QUIC连接，在进行QUIC流量抓包的时候不得不切换到流量上网。</p><h1 id="ECH"><a href="#ECH" class="headerlink" title="ECH"></a>ECH</h1><p>前文说到QUIC对Client Hello所在的<code>Initial Packet</code>进行了对称加密，但中间人还是可以根据公开信息解密，那么有没有什么可以彻底加密Client Hello数据的方式呢？这就要提到相关<a href="https://datatracker.ietf.org/doc/draft-ietf-tls-esni/">RFC标准</a>仍在制定中的ECH（Encrypted Client Hello）了。2018年，Fastly, Cloudflare, Mozilla 等一众研究员和学者提出了<a href="https://datatracker.ietf.org/doc/draft-ietf-tls-esni/00/">ESNI草案</a>，研究加密Client Hello中SNI扩展的可行性。2020年，协议从ESNI变为ECH，将加密范围从SNI变为整个Client Hello信息，将ALPN和Key Share等能够间接推断出应用层协议的信息也纳入了保护范围。<br>ECH加密Client Hello的方式并非QUIC协议<code>Initial Packet</code>阶段的对称加密，而是与TLS加密类似的非对称加密。如何在TLS握手阶段，服务端密钥还没有发送至客户端的情况下就使用非对称加密呢？解决方案不在TLS协议上，而是<strong>DNS</strong>。<br>DNS并非只能记录域名对应的IP信息，还可以记录许许多多跟域名相关的信息，例如用于将一个域名映射到另一个域名的CNAME记录，用于电子邮件服务器的MX记录等等。而ECH正是利用了其中一种DNS记录类型——SVCB (Service Binding)。<br>SVCB记录原先用于扩展CNAME的功能，除了可以设置映射的域名之外，还可以设置服务所在的端口，ALPN信息等等，举个例子：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">svc.example.net.  7200  IN SVCB 3 svc4.example.net. (alpn=&quot;h3,h2&quot; port=&quot;8004&quot; ech=&quot;...&quot; )<br></code></pre></td></tr></table></figure><p>SVCB后是记录内容，3代表优先级，<code>svc4.example.net</code>代表可替代的主机端点（<code>TargetName</code>），之后的部分（<code>SvcParams</code>）记录了连接的细节。你可能已经发现了，这里存在一个<code>ech</code>设置项，实际上，这就是解决加密问题的关键：ECH将非对称加密所使用的公钥放在了DNS记录中。<br>事实上，对于HTTPS协议，一般不会直接使用SVCB记录类型记录连接相关的信息，而是使用一种从SVCB记录派生出类的DNS记录类型——HTTPS记录，其结构与SVCB几乎一致，只不过专用于HTTPS协议，以上文提到的被封锁的网站<code>v2ex.com</code>为例，它的HTTPS记录长这样：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">v2ex.com.  600  IN  HTTPS  1  .  alpn=&quot;h3,h2&quot;  ipv4hint=172.66.133.207,172.66.137.6  ech=AEX+DQBBrgAgACBFdzOUkqW5BOSqjBlyYnCth1Zys0q2mhTuAgY4m5HzGgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=  ipv6hint=2606:4700:10::ac42:85cf,2606:4700:10::ac42:8906<br></code></pre></td></tr></table></figure><p>可以看到，在<code>SvcParams</code>部分记载了ECH所使用的加密公钥，除此之外还有<code>ipv4hint</code>和<code>ipv6hint</code>字段，记载了网站域名对应的IP，这是为了减少DNS查询的次数从而降低延迟。<code>ech</code>是一串使用Base64编码的字符串<sup id="fnref:5" class="footnote-ref"><a href="#fn:5" rel="footnote"><span class="hint--top hint--rounded" aria-label="有关这个字段的详细介绍，请参考： https://blog.outv.im/2023/ech/#%E9%BA%BB%E7%83%A6%E7%9A%84-ech-%E9%83%A8%E7%BD%B2%EF%BC%9A%E6%89%98%E7%AE%A1%E5%9C%A8-dns-%E4%B8%8A%E7%9A%84-echconfig">[5]</span></a></sup>，试将其解码：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">EA Ew3䒥鄤ꌙrbp퇖r㊶ꔮ8둳cloudflare-ech.com<br></code></pre></td></tr></table></figure><p>一大串乱码之后是一个域名<code>cloudflare-ech.com</code>，这是客户端所连接的中间服务器的域名，一般是网站的CDN提供商。事实上，ECH将Client Hello分为了外层和内层（<code>Outer ClientHello</code>和<code>Inner ClientHello</code>），客户端在进行HTTPS类型解析后，首先用公钥加密连接至服务端的Client Hello信息，将其放入TLS握手包的<code>encrypted_client_hello</code>扩展中，再将连接至中间服务器的Client Hello信息以明文的形式放在原先的位置。中间服务器收到数据包后，先使用私钥解密<code>encrypted_client_hello</code>扩展中的信息，再根据其中的SNI信息将其转发至对应的后端服务器。这样中间人就只能看见用户连接中间服务器的握手信息，而看不到连接后端服务器的握手信息了。  </p><pre><code class=" mermaid">sequenceDiagram    participant U as 用户 (Client)    participant M as 中间人 (观察者)    participant F as 前端服务器 (Gateway/CDN)    participant B as 后端服务器 (真实网站)    Note over U,M: DNS下发ECH公钥 (via HTTPS RR / SVCB)    U-&gt;&gt;F: 发送 Outer ClientHello (包含加密的Inner ClientHello)    Note right of M: 只能看到Outer ClientHello&lt;br/&gt;及外层SNI (伪装用)        alt 前端服务器支持ECH        F-&gt;&gt;F: 使用DNS提供的公钥解密Inner ClientHello        F-&gt;&gt;B: 将解密得到的真实SNI/ALPN等转发给后端服务器        B--&gt;&gt;F: 返回握手响应 (证书/密钥交换等)        F--&gt;&gt;U: 返回加密的ServerHello等握手消息        Note over U,F: 成功建立TLS会话 (真实目标隐藏)    else 不支持ECH        F--&gt;&gt;U: 返回ECH失败信号 (ech_required)        U-&gt;&gt;F: 回退到明文ClientHello    end</code></pre><p>那么，想要使用上ECH，获取到正确的HTTPS记录就显得尤为重要。如何确保DNS记录不被中间人审查与篡改？这就需要依赖文章前半部分所说的DoT和DoH了。<sup id="fnref:6" class="footnote-ref"><a href="#fn:6" rel="footnote"><span class="hint--top hint--rounded" aria-label="当然，你得使用没有受到污染的 DoH 服务器">[6]</span></a></sup>事实上，虽然ECH相关的草案中没有明确规定，但Google Chrome和Firefox都将使用DoH作为启用ECH的前提条件之一，毕竟根据木桶理论，在使用传统UDP DNS的情况下，隐私方面最短的一块板应该是DNS查询，而非Client Hello中的明文信息。<br><img src="/post/bypass-gfw-p2/17.png" alt="在使用UDP协议查询HTTPS类型的记录时，使用境外的DNS会被防火墙识别并直接返回`SERVFAIL`，而境内的DNS服务器似乎根本就不会解析并缓存HTTPS类型的记录"></p><p>在使用ECH的情况下，抓包看看访问<code>v2ex.com</code>的结果：<br><img src="/post/bypass-gfw-p2/15.png"><br>可以看到在Client Hello阶段，SNI被替换为了中间服务器的地址，同时真正的Client Hello信息加密后放在了<code>encrypted_client_hello</code>扩展中。TLS握手也顺利完成，没有被阻断。  </p><p>但是，仍然存在一个小问题：虽然真实的SNI被加密了，但是“存在着被加密的SNI”这件事却是可以被检测到的，毕竟<code>encrypted_client_hello</code>这个扩展就摆在 (Outer) Client Hello 信息中。中间人难道不会通过检测是否存在这个扩展从而阻断所有的ECH流量吗？制定ECH草案的人也想到了这一点，这就是我认为ECH中一个绝妙的设计点：<strong>GREASE</strong>（Generate Random Extensions And Sustain Extensibility）。<br>GREASE并非ECH的首创，其于2016年被提出，并在2020年<a href="https://datatracker.ietf.org/doc/rfc8701/">标准化</a>，旨在对抗TLS面临的协议僵化 (Protocol Ossification) 问题。具体在RFC的第一节有详细介绍：  </p><blockquote><p>…TLS follows a model where one side, usually the client, advertises capabilities, and the peer, usually the server, selects them. The responding side must ignore unknown values so that new capabilities may be introduced to the ecosystem while maintaining interoperability.<br>However, bugs may cause an implementation to reject unknown values……when new values are defined, updated peers will discover that the metaphorical joint in the protocol has rusted shut<sup id="fnref:7" class="footnote-ref"><a href="#fn:7" rel="footnote"><span class="hint--top hint--rounded" aria-label="这里使用了“生锈的关节”这个比喻，这也是为什么这个特性（或者说思想）会被特地命名成 `GREASE`（润滑）">[7]</span></a></sup> and the new values cannot be deployed….<br>To avoid this problem, this document reserves some currently unused values for TLS implementations to advertise at random. Correctly implemented peers will ignore these values and interoperate. Peers that do not tolerate unknown values will fail to interoperate, revealing the mistake before it is widespread.</p></blockquote><p>GREASE 的目的，就是为了防止中间设备因为协议新特性引入的额外字段而无法正确解析流量，因此采取的一种预防性措施。通过人为增加一些无用的字段，迫使中间设备在解析流量时忽略掉这些字段（而不是抛出错误），从而为协议后续的升级留下空间。<br>回到ECH，GREASE在此处的作用在RFC规范的6.2小节中有说明：  </p><blockquote><p>The GREASE ECH mechanism allows a connection between and ECH-capable client and a non-ECH server to appear to use ECH, thus reducing the extent to which ECH connections stick out.</p></blockquote><p>除了上述所说的目的之外，GREASE还可以使ECH数据包和非ECH数据包看起来完全一致，从而对抗审查。具体实现方法就是：对于不支持ECH的网站（例如未在HTTPS记录中配置ECH公钥或是干脆没有HTTPS记录），也在Client Hello数据包内添加<code>encrypted_client_hello</code>扩展，只不过其中的参数都是随机生成的。根据GREASE的相关规范，如果不支持ECH的服务端解析到这个扩展，应该直接将其丢弃，这样就保证了ECH既不会被中间人识别，又不会对旧服务端造成影响。<sup id="fnref:8" class="footnote-ref"><a href="#fn:8" rel="footnote"><span class="hint--top hint--rounded" aria-label="对于 ECH 的 GREASE 在浏览器端实现，支持ECH的浏览器都支持 ECH GREASE">[8]</span></a></sup><br><img src="/post/bypass-gfw-p2/16.png" alt="尝试访问不支持ECH的`baidu.com`，同样在Client Hello中看到了`encrypted_client_hello`扩展"></p><p>最后还是说说缺点，ECH在可部署性，机密性，抗审查性和前向兼容性方面基本都做到了极致，不过目前支持ECH的网站还是少之又少，目前来看几乎只有使用Cloudflare免费计划的网站支持ECH<sup id="fnref:9" class="footnote-ref"><a href="#fn:9" rel="footnote"><span class="hint--top hint--rounded" aria-label="因为 Cloudflare 会为免费计划的用户强制启用 ECH，相当于当小白鼠了">[9]</span></a></sup>（当然，是CDN服务器提供的支持），希望在不久的将来ECH能正式标准化，更多网站能支持ECH这项先进的特性。</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>本期与上期间隔时间较远，主要还是因为笔者对其中的一些概念处于一知半解的状态，集中几天查阅了相关资料才熬出的这篇文章，可以说是很不容易了（<br>但即便如此，文章肯定存在着（可能很多的）错漏，敬请各位海涵，欢迎在评论区与我讨论。<br>虽然标题写的是中篇，但处于一些原因，下篇完成的时间仍是未知数（可能很快，也可能不会有），请各位以不抱期望的心情期待一下（</p><h1 id="注释与参考"><a href="#注释与参考" class="headerlink" title="注释与参考"></a>注释与参考</h1><p>本文撰写过程中参考了以下资料：<br><a href="https://www.ruanyifeng.com/blog/2022/08/dns-query.html">https://www.ruanyifeng.com/blog/2022/08/dns-query.html</a><br><a href="https://en.wikipedia.org/wiki/QUIC">https://en.wikipedia.org/wiki/QUIC</a><br><a href="https://en.wikipedia.org/wiki/Internet_protocol_suite">https://en.wikipedia.org/wiki/Internet_protocol_suite</a><br><a href="https://suntus.github.io/2019/05/09/HKDF%E7%AE%97%E6%B3%95/">https://suntus.github.io/2019/05/09/HKDF%E7%AE%97%E6%B3%95/</a><br><a href="https://blog.cloudflare.com/encrypted-client-hello/">https://blog.cloudflare.com/encrypted-client-hello/</a><br><a href="https://blog.cloudflare.com/handshake-encryption-endgame-an-ech-update/">https://blog.cloudflare.com/handshake-encryption-endgame-an-ech-update/</a><br><a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types">https://en.wikipedia.org/wiki/List_of_DNS_record_types</a><br><a href="https://kb.isc.org/docs/svcb-and-https-resource-records-what-are-they">https://kb.isc.org/docs/svcb-and-https-resource-records-what-are-they</a><br>此外还有文章中已经给出的超链接<br>感谢ChatGPT画的mermaid图</p><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>可由 DNS Header 中的 <code>recursion-desired</code> Flag 控制<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:2" class="footnote-text"><span>图片来源：<a href="https://en.wikipedia.org/wiki/File:HTTP-1.1_vs._HTTP-2_vs._HTTP-3_Protocol_Stack.svg">https://en.wikipedia.org/wiki/File:HTTP-1.1_vs._HTTP-2_vs._HTTP-3_Protocol_Stack.svg</a> , <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.en">CC BY-SA 4.0</a><a href="#fnref:2" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:3" class="footnote-text"><span><code>DCID</code>即 Destination Connection ID，代表连接对方的ID，初次连接时由客户端随机生成，服务端接收连接后会生成 <code>SCID</code>(Source Connection ID)代表己方发回给客户端，之后的阶段客户端便会使用这个ID作为 <code>DCID</code>，在截图中亦有体现<a href="#fnref:3" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:4" class="footnote-text"><span>服务端需要生成两组初始密钥，一组用于解密客户端发来的包（<code>client_initial_secret</code>），另一组用于加密发给客户端的 <code>Handshake Packet</code>（<code>server_initial_secret</code>）<a href="#fnref:4" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:5" class="footnote-text"><span>有关这个字段的详细介绍，请参考： <a href="https://blog.outv.im/2023/ech/#%E9%BA%BB%E7%83%A6%E7%9A%84-ech-%E9%83%A8%E7%BD%B2%EF%BC%9A%E6%89%98%E7%AE%A1%E5%9C%A8-dns-%E4%B8%8A%E7%9A%84-echconfig">https://blog.outv.im/2023/ech/#%E9%BA%BB%E7%83%A6%E7%9A%84-ech-%E9%83%A8%E7%BD%B2%EF%BC%9A%E6%89%98%E7%AE%A1%E5%9C%A8-dns-%E4%B8%8A%E7%9A%84-echconfig</a><a href="#fnref:5" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:6" class="footnote-text"><span>当然，你得使用没有受到污染的 DoH 服务器<a href="#fnref:6" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:7" class="footnote-text"><span>这里使用了“生锈的关节”这个比喻，这也是为什么这个特性（或者说思想）会被特地命名成 <code>GREASE</code>（润滑）<a href="#fnref:7" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:8" class="footnote-text"><span>对于 ECH 的 GREASE 在浏览器端实现，支持ECH的浏览器都支持 ECH GREASE<a href="#fnref:8" rev="footnote" class="footnote-backref"> ↩</a></span></span></li><li><span id="fn:9" class="footnote-text"><span>因为 Cloudflare 会为免费计划的用户强制启用 ECH，相当于当小白鼠了<a href="#fnref:9" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]></content>
    
    
    <summary type="html">越过长城，走向世界</summary>
    
    
    
    <category term="研究分享" scheme="https://blog.kenxu.top/categories/%E7%A0%94%E7%A9%B6%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="network" scheme="https://blog.kenxu.top/tags/network/"/>
    
  </entry>
  
  <entry>
    <title>记一次降级笔记本BIOS固件的经历</title>
    <link href="https://blog.kenxu.top/post/downgrade-bios/"/>
    <id>https://blog.kenxu.top/post/downgrade-bios/</id>
    <published>2025-05-17T10:00:00.000Z</published>
    <updated>2025-05-20T11:09:35.041Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><h1 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h1><p>笔者的笔记本电脑是RedmiBook Pro 14 2024，于去年年中购入，之后便装了Windows 11 + Fedora Linux双系统。某一次从Windows重启到Linux后，发现在过了Grub引导后卡住，除了屏幕左上角显示一个下划线之外，没有打印任何报错日志。当时以为是系统被我折腾坏了，排查的时候试过重新制作USB启动盘，结果情况相同；又试了一下其他系统的ISO镜像，结果是有些镜像可以启动，有些则不行。考虑到系统内没什么资料需要保存，于是便格式化了分区，装上了能正常启动的ArchLinux（<br>直到最近，我看到了Fedora 42发布的<a href="https://x.com/geekbb/status/1912314596662313461">帖子</a>，看着<a href="https://fedoramagazine.org/whats-new-fedora-workstation-42/">新功能特性</a>以及美观典雅的GNOME桌面，我再次萌生了安装Fedora的想法。本以为都过了两个大版本了，这个Bug应该被修复了。结果在我制作好USB启动盘，准备开始安装系统的时候，发现再次卡在了同样的地方，这下只能看看能不能自己解决这个问题了。</p><h1 id="搜索"><a href="#搜索" class="headerlink" title="搜索"></a>搜索</h1><p>很自然地，直接在Google上搜索笔记本型号+Fedora，找到了Fedora论坛上的一个<a href="https://discussion.fedoraproject.org/t/stuck-after-grub-menu-when-booting-fedora-40-usb-installer/129904">帖子</a>，看起来有人遇到了一模一样的问题，并且已经有了<a href="https://discussion.fedoraproject.org/t/installation-problem-black-screen-fedora-41-on-laptop-with-an-intel-ultra-5-cpu-and-integrated-graphics-card/136645/13">解决方案</a>。原来是BIOS固件的问题，某天我可能通过小米电脑管家更新了BIOS固件，最新版的固件号是<code>RMAMT4B0P0A0A</code>，而降级<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="原帖中写的是升级，但在经过[确认](https://4pda.to/forum/index.php?showtopic=1085675&st=2760#entry136058114)后，实际上`RMAMT4B0P0A0A`是比`RMAMT4B0P0909`更新的">[1]</span></a></sup>到上一个版本<code>RMAMT4B0P0909</code>就可以正常启动Fedora Linux了。<br><img src="/post/downgrade-bios/1.png"><br>看起来问题已经解决了，我们只需要找到<code>0909</code>版本的固件，刷上就行了。原帖作者也在下面留了<code>0909</code>版本固件的链接，但无一例外的都失效了。最后一条回复的网友表示将固件上传到了一个俄罗斯论坛4PDA上，满心欢喜地点开<a href="https://4pda.to/forum/dl/post/32827362/RMAMT4B0P0909.zip">链接</a>一看：<br><img src="/post/downgrade-bios/2.png" alt="Man!">  </p><div class="note note-info">            <p>4PDA上的文件需要注册登录后才能下载！这个分享链接并没有失效，登录论坛后即可下载该固件。感谢评论区<a href="https://haleclipse.org/">@Haleclipse</a>提醒。<br>其实到这里就已经获取到需要的文件了，但<del>我都写了这么多了</del>知其然不如知其所以然，接着往下看也无妨嘛～</p>          </div><p>靠北！于是再次试着在Google上用固件名称搜索相关信息，虽然没有找到固件包，但找到了这个V2EX上的<a href="https://www.v2ex.com/t/1051677?p=2#r_15611387">帖子</a>并顺藤摸瓜找到了一条Github Issue中的<a href="https://github.com/vrolife/modern_laptop/issues/96#issuecomment-2519013530">Comment</a>，再次验证了只要降级到<code>0909</code>版本的BIOS一切问题就会迎刃而解。  </p><blockquote><p>这条Comment附带了作者从4PDA上下载的16寸SKU的<code>0909</code>版BIOS固件包，这是<a href="https://github.com/user-attachments/files/18016847/RMAMT6B0P0909.zip">直链</a>，16寸SKU的读者可以用这个固件，14寸SKU请继续往下看。</p></blockquote><p>最终，我发现了一位俄罗斯爱好者创建的Github仓库：<a href="https://github.com/Data-Name-ID/RedmiBook-Pro-14-2024">Data-Name-ID&#x2F;RedmiBook-Pro-14-2024</a>，并加入了README中提到的<a href="https://t.me/redmiclub1">Telegram群聊</a>，又在群聊中搜索了一番，最终找到了降级BIOS的具体的操作指南。<a href="https://t.me/redmiclub1/26886/40635">原文</a>为俄语，经过DeepSeek的翻译和我的试验，以下是具体的操作方法。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><div class="note note-warning">            <p>刷写BIOS有风险，请擦亮眼睛确认刷写文件是否正确，刷入错误的文件可能导致设备变砖！</p>          </div><p>首先，请将笔记本接入电源，BIOS刷写程序无法在电池供电情况下工作。<br>从<a href="https://www.mi.com/service/notebook/drivers/N57">小米官网</a>下载最新的BIOS固件，解压并运行。待文件释放完毕后，程序会提示BIOS版本相同，无法降级。<br><img src="/post/downgrade-bios/3.png"><br>先不要关闭弹窗，打开<code>%WINDIR%\Temp</code>目录，找到以<code>7zS</code>开头的一个文件夹，复制到桌面上，这是安装程序刚刚释放的文件。<br><img src="/post/downgrade-bios/4.png" alt="7zS后的字符是随机的"><br>关闭弹窗，程序会自动退出。接下来，打开<a href="https://www.catalog.update.microsoft.com/Search.aspx?q=xiaomi%20Firmware">这个网页</a>，找到下面版本号为<code>1.9.1.9</code>的固件，有两项，上面一个(<code>1c18... .cab</code>)是14寸SKU的固件，下面一个(<code>8f5a... .cab</code>)是16寸SKU的固件。选择对应你SKU的固件下载。<br>下载完成后，解压这个<code>.cab</code>文件。其中<code>wucapsule.bin</code>就是<code>0909</code>版本的固件。<br><img src="/post/downgrade-bios/5.png"><br>对于14寸SKU，请将这个文件重命名为<code>TM2307.fd</code>，对于16寸SKU，请将文件重命名为<code>TM2309.fd</code>，然后将其移动到刚刚复制到桌面上的目录中，选择覆盖源文件。<br>之后打开目录中<code>platform.ini</code>文件，将<code>[Bios_Version_Check]</code>一节下的<code>Flag=1</code>改为<code>Flag=0</code>，禁用BIOS版本检查。<br>最后运行目录中的<code>H2OFFT-Wx64.exe</code>文件，如果一切顺利，<code>New</code>下的<code>Version</code>将会显示<code>RMAMT4B0P0909</code>，并弹窗提示是否需要刷入。<br><img src="/post/downgrade-bios/6.png"><br>点击确认即可，稍后系统会自动重启，随后来到BIOS升级页面<br><img src="/post/downgrade-bios/7.png">  </p><blockquote><p>进度条走完后会黑屏一到两分钟，此为正常现象</p></blockquote><p>刷写完毕后会再次重启，降级完成，可打开小米电脑管家进行确认，会提示BIOS需要升级。<br><img src="/post/downgrade-bios/8.png"></p><blockquote><p>降级BIOS后可能会丢失Linux系统的UEFI启动条目，可自行使用相关工具（如DiskGenius）添加回来。</p></blockquote><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>最终成功安装上了Fedora 42，美滋滋～🥰<br>顺带一提，我认为Fedora<a href="https://fedoramagazine.org/anaconda-installer-redesign/">新版的Anaconda Installer</a>已经比Windows的安装器还要用户友好了，全程只有两步：选语言和划分区；而且，只要你预先留好一块空闲区域，安装器就能自动划好引导分区和btrfs subvolume，可以说连Linux小白都会安装了。</p><h1 id="注释"><a href="#注释" class="headerlink" title="注释"></a>注释</h1><section class="footnotes"><div class="footnote-list"><ol><li><span id="fn:1" class="footnote-text"><span>原帖中写的是升级，但在经过<a href="https://4pda.to/forum/index.php?showtopic=1085675&st=2760#entry136058114">确认</a>后，实际上<code>RMAMT4B0P0A0A</code>是比<code>RMAMT4B0P0909</code>更新的<a href="#fnref:1" rev="footnote" class="footnote-backref"> ↩</a></span></span></li></ol></div></section>]]></content>
    
    
    <summary type="html">RedmiBook Pro 2024的新版BIOS固件无法正常启动某些Linux发行版，本文记述了将BIOS固件降级到旧版的过程</summary>
    
    
    
    <category term="折腾记录" scheme="https://blog.kenxu.top/categories/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="linux" scheme="https://blog.kenxu.top/tags/linux/"/>
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
  </entry>
  
  <entry>
    <title>聊聊那些翻山越岭的神奇操作（上）</title>
    <link href="https://blog.kenxu.top/post/bypass-gfw-p1/"/>
    <id>https://blog.kenxu.top/post/bypass-gfw-p1/</id>
    <published>2025-03-24T11:00:00.000Z</published>
    <updated>2025-03-24T11:06:57.908Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><h1 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h1><blockquote><p>Deepseek生成，比较简略，详细内容可以查看维基百科相应词条</p></blockquote><ul><li><p><strong>TCP</strong>: 传输控制协议，提供可靠、面向连接的端到端数据传输服务，通过三次握手建立连接，具有流量控制和拥塞控制机制</p></li><li><p><strong>TLS</strong>: 安全传输层协议，为网络通信提供加密传输、身份认证和数据完整性保护，HTTPS 的核心安全基础  </p><blockquote><p>TCP和TLS的关系相当于：TCP是快递员，TLS是包裹，封装（加密）了其中的数据</p></blockquote></li><li><p><strong>DNS</strong>: 域名解析系统，通过分层分布式架构实现域名与IP地址的映射，<strong>传输内容为明文</strong>  </p></li><li><p><strong>AnyCast</strong>: 任播路由技术，将相同IP地址分配给多个节点，基于BGP路由策略将用户请求智能引导至最优服务节点，提升网络服务响应速度与可用性</p></li></ul><h1 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h1><p>一般我们聊到翻墙，想到的肯定是通过代理服务器的方式连接被封锁的境外网站，这确实是最常见和最直接的想法。但是，各位有没有想过通过防火长城本身的设计缺陷来完成翻墙的目的呢？相比起前者，这种方法难度更大，而且随时面临这失效的风险（防火长城经过近二十年的发展，许多漏洞已经被堵的死死的了）。但它们真的挺有意思的，（硬说优点的话，或许可以是：不需要额外花钱？）。本文将会探讨其中的一种可行思路，其他思路将会放到下一篇（或者是两篇？）文章中。</p><h1 id="历史"><a href="#历史" class="headerlink" title="历史"></a>历史</h1><blockquote><p>这里写的比较简略，想详细了解可自行前往维基百科查询</p></blockquote><p>互联网刚引入中国不久时，人们可以自由地访问境内外的网站。（毕竟当时能上网的人是少数）但随着技术的不断发展，有关部门逐渐意识到，需要通过某种手段对互联网进行管控，过滤掉互联网上不合适的内容。<br>防火墙刚刚建立的时候，互联网还没有“加密”这个概念，所有信息会通过明文HTTP协议发送，因此审查者只需要在适当的位置（对于GFW，通常是物理链路国内转国际的地方，一般位于北京，上海，广州，下称跨境段）布置审查设备，即可获取到所有流量的详细内容。大约20年前，人们发现维基百科遭到了干扰，具体而言，当访问我国历任领导人相关页面的时候，网页便无法加载，而与此同时其它页面又完全不受影响。这就是因为审查设备在探测到流量中的一些词条时，发送TCP RST数据包强行终止了数据包的传输。<br>与此同时，审查者还会通过DNS污染和DNS劫持的方式返回某些网站错误的DNS，进而导致客户端获取错误的IP地址，导致无法连接这些网站。<br>对于一些“行为恶劣”的网站，防火墙还会直接封锁对应的IP，当你访问这些IP时，其数据包会在跨境段被丢弃。<br>前两种方法十分初级，因此早有成熟的解决方法。对于第一种，随着互联网的不断发展，人们逐渐认识到加密的重要性。随着TLS的广泛应用，明文检测这种审查方法逐渐式微。<br>而第二种则更加简单，电脑本地存在一个叫做hosts文件的东西，其优先级高于DNS，只需要把被封锁网站的IP地址放入这个文件中，就能绕过污染和劫持访问网站。<br>对于第三种方法，虽然对封锁的IP并没有任何解决方法，但对于类似于谷歌这种拥有数百万IP的互联网企业，审查者不可能确保封锁每一个IP，总会有漏网之鱼。同时，广泛使用的AnyCast技术也使得审查者在使用这种方法时更加小心谨慎。  </p><p>随着时代的发展，防火墙也在不断地自我迭代。SNI阻断是相对新兴的审查方式。在建立TLS加密连接之前，客户端会先发送一个Client Hello数据包与服务端建立连接（TLS三次握手的第一次），其中包含了一个明文SNI(Server Name Indicator)字段，内容为访问网站的域名。  </p><pre><code class=" mermaid">sequenceDiagram    participant Client as 客户端    participant CDN as CDN/服务器    Client-&gt;&gt;CDN: 发送 ClientHello (SNI: cdn.example.com)    CDN-&gt;&gt;Client: 发送 ServerHello 和 cdn.example.com 证书    Client-&gt;&gt;CDN: 使用 cdn.example.com 公钥加密预主密钥    CDN-&gt;&gt;Client: 完成 TLS 握手    Note over Client,CDN: TLS 连接建立    Client-&gt;&gt;CDN: 发送加密的 HTTP 请求 (Host: cdn.example.com)    CDN-&gt;&gt;Client: 加密响应并发送给客户端    Note over Client,CDN: 客户端解密并接收响应</code></pre><p>也就是说，虽然审查者无法解密后续数据包中的数据，但可以通过这个字段判断用户访问的网站，进而针对被封锁的域名向用户发送RST数据包中断连接。<br>18年夏，通过修改 hosts 以连接被 GFW 屏蔽的维基百科、Pixiv等网站的方法突然失效。很快人们就<a href="https://github.com/googlehosts/hosts/issues/87">反应过来</a>问题的所在：SNI阻断。 在这之前，修改 hosts 是一个几近于零成本的翻墙方法。突然的变化意味着翻墙成本的急剧上升。眼下易于使用的翻墙手段已悉数失效，难度只有使用代理这一条路了吗？</p><h1 id="RST"><a href="#RST" class="headerlink" title="RST"></a>RST</h1><p>防火墙是怎样强制终止我们与服务器的连接的呢？想知道这一点，我们得简单了解一下TCP报文的组成结构。<br>在TCP报文中，有数个标识符可以设置，以向客户端或服务端表明连接状态。如下图：<br><img src="/post/bypass-gfw-p1/image-4.png"><br>其中SYN和ACK(也就是图中的Acknowledgement)在TCP握手中使用，这张图中的ACK位设置为1，说明这是TCP的第三次握手，连接已经建立，可以开始发送数据了。<br>先不管TCP握手的<a href="https://zhuanlan.zhihu.com/p/108504297">详细知识</a>，有没有注意到这张图中有一个名为Reset的标识符，其值为0？<br>这就是RST标识符，其作用是在服务端接收到错误或非法的数据包时，<strong>强制</strong>终止连接。与SYN，ACK类似，只要将其值置为1即可。<br>遗憾的是，TCP握手的流程并没有任何形式的加密（连接都没有建立怎么加密呢？），所以只要监听到了TCP连接的源IP、源端口、目的IP、目的端口（这些信息已经包含在三次握手中），任何人都可以伪装成服务端向我们发送伪造的RST数据包。这也正是防火墙阻断我们连接的技术：在建立TCP连接后，通过识别TLS Client Hello中的SNI信息，判断是否是黑名单网站，若是，则<strong>伪装成服务端向我们发送RST数据包，强行中断我们与真正服务端的连接。</strong>  </p><blockquote><p>这项技术不仅用于防火墙，也广泛用于国内各大云服务厂商，用于阻断未经过ICP备案的域名连接</p></blockquote><h1 id="SNI"><a href="#SNI" class="headerlink" title="SNI"></a>SNI</h1><p>我们不妨详细看看SNI这个字段，首先，SNI的作用是什么？<br>很多时候，一台服务器上可能同时部署有多个网站，同时，对于AnyCast技术，单IP可能对应着许多网站。在这些情况下，服务端需要知道用户究竟想访问哪个网站<strong>以便发送正确的证书进行后续的加密</strong>。SNI便应运而生，由于这个字段是Client Hello中的明文字段，因此不需要解密数据包即可判断出用户想要访问的网站，进而正确地路由流量。<br>这里存在着两个有意思的点：  </p><ol><li>从这段文字可以看出来，在单IP对应多网站的情况下才需要SNI字段，也就是说，（理论上）如果一个IP只对应一个网站，SNI字段就不是必须的了。  </li><li>在IETF发布的有关TLS扩展的<a href="https://www.rfc-editor.org/rfc/rfc6066#section-3">RFC</a>中，对于SNI的描述是这样的  <blockquote><p>In order to provide any of the server names, clients MAY include an<br>extension of type “server_name” in the (extended) client hello.</p></blockquote>也就是说，这个扩展其实是可选的，去除这个字段也不会影响TLS协议的完整性（虽然目前所有的客户端都会发送这个扩展）</li></ol><p>那落实到实践上，可以怎么办呢？</p><p>首先，利用某些服务端不检查SNI的特性，我们完全可以人为去除Client Hello信息中的SNI字段，进而绕过审查。最著名的一个网站就是<code>pixiv.net</code>，Pixiv虽然使用了Cloudflare的CDN，这些CDN的IP也确实要求SNI字段，但Pixiv的源服务器可以直接访问且不检查SNI字段。因此只要手动指定Pixiv网站的IP为源服务器的IP并去除SNI字段即可做到“直连”P站，目前一些宣传可以直连Pixiv的应用使用的就是这种技术。</p><p>那对于单IP对应多网站的场景，这种方法就完全不可用了吗？也不尽然。SNI只在TLS握手阶段使用以选择正确的TLS证书，建立加密连接后，服务器和CDN提供商会解密并根据使用请求头中的Host字段来路由流量。假如我们将SNI字段设置成该服务器或CDN供应商托管的，未被封锁的网站，而保持HTTP请求头中Host字段为我们想要访问的网站，我们便可以在TLS握手流程中向GFW展示这个正常网站的连接过程，而在结束握手后的加密连接中，通过Host字段使服务器或CDN提供商将流量路由到我们真正想访问的网站。<br>用一张图表示这个略显复杂的流程：  </p><pre><code class=" mermaid">sequenceDiagram    participant Client as 客户端    participant CDN as CDN/服务器    participant Backend as blocked.example.com 后端服务    Client-&gt;&gt;CDN: 发送 ClientHello (SNI: cdn.example.com)    CDN-&gt;&gt;Client: 发送 ServerHello 和 cdn.example.com 证书    Client-&gt;&gt;CDN: 使用 cdn.example.com 公钥加密预主密钥    CDN-&gt;&gt;Client: 完成 TLS 握手    Note over Client,CDN: TLS 连接建立    Client-&gt;&gt;CDN: 发送加密的 HTTP 请求 (Host: blocked.example.com)    CDN-&gt;&gt;CDN: 解密 HTTP 请求头    CDN-&gt;&gt;Backend: 根据 Host 字段路由请求    Backend-&gt;&gt;CDN: 处理请求并返回响应    CDN-&gt;&gt;Client: 加密响应并发送给客户端    Note over Client,CDN: 客户端解密并接收响应</code></pre><p>举个例子，对于Steam社区，其网址<code>steamcommunity.com</code>在GFW的黑名单中，但与其共享同一IP的<code>www.valvesoftware.com</code>的V社官网则不在黑名单中。<br><img src="/post/bypass-gfw-p1/image.png" alt="这两个网址共享同一个IP"><br>因此，当我们想要连接Steam社区的时候，只要把SNI字段的值修改为V社官网的域名，就可以绕过GFW的封锁。<br>这种修改SNI方法其实有个正式的名称，叫做<a href="https://en.wikipedia.org/wiki/Domain_fronting">域前置</a>，以前我一直没理解这个“前置”是什么意思，现在终于明白了，所谓域前置，就是<strong>在Client Hello包的SNI字段中，使用一个正常的域名来“前置”（替代）被封锁网站的域名</strong>，从而达成混淆审查者的目的。  </p><p>使用这种方法，我们便能访问一系列被封锁但支持域前置的网站了，例如：  </p><ul><li>Twitch</li><li>Discord</li><li>Pixiv</li><li>Epic</li><li>Uplay</li><li>Onedrive</li><li>…</li></ul><p>由于修改了SNI字段，GFW针对部分网站的随机丢包策略也随之失效，具体表现为一些连接不稳定的网站也能随时随地稳定连接了，例如Github和Steam。<br>讲句题外话，各位应该听说过<code>Watt Toolkit</code>(原名<code>Steam++</code>)吧，该软件使用的加速方法（其称之为“本地代理”）就是上文所说的域前置。  </p><h1 id="抓包"><a href="#抓包" class="headerlink" title="抓包"></a>抓包</h1><p>这里还是以<code>steamcommunity.com</code>为例子，使用Wireshark进行抓包分析<br>我们先模拟一个正常用户的行为，直接在网址栏中输入域名，回车，看看会发生什么<br><img src="/post/bypass-gfw-p1/1.png"><br>可以看到，这里DNS首先返回了<code>steamcommunity.com</code>的地址，是<code>199.59.148.147</code>，查询可知，这个IP地址归属于Twitter，很明显不是V社的IP，这说明我们遭到了DNS污染<br><img src="/post/bypass-gfw-p1/image-3.png" alt="这个/24的IP段都是Twitter的"><br>接下来客户端开始TCP握手，向这个错误的IP发送包含SYN的报文以建立连接，不过我们并没有看到来自服务器的ACK确认报文。使用Traceroute工具可知，我们对这个IP发送的数据包在跨境段被丢弃了，因此服务器（无论是真是假）永远都不可能收到我们的连接请求<br><img src="/post/bypass-gfw-p1/4.png">  </p><p>随后，我们从其他途径获取到<code>steamcommunity.com</code>对应的正确IP，再次进行测试。<br><img src="/post/bypass-gfw-p1/2.png"><br>可以发现，首先我们顺利完成了TCP三次握手的流程，开始了TLS握手。在发送了包含<code>steamcommunity.com</code>SNI的Client Hello数据包（深蓝色）后，立刻就收到了来自GFW的RST数据包（红色），连接终止，网页显示“连接已重置”。  </p><p>接下来，我们通过工具修改SNI为人畜无害的<code>www.valvesoftware.com</code>，再加载一遍网页试试，见证奇迹的时刻：<br><img src="/post/bypass-gfw-p1/3.png"><br>果不其然，在修改完SNI字段之后，我们成功收到了来自服务端的Server Hello数据包，随后顺利完成了TLS握手并建立了加密连接，说明我们确实绕过了GFW的封锁。</p><h1 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h1><p>听上去域前置是不是挺厉害？那为什么没有被广泛采用呢？<br>首先，并不是所有网站都支持域前置，甚至可以说，支持域前置的网站是少数。因为许多大型的CDN提供商，如Cloudflare, Cloudfront, Azure, 都主动拒绝了使用域前置进行的连接。考虑到这几家CDN公司的巨大市场份额，这意味着大部分被封锁的网站都是无法通过域前置进行访问的。    </p><blockquote><p>究其原因，一方面，这项技术也可能被用于<a href="https://en.wikipedia.org/wiki/Domain_fronting#Cyberattacks">网络攻击</a>。另一方面，也有人猜测其背后有<a href="https://en.wikipedia.org/wiki/Domain_fronting#Reactions">政府势力的干预</a>。</p></blockquote><p>另一方面，域前置涉及到对SNI字段的修改。虽然SNI字段并未加密，但TLS握手结束时，客户端会发送一个<code>Finished</code>消息，其中包含对先前握手信息的校验值。因此，如果直接修改SNI字段信息，服务端会在校验完Finished信息后终止连接。也就是说，在不对浏览器或应用程序源代码进行修改的情况下，需要对数据包进行劫持以达成修改SNI的目的（也就是俗称的<a href="https://zh.wikipedia.org/zh-hans/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB">MitM</a>）。了解过<a href="https://en.wikipedia.org/wiki/Public-key_cryptography">非对称加密</a>的小伙伴们应该清楚，为了达成这一点，需要让代理软件充当中间人的角色，劫持并对流量进行加解密操作。暂且不提对软件和开发者的信任问题，这需要你的设备信任代理软件生成的根证书，否则现代浏览器都会认为遭到了网络攻击从而拒绝连接。<br><img src="/post/bypass-gfw-p1/image-1.png" alt="未信任根证书的情况下进行连接，被网站的HSTS设置阻拦"><br>这不仅提升了操作的复杂度，在部分系统中（如Android），操作系统的受信任根证书存储区也是不被允许的，更不用提部分网站启用的<a href="https://en.wikipedia.org/wiki/Certificate_Transparency">证书透明度</a>和<a href="http://fiddler.wikidot.com/certpinning">证书锁定</a>技术，都从根本上阻止了通过域前置进行连接。</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p><code>Watt Toolkit</code>，或者说<code>Steam++</code>的老用户不知道记不记得，曾经这个工具是可以加速Pixiv和Discord的。大约一年前的某次更新中，这两个网站被移除了。<br>为什么？请看<a href="https://github.com/BeyondDimension/SteamTools/issues/3060#issuecomment-1837516432">作者的回复</a>：<br><img src="/post/bypass-gfw-p1/image-2.png">  </p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p>几乎都以超链接形式给出，除此之外还有一些关于TCP报文的相关知识：<br><a href="https://blog.csdn.net/Chaman1378/article/details/107160327">https://blog.csdn.net/Chaman1378/article/details/107160327</a><br><a href="https://www.cnblogs.com/n0rmally/p/18789097">https://www.cnblogs.com/n0rmally/p/18789097</a><br>感谢<a href="https://grok.com/">Grok</a>绘制的两张Mermaid图</p>]]></content>
    
    
    <summary type="html">越过长城，走向世界</summary>
    
    
    
    <category term="研究分享" scheme="https://blog.kenxu.top/categories/%E7%A0%94%E7%A9%B6%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="network" scheme="https://blog.kenxu.top/tags/network/"/>
    
  </entry>
  
  <entry>
    <title>为旧打印机添加无线打印和扫描支持</title>
    <link href="https://blog.kenxu.top/post/wireless-support-for-printers/"/>
    <id>https://blog.kenxu.top/post/wireless-support-for-printers/</id>
    <published>2024-10-25T07:00:00.000Z</published>
    <updated>2024-10-27T15:12:15.190Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>虽然现在市面上的无线打印机种类繁多，但我认为存在以下几个痛点：  </p><ul><li>打印机并非快消品，一般用坏了才会考虑买一台新的，三四年前或更久之前购买的打印机很多不具备无线打印功能，为了无线打印功能专门购买一台新的也不划算。  </li><li>大部分标榜具有无线功能的打印机只能通过厂商专有的软件实现无线有关的功能，有些甚至用的是微信小程序<del>喜欢我打印前先看5秒广告吗</del>。借助这些软件打印时设计的隐私问题让我有所顾虑，同时，每台设备都要下载软件并进行配置，十分麻烦。<br>尤其是第二点，几乎所有的手机和电脑操作系统都自带无线打印协议（<a href="https://en.wikipedia.org/wiki/Internet_Printing_Protocol">IPP</a>）的支持，但很多厂商却仍然采用自己研发的私有协议，甚是奇怪。</li></ul><p>为了解决这些痛点，我们可以在投入较少资金（100元以下）的前提下，让旧打印机也能实现无线打印，同时<strong>调用系统自带功能</strong>进行无线打印，无需安装第三方软件。<br>与先前的文章一样，本文主要在Linux系统上操作，需要具备一定的Linux基础。</p><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h1><p><del>首先你需要一台打印机</del></p><p>整体的思路如下：我们需要一台24小时开机的设备，将打印机连接到这台设备上，在这台设备上安装打印机驱动，并通过这台设备将打印机共享至局域网内，只要连接到与设备相同的局域网即可使用无线打印和扫描功能。</p><h2 id="购买硬件"><a href="#购买硬件" class="headerlink" title="购买硬件"></a>购买硬件</h2><p>如果你已经是NAS或Homelab玩家那直接利用现有的服务器即可。但对于手头没有硬件设备的人来说，有没有一种设备，它功耗低，噪音小，价格便宜，同时还不占地方呢？<br>在经过简单的对比后，我最终选择了目前热度依然不低的斐讯N1盒子。它拥有上述说到的所有优点。同时，它还有一个有线网口以及支持5GHz WiFi的无线网卡。闲鱼搜索发现<strong>价格低至70</strong>（这个卖家还送电源线和网线），果断拿下。<br><img src="/post/wireless-support-for-printers/1.jpg">  </p><blockquote><p>由于社区的热情，N1盒子从一个原本只能用来挖矿的设备变成了可以刷openWRT,istoreOS,OpenMediaVault,Armbian,Android TV等多种系统，可玩性超强的设备。本人接触N1盒子也才一个月左右，更多玩法有待我慢慢挖掘。</p></blockquote><blockquote><p>购买的时候不必追求全新未拆封或是带不带遥控器（通电自动开机），在功能一切正常的情况下，买便宜的即可。</p></blockquote><h2 id="系统安装"><a href="#系统安装" class="headerlink" title="系统安装"></a>系统安装</h2><p>系统方面，我选择的是轻量化的Linux发行版<a href="https://www.armbian.com/">armbian</a>。由于N1盒子是arm架构的设备，因此不能像x86设备那样使用通用镜像进行安装。这里我们使用<a href="https://github.com/ophub">ophub</a>大佬适配的镜像进行安装，以下是仓库地址：<a href="https://github.com/ophub/amlogic-s9xxx-armbian">https://github.com/ophub/amlogic-s9xxx-armbian</a><br>如果你的设备到手就是Armbian，你可以跳过这节直接进入软件安装部分，我的设备到手是istoreOS，所以需要刷成Armbian，以下是较为简略的操作步骤：<br>README中包含了设备与镜像名称的对应表格，查表可知设备CPU具体型号是<code>s905d</code>，应该使用<code>amlogic_s905d.img</code>结尾的镜像<br><img src="/post/wireless-support-for-printers/2024-10-14T232645.png"><br>接下来就可以去release中选择镜像了。不过需要注意一点的是：Armbian系统对于N1盒子的无线网卡的支持似乎欠佳，根据我自己的使用体验以及<a href="https://github.com/ophub/amlogic-s9xxx-armbian/issues?q=is:issue+N1+wifi">issue区的搜索结果</a>，部分版本可能会出现无线网卡无法扫描和连接WiFi的问题。根据<a href="https://github.com/ophub/amlogic-s9xxx-armbian/issues/2555">这条issue</a>，我最终选择了2024年9月发布的，基于<code>Ubuntu 24.04</code>，内核版本号为<code>6.6.50</code>的系统镜像，文件名为<a href="https://github.com/ophub/amlogic-s9xxx-armbian/releases/download/Armbian_noble_save_2024.09/Armbian_24.11.0_amlogic_s905d_noble_6.6.50_server_2024.09.10.img.gz">Armbian_24.11.0_amlogic_s905d_noble_6.6.50_server_2024.09.10.img</a>，使用下来也确实没发现什么问题。如果你对无线功能有需求，可以下载这个镜像。  </p><blockquote><p>根据上面提到的issue区的评论，10月份发布的版本又有无线方面的问题，所以安装完系统后记得不要升级内核。（其实我就是从10月份版本降级下来的）<br><img src="/post/wireless-support-for-printers/2024-10-14T234630.png"></p></blockquote><p>下载好镜像后，使用<code>rufus</code>或<code>etcher</code>等工具将镜像文件刷入U盘，不多赘述。</p><p>接下来，将刷写好镜像的U盘插入<strong>HDMI接口左侧第一个USB接口</strong>，插电，机器会自动从U盘中加载系统。<br>等待一段时间后，在路由器后台查看机器分配到的IP地址，使用<code>ssh root@ip</code>连接到机器，默认密码是<code>1234</code>，登录后会自动进入系统初始化流程，自行设置用户名和密码等设置项即可。<br>完成后即可连接到shell<br><img src="/post/wireless-support-for-printers/2024-10-22T113550.png"><br>但此时系统还在U盘中，我们需要输入以下指令安装系统：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">armbian-install<br></code></pre></td></tr></table></figure><p>机器型号选择<code>101</code>，文件系统随意，接下来等待一段时间，系统会被安装到内置的EMMC闪存芯片中。  </p><p>安装完成后，拔掉电源和U盘，再插回电源，机器就会进入新安装的系统。最后一步，我们需要连接到WiFi以便接下来进行无线共享，在终端输入：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">armbian-config<br></code></pre></td></tr></table></figure><p>选择<code>network</code>-<code>WiFi</code>，选择想要连接到的WiFi并激活即可。<br><img src="/post/wireless-support-for-printers/2024-10-22T114810.png">  </p><blockquote><p>如果<code>network</code>菜单里没有<code>WiFi</code>选项，则很有可能是无线网卡驱动存在问题，可以更换镜像试试。但需要注意一点：如果U盘中的系统与EMMC中的系统一样，机器不会从U盘启动。因此，如果你想降级到先前的版本，你需要先刷入另一个系统，再刷回低版本的系统（例如你想从10月的Ubuntu降级到9月的Ubuntu，你需要先刷入另一个发行版，再刷回9月的Ubuntu）</p></blockquote><h1 id="软件安装"><a href="#软件安装" class="headerlink" title="软件安装"></a>软件安装</h1><h2 id="驱动"><a href="#驱动" class="headerlink" title="驱动"></a>驱动</h2><p>与Windows上的打印机共享不同，CUPS使用IPP协议共享打印机，这意味着只有负责共享的设备需要安装打印机驱动，其他设备只要支持IPP打印协议即可直接打印，无需重复安装驱动。<br>所以接下来我们需要在armbian上安装驱动。这一步可能比较困难，虽然大部分打印机厂商都会提供Linux驱动，但绝大多数只会提供x86架构下的驱动。如果你的打印机厂商提供了arm架构的Linux驱动，那这段可以不用看了。我使用的打印机是爱普生的，Linux下的驱动只有<code>.deb</code>和<code>.rpm</code>的x86架构安装包，既没有arm架构的安装包也没有源代码供用户编译。如果你遇到了相同的问题，可以参考以下两种解决思路：  </p><ol><li><p><strong>从“国产操作系统”拿</strong>：随着国产化的逐步推进，许多硬件厂商也都推出了支持“国产操作系统”的驱动程序，除了x86架构以外，一般还会适配arm,mips甚至loongarch架构。通过<a href="https://www.epson.com.cn/campaign/uos/index.html">搜索可知</a>，爱普生对UOS操作系统进行了适配，其中就有arm架构的驱动程序。UOS本身基于Debian，Ubuntu也是Debian系的，因此直接安装这个驱动即可。  </p><blockquote><p>虽然“国产操作系统”也是Linux发行版，但以我的打印机型号<code>EPSON L3210</code>为例，直接搜类似<code>epson L3210 linux arm drivers</code>是得不到任何结果的，只有在<a href="https://www.epson.com.cn/services/search.html">爱普生中国官网</a>搜索打印机型号才能找到“国产操作系统”的驱动安装包。<br><img src="/post/wireless-support-for-printers/2024-10-24T230558.png"></p></blockquote><p>安装这类驱动并不容易，以爱普生的驱动为例，软件包中有一个依赖项<code>deepin-elf-verify</code>。这个依赖项只能在deepin&#x2F;UOS的软件仓库中找到。让人气愤的是，这个依赖项没有任何实际作用，仅仅是为了确认安装这个软件包的平台是deepin&#x2F;UOS。更详细的内容可以参见<a href="https://zhul.in/2021/11/20/what-is-deepin-elf-verify/">这篇博客</a>。所以接下来我们要去除这个依赖项，方法来自于<a href="https://blog.csdn.net/qq_27818541/article/details/131713280">这里</a>：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> <span class="hljs-built_in">mkdir</span> epson  <br><span class="hljs-built_in">sudo</span> dpkg-deb -R signed_epson-inkjet-printer-202101w_1_0_1_arm64.deb epson <span class="hljs-comment">#解包  </span><br><span class="hljs-built_in">sudo</span> vim epson/DEBIAN/control  <br><span class="hljs-comment"># 去除 Depend: deepin-elf-verify (&gt;= 0.0.16.7-1) 一行，保存退出  </span><br><span class="hljs-built_in">sudo</span> dpkg-deb -b epson/ epson.deb  <br><span class="hljs-built_in">sudo</span> dpkg -i epson.deb<br></code></pre></td></tr></table></figure><p>注意这里使用root用户进行解包和打包很重要，否则安装时驱动程序文件的权限会变成打包时的用户，CUPS调用时会报错：<code>File \&quot;/usr/lib/cups/filter/epson-inkjet-printer-202101w\&quot; has insecure permissions (0100755/uid=1000/gid=1000).</code><br>最后，还需要安装一个依赖项：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install libjpeg62<br></code></pre></td></tr></table></figure><p>否则打印时会出现<code>error while loading shared libraries: libjpeg.so.62: cannot open shared object file: No such file or directory</code>的报错，WebUI会提示<code>Filter Error</code></p></li><li><p><strong>试着在软件源里找找</strong>：如果上面的方法没有解决问题的话，你还可以尝试在软件源里找找有没有适用于你所使用的打印机的驱动。以Ubuntu为例：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">apt search <span class="hljs-string">&quot;printer driver&quot;</span><br></code></pre></td></tr></table></figure><p>应该会列出一堆打印机驱动，看看有没有与你的型号相符的，或者有没有与你打印机厂商相符的<br><img src="/post/wireless-support-for-printers/2024-10-24T232505.png" alt="部分搜索结果"><br>还是以我的打印机型号<code>EPSON L3210</code>为例，可以安装搜索结果中的<code>printer-driver-escpr</code>软件包，里面包含了许多爱普生喷墨打印机的驱动，其他品牌的打印机同理。<br><img src="/post/wireless-support-for-printers/2024-10-24T232823.png" alt="apt info输出的结果"></p></li></ol><h2 id="CUPS"><a href="#CUPS" class="headerlink" title="CUPS"></a>CUPS</h2><p>我们使用CUPS管理与共享打印机。首先安装CUPS：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install cups avahi-daemon<br></code></pre></td></tr></table></figure><p>接下来我们要调整CUPS的配置文件，否则无法进入管理页面，打开配置文件<code>/etc/cups/cupsd.conf</code>并作出以下修改：  </p><ul><li>将<code>Listen localhost:631</code>改为<code>Listen 0.0.0.0:631</code></li><li>将<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">Location</span> /&gt;</span><br>  Order allow,deny<br><span class="hljs-tag">&lt;/<span class="hljs-name">Location</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">Location</span> /<span class="hljs-attr">admin</span>&gt;</span><br>  AuthType Default<br>  Require user @SYSTEM<br>  Order allow,deny<br><span class="hljs-tag">&lt;/<span class="hljs-name">Location</span>&gt;</span><br></code></pre></td></tr></table></figure>改为<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">Location</span> /&gt;</span><br>  Order allow,deny<br>  Allow all<br><span class="hljs-tag">&lt;/<span class="hljs-name">Location</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">Location</span> /<span class="hljs-attr">admin</span>&gt;</span><br>  AuthType Default<br>  Require user @SYSTEM<br>  Order allow,deny<br>  Allow all<br><span class="hljs-tag">&lt;/<span class="hljs-name">Location</span>&gt;</span><br></code></pre></td></tr></table></figure></li></ul><p>在CUPS网页端管理打印机时，需要输入root的账号密码。如果你想让自己也可以管理打印机，或者你压根就没配置root账户的密码，你可以将自己添加到<code>lpadmin</code>组，这样在管理打印机时输入自己的账号密码即可：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> usermod -aG lpadmin $(<span class="hljs-built_in">whoami</span>)<br></code></pre></td></tr></table></figure><p>考虑到我们仅仅是在内网使用，这里权限放的比较开，如果你想更加精细地调整访问权限，可以看看<a href="https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/configuring_and_using_a_cups_printing_server/index#installing-and-configuring-cups_configuring-printing">RedHat手册</a>  </p><p>最后重启CUPS服务  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> systemctl restart cups.service<br></code></pre></td></tr></table></figure><p>局域网内浏览器打开<code>http://IP:631</code>即可<br><img src="/post/wireless-support-for-printers/2024-10-24T232952.png" alt="CUPS WebUI">  </p><blockquote><p>考虑到CUPS在半个月前爆出的<a href="https://www.evilsocket.net/2024/09/26/Attacking-UNIX-systems-via-CUPS-Part-I/">严重RCE漏洞</a>，强烈建议不要将CUPS服务开放至公网。</p></blockquote><h1 id="添加打印机并设置共享"><a href="#添加打印机并设置共享" class="headerlink" title="添加打印机并设置共享"></a>添加打印机并设置共享</h1><p>完成了上面两部之后，这一步就非常简单了，在CUPS的webUI一步一步操作就行了。<br>点击<code>Administration</code>，网页会自动跳转至HTTPS，忽略证书警告（因为是一张自签证书），输入root账户和密码（如果做了上文的配置，输入自己的账号密码也行）<br><img src="/post/wireless-support-for-printers/2024-10-25T105815.png"><br>点击<code>Add Printer</code>，选择你的打印机，点击<code>Continue</code>。下一页中的<code>Name</code>和<code>Description</code>可以随意修改，记得把<code>Share This Printer</code>勾选上<br><img src="/post/wireless-support-for-printers/2024-10-25T113846.png"><br>下一页需要选择打印机驱动，如果你的驱动安装正确应该会显示在<code>Model</code>中，直接选择正确的型号即可<br><img src="/post/wireless-support-for-printers/2024-10-25T114047.png">  </p><blockquote><p>如果你是按照上文所说的第二种思路安装的驱动，又没有找到完全符合型号的驱动，可以试试名称相近的驱动。以我的打印机为例，虽然型号是<code>L3210</code>，但根据我的实测，选择<code>L3110</code>的驱动也可以正常进行打印。  </p></blockquote><p>点击<code>Add Printer</code>，打印机就添加成功了。</p><p>接下来，你可以去<code>Printers</code>页面，点击刚刚添加的打印机，选择<code>Print Test Page</code>，如果配置正确，应该会打印出测试页。<br><img src="/post/wireless-support-for-printers/2024-10-25T114617.png"></p><p>至此，打印机添加完成，并且已经共享至局域网。对于Windows系统，打开系统设置-蓝牙与其他设备-打印机和扫描仪，点击“添加设备”按钮，应该立刻就能看到共享出来的打印机，添加即可。<br><img src="/post/wireless-support-for-printers/2024-10-25T115319.png" alt="显示类型是IPP打印机"><br>Android系统，任意应用打开文档后，选择应用内自带的打印即可，会自动发现打印机  </p><div class="group-image-container"><div class="group-image-row"><div class="group-image-wrap"><img src="/post/wireless-support-for-printers/2.jpg" alt="原生打印页面UI"></div><div class="group-image-wrap"><img src="/post/wireless-support-for-printers/3.jpg" alt="HyperOS适配的UI"></div></div></div>  <p>iOS&#x2F;iPadOS同理（其实Airprint就是<a href="https://en.wikipedia.org/wiki/List_of_printing_protocols#Wireless_protocols">基于IPP的</a>）<br><img src="/post/wireless-support-for-printers/4.png"></p><h1 id="配置无线扫描"><a href="#配置无线扫描" class="headerlink" title="配置无线扫描"></a>配置无线扫描</h1><p>比起打印，无线扫描的配置要简单得多。没有繁琐的驱动安装与配置流程，只需要安装<a href="http://www.sane-project.org/">SANE</a>和通过AirScan&#x2F;eSCL协议共享的服务端即可。听上去有点复杂？Github上已经有现成的项目整合了这两部分，仓库地址：<a href="https://github.com/SimulPiscator/AirSane">https://github.com/SimulPiscator/AirSane</a><br>安装流程很简单，就是安装依赖+拉取源代码+编译，跟着README走就可以了，以下是安装流程的搬运，更详细的配置和troubleshooting请参见README。  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 编译与安装</span><br><span class="hljs-built_in">sudo</span> apt-get install libsane-dev libjpeg-dev libpng-dev  <br><span class="hljs-built_in">sudo</span> apt-get install libavahi-client-dev libusb-1.*-dev  <br><span class="hljs-built_in">sudo</span> apt-get install git cmake g++  <br>git <span class="hljs-built_in">clone</span> https://github.com/SimulPiscator/AirSane.git  <br><span class="hljs-built_in">mkdir</span> AirSane-build &amp;&amp; <span class="hljs-built_in">cd</span> AirSane-build  <br>cmake ../AirSane  <br>make &amp;&amp; <span class="hljs-built_in">sudo</span> make install  <br><span class="hljs-comment"># 设置权限，最简单的办法是安装sane-utils包  </span><br><span class="hljs-built_in">sudo</span> apt-get install sane-utils  <br><span class="hljs-comment"># 测试能否列出扫描仪  </span><br><span class="hljs-built_in">sudo</span> -u saned scanimage -L  <br><span class="hljs-comment"># 启动服务并设置开机自启  </span><br><span class="hljs-built_in">sudo</span> systemctl <span class="hljs-built_in">enable</span> airsaned --now<br></code></pre></td></tr></table></figure><p>有一点需要注意，<code>armbian</code>似乎将systemd的启动超时配置成了10秒（我还额外确认了Homelab上安装的Debian系统，是默认的90秒），对于N1盒子这种性能孱弱的机器，最后一步可能会因为超时而启动失败。<br>有两种解决方法，一是直接将默认的超时时间改回90秒，打开<code>/etc/systemd/system.conf</code>并将<code>DefaultTimeoutStartSec=10s</code>改为<code>DefaultTimeoutStartSec=90s</code>。<br><img src="/post/wireless-support-for-printers/2024-10-25T223922.png"><br>二是修改<code>airsaned</code>的<code>systemd</code>文件，<code>sudo systemctl edit airsaned.service --full</code>打开配置文件，再在<code>[Service]</code>下加入一项<code>TimeoutStartSec=900</code>。<br><img src="/post/wireless-support-for-printers/2024-10-25T224204.png"><br>无论是哪一种方法，最后都不要忘记：  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> systemctl daemon-reload  <br><span class="hljs-built_in">sudo</span> systemctl restart airsaned.service<br></code></pre></td></tr></table></figure><p>至此安装完成，现在你就可以用浏览器打开<code>http://IP:8090</code>，在网页端进行扫描了。Windows和macOS系统也可以在系统设置中添加扫描仪之后，通过系统自带的APP进行扫描。<br><img src="/post/wireless-support-for-printers/2024-10-25T230251.png" alt="Windows上的扫描应用，没有预装，需要自行去Microsoft Store里装一下"></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>现在，你应该能有一个很舒服的打印与扫描体验了。只花了70块钱就实现了无线打印和扫描功能，还得到了一个十分能折腾的N1盒子。  </p><blockquote><p>本文于10&#x2F;11动工，两周之后的10&#x2F;25终于完成了，<del>真是具有松弛感的更新频率呢</del></p></blockquote>]]></content>
    
    
    <summary type="html">现在购买的新打印机大都带有无线支持，但这类打印机价格较高，而且其中部分只能通过厂商专有的软件使用无线功能。本文记述了如何用少于100元为打印机添加无线支持，同时客户端无需安装第三方软件就能进行无线打印和扫描。</summary>
    
    
    
    <category term="折腾记录" scheme="https://blog.kenxu.top/categories/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
    <category term="homelab" scheme="https://blog.kenxu.top/tags/homelab/"/>
    
  </entry>
  
  <entry>
    <title>从零开始的博客搭建指北-篇三：公网访问</title>
    <link href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p3-access-from-internet/"/>
    <id>https://blog.kenxu.top/post/how-to-create-your-own-blog-p3-access-from-internet/</id>
    <published>2024-09-22T13:30:13.000Z</published>
    <updated>2024-09-24T05:55:40.057Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><p>接上一篇：<a href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p2-installation-and-configuration/">从零开始的博客搭建指北-篇二：安装与配置</a></p><h1 id="推向公网"><a href="#推向公网" class="headerlink" title="推向公网"></a>推向公网</h1><h2 id="Netlify配置"><a href="#Netlify配置" class="headerlink" title="Netlify配置"></a>Netlify配置</h2><p>根据<a href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p1-preparation/#Serverless%E5%B9%B3%E5%8F%B0%E9%80%89%E6%8B%A9">第一篇文章中得出的结论</a>，我们使用<a href="https://www.netlify.com/">Netlify</a>平台进行部署  </p><p>进入官网，点击右上角登录，选择使用Github登录<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-22T215120.png"><br>之后会出现授权Github仓库的界面，根据最小权限原则，个人建议只选择存放博客静态文件的仓库<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/1.png"></p><p>进入主页，点击<code>Add new site</code>，再点击<code>Import an existing project</code><br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-22T215856.png"><br>接下来一路傻瓜式操作：<br>选Github<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-22T220041.png"><br>选仓库<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-22T220151.png"><br>所有参数全部默认<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-22T220332.png"><br>最后划到页面最底下，点击部署按钮，等待即可  </p><p>一分钟以内，你的博客就会部署完成，同时Netlify还会为你的博客分配一个默认的域名：<code>xxx.netlify.app</code>。<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2.png">  </p><h2 id="自定义域名"><a href="#自定义域名" class="headerlink" title="自定义域名"></a>自定义域名</h2><p>此时你的博客已经可以通过公网进行访问了，但为了提升在中国大陆地区的可访问性，我们还需要绑定自定义域名。  </p><blockquote><p>你既可以将购买的域名托管在Netlify上，也可以只将子域名（例如<code>blog.example.com</code>）通过CNAME解析的方式接入Netlify，由于我的域名已经托管在其他平台，同时考虑到我还要为其他项目配置不同的域名，我选择了CNAME的接入方式</p></blockquote><p>点击刚刚创建的项目，再点击左侧边栏的<code>Domain management</code><br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/3.png"><br>点击下面的<code>Add a domain</code>，填入你的自定义域名<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/4.png"><br>点击<code>Verify</code>，Netlify会提示你域名已被注册，点击<code>Add subdomain</code><br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/5.png"><br>之后会回到添加域名的界面，此时子域名已经被添加到Netlify，但我们还没有配置DNS解析，点击域名右侧的<code>Awaiting External DNS</code><br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/6.png"><br>之后会提示你添加一个指向Netlify分配给你的域名的CNAME解析记录，去你的域名解析服务商那里添加即可<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/7.png"><br>添加完成后，划到页面的最底下，点击<code>Verify DNS Configuration</code>，如果你添加的DNS记录是正确的，点击后应该会出现验证通过的提示<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/8.png"><br>此时Netlify会为你的自定义域名签发证书，耐心等待5-10分钟后，你就可以用自定义域名访问博客了！</p><h1 id="配置评论区"><a href="#配置评论区" class="headerlink" title="配置评论区"></a>配置评论区</h1><h2 id="配置Twikoo"><a href="#配置Twikoo" class="headerlink" title="配置Twikoo"></a>配置Twikoo</h2><p>细心的你想必已经发现了，无论是Hexo生成的页面，还是Netlify拉取的内容，都是一些静态文件，换句话来说，我们的博客是一个纯静态的网站，无法展示动态的内容，例如评论区。因此，我们需要借助其他项目将评论区接入到我们的博客中。</p><p>这里我选用的是<a href="https://twikoo.js.org/">Twikoo</a>，该项目原本基于腾讯云开发，不过现在也支持在多种平台上进行部署。你可以将整个项目部署在<a href="https://www.mongodb.com/products/platform/atlas-database">MongoDB Atlas</a>和Netlify上，同样全程不花一分钱。<br>详细部署流程请直接参考<a href="https://twikoo.js.org/backend.html#netlify-%E9%83%A8%E7%BD%B2">官方文档</a>，设置自定义域名的流程与上文相同，本文不再赘述。</p><blockquote><p>使用Netlify部署，可以将主题配置文件中<code>region: ap-shanghai</code>注释掉</p></blockquote><p>部署完成后，可以点击发送键右下角的小齿轮进行进一步配置，推荐先设置<code>HIDE_ADMIN_CRYPT</code>参数。这样默认情况下会隐藏设置入口，只有在昵称一栏输入设置的参数值之后才会显示小齿轮图标。</p><h2 id="发送图片"><a href="#发送图片" class="headerlink" title="发送图片"></a>发送图片</h2><p>为了让访客能在我们的评论区中发送图片，我们需要配置图床。这里使用<a href="https://sm.ms/">sm.ms</a>提供的图床服务。<br>方法也很简单，进入主页后点击<code>sign up</code>注册即可<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T001332.png"><br>注册完成后点击<code>user</code>-<code>Dashboard</code>，再点击左侧边栏的<code>API Token</code>，点击<code>Generate Secret Token</code>生成一个令牌，再将令牌填入Twikoo设置中的<code>IMAGE_CDN_TOKEN</code>一栏即可<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/9.png"></p><h2 id="反垃圾"><a href="#反垃圾" class="headerlink" title="反垃圾"></a>反垃圾</h2><p><del>虽然大概率不会有人评论</del>为了不让评论区出现垃圾广告与恶意言论，我们还是来配置一下评论区反垃圾服务  </p><p>这里我们使用<a href="https://akismet.com/">Akismet</a>提供的服务，详细教程还是请参考<a href="https://twikoo.js.org/faq.html#%E4%BD%BF%E7%94%A8-akismet-%E5%8F%8D%E5%9E%83%E5%9C%BE%E6%9C%8D%E5%8A%A1">官方文档</a>。这里说一个要点：注册与选择计划完成之后，网站会问你愿意为Personal订阅付费多少，这里可以将滑条向左滑至零元，这样就可以免费使用反垃圾服务了。<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/10.png">  </p><blockquote><p>免费计划要求网站不能出现广告与推销内容（图上也写的很清楚了），各位还是自觉遵守一下吧。</p></blockquote><h1 id="SEO"><a href="#SEO" class="headerlink" title="SEO"></a>SEO</h1><p>至此，我们的博客已经搭建完成了。但是，我们肯定希望有人来阅读我们撰写的内容。除了在朋友圈推广自己写的东西，如果有人通过搜索引擎搜索到了我们的博客，那便再好不过了。因此，我们要让自己的博客出现在搜索结果中，也就是说，我们要做SEO(Search Engine Optimization)。<br>除了耐心等待搜索引擎的爬虫之外，我们也可以主动出击，要求搜索引擎将我们的博客编入索引。接下来将以Google和Bing为例进行说明。</p><h2 id="Google"><a href="#Google" class="headerlink" title="Google"></a>Google</h2><p>Google的网站管理工具是<a href="https://search.google.com/search-console/about">Google Search Console</a><br>进入网站并登录自己的谷歌账号之后，会让你选择资源类型，这里选择“网址前缀”即可，在此填入博客的网址<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T120821.png"><br>等待一段时间，之后会出现验证所有权的窗口<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T121229.png"><br>这里直接使用推荐的验证方式即可，当然你也可以使用其他的验证方式，不过这些方式不太适合我们的博客，而且比较繁琐。  </p><blockquote><p>注意：如果你通过CNAME解析的方式为自己的博客绑定了自定义域名，<strong>不能使用页面中提到的添加TXT DNS记录的方式</strong>，这是因为在<a href="https://www.rfc-editor.org/rfc/rfc1034">RFC 1034</a>中规定：<code>If a CNAME RR is present at a node, no other data should be present</code></p></blockquote><p>我们直接下载<code>.html</code>验证文件，将其放置在<code>source</code>文件夹下。不过，为了防止这个文件被Hexo渲染成一个博客页面，以及将其排除在网站地图之外，我们需要为其添加以下内容：  </p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs html">---<br>layout: false  <br>sitemap: false<br>---<br></code></pre></td></tr></table></figure><p>之后使用<code>hexo deploy</code>推送到Github即可，等Netlify构建完成之后，点击验证按钮，网站就被添加到Google中了。<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T122649.png">  </p><blockquote><p>Google会定期验证这个文件的存在，因此不要移除这个文件</p></blockquote><p>编入索引需要等待两天左右，之后便可以在控制台看见博客的数据了<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T123005.png"></p><p>除此之外，我们还可以做一些利于SEO的事情：  </p><h3 id="配置rel-”canonical”"><a href="#配置rel-”canonical”" class="headerlink" title="配置rel&#x3D;”canonical”"></a>配置rel&#x3D;”canonical”</h3><p>根据<a href="https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls?hl=zh-cn">官方文档</a>，配置<code>rel=&quot;canonical&quot;</code>元素有助于告诉搜索引擎正确的URL地址，配置方法也很简单，在主题配置文件中将<code>canonical</code>配置项开启即可  </p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">canonical:</span><br>  <span class="hljs-attr">enable:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></table></figure><h3 id="生成网站地图"><a href="#生成网站地图" class="headerlink" title="生成网站地图"></a>生成网站地图</h3><p>网站地图有助于告诉搜索引擎哪些页面需要被编入，我们可以通过安装<code>hexo-generator-sitemap</code>插件来生成网站地图。  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install hexo-generator-sitemap --save<br></code></pre></td></tr></table></figure><p>之后在<code>hexo deploy</code>时便会自动生成网站地图  </p><blockquote><p>更多配置项请参考<a href="https://github.com/hexojs/hexo-generator-sitemap">官方文档</a></p></blockquote><p>在Google Search Console中，点击左侧边栏的站点地图，输入<code>sitemap.xml</code>，点击提交即可。</p><h2 id="Bing"><a href="#Bing" class="headerlink" title="Bing"></a>Bing</h2><p>Bing的网站管理工具是<a href="https://www.bing.com/webmasters/about">Bing Webmaster</a>，与Google类似，登录后会让我们选择网站的验证方式。<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T125045.png"><br>由于刚刚我们已经完成了Google的配置，这里我们直接选择从GSC导入网站<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T125232.png"><br>点击继续后，使用你的Google账号登录，Bing Webmaster会扫描Google Search Console上的数据，之后网站便添加完成了。<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T125924.png" alt="添加快一个月过后还是零点击……"></p><h1 id="增加曝光量"><a href="#增加曝光量" class="headerlink" title="增加曝光量"></a>增加曝光量</h1><p>除了SEO之外，还有几个增加点击量的小技巧：<br>如果你有X(Twitter)等社交账号，可以将自己的博客添加到个人资料中<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T132000.png"><br>如果你有<a href="https://v2ex.com/">V2EX</a>账号，可以将自己的博客提交到<a href="https://v2ex.com/go/vxna">VXNA节点</a>，只需创建一个包含博客地址和RSS Feed的主题即可<br><img src="/post/how-to-create-your-own-blog-p3-access-from-internet/2024-09-24T132744.png"><br>被收录之后，博客文章会被聚合在<a href="https://v2ex.com/xna">这里</a><br>有关VXNA的更多信息可以查看这个帖子：<a href="https://v2ex.com/t/1039945">这是 V2EX 即将推出的新功能吗？</a></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>至此，博客搭建教程告一段落。从准备框架，平台和域名开始，到配置Hexo，编辑器和统计数据，再到最后的公网访问，评论区，SEO优化。三篇文章根据我在搭建博客时做的功课撰写而成，同时参考了以下页面，衷心感谢这些创作者们：<br><a href="https://blog.myencyclopedia.top/zh/2021/general-hexo/">https://blog.myencyclopedia.top/zh/2021/general-hexo/</a><br><a href="https://easonyang.com/posts/hexo-to-hugo/">https://easonyang.com/posts/hexo-to-hugo/</a><br><a href="https://io-oi.me/tech/hugo-vs-hexo/">https://io-oi.me/tech/hugo-vs-hexo/</a><br><a href="https://hpcesia.github.io/posts/2024/48acad6d/">https://hpcesia.github.io/posts/2024/48acad6d/</a><br><a href="https://serverfault.com/questions/834320/adding-both-cname-and-txt-dns-records-for-one-subdomain">https://serverfault.com/questions/834320/adding-both-cname-and-txt-dns-records-for-one-subdomain</a><br><a href="https://heningwang.github.io/my_homepage/posts/f7b96e5d.html">https://heningwang.github.io/my_homepage/posts/f7b96e5d.html</a></p><p>现在，开始你的创作之旅吧！</p>]]></content>
    
    
    <summary type="html">博客搭建系列的最后一篇，将你的博客推向公网，同时让你写的内容可以被他人检索到</summary>
    
    
    
    <category term="折腾记录" scheme="https://blog.kenxu.top/categories/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
    <category term="blog" scheme="https://blog.kenxu.top/tags/blog/"/>
    
    <category term="hexo" scheme="https://blog.kenxu.top/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>从零开始的博客搭建指北-篇二：安装与配置</title>
    <link href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p2-installation-and-configuration/"/>
    <id>https://blog.kenxu.top/post/how-to-create-your-own-blog-p2-installation-and-configuration/</id>
    <published>2024-09-13T14:50:00.000Z</published>
    <updated>2024-09-17T11:43:48.891Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><p>接上一篇：<a href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p1-preparation/">从零开始的博客搭建指北-篇一：准备工作</a></p><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>Linux用户建议从<a href="https://github.com/nodesource/distributions">Nodesource</a>安装，支持Debian和RHEL系，添加软件源后可通过包管理组件安装与更新，废话不多说，直接上命令，以Debian 12为例：  </p><blockquote><p>不要安装系统软件仓库里的，版本过旧（可自行通过<code>apt-cache policy nodejs</code>查看，Debian 12还在v18）  </p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">sudo</span> apt install git curl<br>curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh<br><span class="hljs-comment">#或是安装v20 LTS版，随意</span><br><span class="hljs-built_in">sudo</span> bash nodesource_setup.sh<br><span class="hljs-built_in">sudo</span> apt install nodejs<br>node -v <span class="hljs-comment">#输出版本即安装正确</span><br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#给npm换源</span><br>npm config <span class="hljs-built_in">set</span> registry https://registry.npmmirror.com<br></code></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install -g hexo-cli<br><span class="hljs-comment">#接下来创建一个你想用来存放博客内容的文件夹，并cd到这个文件夹</span><br>hexo init<br>npm install<br>hexo serve<br><span class="hljs-comment">#接下来用浏览器访问localhost:4000（当然了你可能要将这个端口映射到本地），你会看到一个有着一篇默认文章的博客页面，至此安装完成</span><br></code></pre></td></tr></table></figure><h1 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h1><p>Hexo自带了一个默认主题<a href="https://github.com/hexojs/hexo-theme-landscape">Landscape</a>。你也可以随意安装使用其他主题。如果你对此一无所知，推荐你去看看这位大佬的文章：<a href="https://pengtech.net/hexo/hexo_theme_recommendation.html">16 款精美的 hexo 博客主题推荐</a>，对16个主题做了详细的描述，并配有效果图，你可以从中挑选一款自己喜欢的  </p><p>我最终选择了<a href="https://github.com/fluid-dev/hexo-theme-fluid">Fluid主题</a>，接下来也将以这个主题为例讲述如何配置</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install --save hexo-theme-fluid<br>wget -O _config.fluid.yml https://raw.githubusercontent.com/fluid-dev/hexo-theme-fluid/master/_config.yml <span class="hljs-comment">#下载主题默认配置文件</span><br>hexo config theme fluid<br>hexo config language zh-CN<br><span class="hljs-comment">#效果等同于直接修改_config.yaml中的theme和language配置项</span><br></code></pre></td></tr></table></figure><p>现在目录中应该有三个配置文件：Hexo配置文件<code>_config.yml</code>，默认主题Landscape配置文件<code>_config.landscape.yml</code>，以及刚刚下载的主题配置文件<code>_config.fluid.yml</code>，你只需要关心Hexo配置文件以及你选择的主题的配置文件即可。接下来只会讲述功能上的配置，与美化等自定义有关的配置请自行参考Hexo与主题文档</p><h1 id="写作与内容管理"><a href="#写作与内容管理" class="headerlink" title="写作与内容管理"></a>写作与内容管理</h1><p>对于博客来说，最重要的就是其中的内容了。在Hexo中，兴建一篇文章的指令很简单：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">hexo new &lt;title&gt; <span class="hljs-comment">#还有一个参数layout，默认是post，即文章</span><br></code></pre></td></tr></table></figure><p>你可以使用Markdown格式来撰写文章，如果你不会Markdown，推荐你去看看这篇教程：<a href="https://markdown.com.cn/">Markdown 教程</a>。比起各类office套件，使用Markdown撰写文章可以让你专注于内容而非排版。</p><p>文章撰写完成后，你可以执行<code>hexo serve</code>指令，Hexo会自动将你写的内容转换为html静态文件，此时在浏览器中打开<code>localhost:4000</code>便可以直接预览生成的网页。</p><p>接下来做一些（我认为的）可以让你的写作与管理变得更加方便的调整</p><h2 id="将图片放入子文件夹"><a href="#将图片放入子文件夹" class="headerlink" title="将图片放入子文件夹"></a>将图片放入子文件夹</h2><p>在Hexo中，图片必须放置在<code>source</code>目录中或其子文件夹中。同时，图片的根路径即为<code>source</code>文件夹（即文章中<code>img.jpg</code>等同于文件系统中的<code>/path/to/hexo/source/img.jpg</code>）。你可以将所有图片放置在一个文件夹中，但这样不便于管理。我们可以让Hexo在创建新文章的时候，同步创建一个同名文件夹用来存放素材，类似于这样：<br><img src="/post/how-to-create-your-own-blog-p2-installation-and-configuration/2024-09-15T232036.png">  </p><p>你需要向<code>_config.yml</code>中加入以下配置：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">post_asset_folder:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></table></figure><p>但是，在向文章中添加图片的时候，以Markdown插入图片的方式直接填写图片的文件名是无法正确显示图片的（在Hexo眼中，图片的路径是<code>/_posts/&lt;title&gt;/img.png</code>）。正确的做法是使用相对路径引用的标签插件，类似于这样：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">&#123;% asset_img example.jpg This is an example image %&#125;<br></code></pre></td></tr></table></figure><p>但这并不符合Markdown语法规范，同时也十分麻烦。对此我们可以安装hexo-renderer-marked插件，并通过配置使得使用Markdown格式插入的图片可以被解析到正确的地址。</p><p>首先安装插件  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install hexo-renderer-marked --save<br></code></pre></td></tr></table></figure><p>然后在<code>_config.yml</code>中加入以下配置：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">marked:</span><br>  <span class="hljs-attr">prependRoot:</span> <span class="hljs-literal">true</span><br>  <span class="hljs-attr">postAsset:</span> <span class="hljs-literal">true</span><br></code></pre></td></tr></table></figure><blockquote><p>更多功能请参考其<a href="https://github.com/hexojs/hexo-renderer-marked">官方文档</a></p></blockquote><h2 id="安装vscode插件"><a href="#安装vscode插件" class="headerlink" title="安装vscode插件"></a>安装vscode插件</h2><p>图片在网页上是能正常显示了，但我们在使用Markdown格式插入图片时只写了图片的文件名，而图片的真实地址其实是<code>&lt;title&gt;/img.png</code>，这导致了一个问题：当我们在本地编辑的时候，我们又无法预览插入的图片了，这可怎么办？  </p><p>我使用的Markdown编辑器是vscode，<a href="https://marketplace.visualstudio.com/items?itemName=fantasy.vscode-hexo-utils">这款插件</a>解决了这个痛点，我们只需要安装即可  </p><p><img src="/post/how-to-create-your-own-blog-p2-installation-and-configuration/2024-09-16T000403.png" alt="&quot;Preview image with assets folder&quot;">  </p><p>安装完成后就可以直接在预览区域查看图片了  </p><p><img src="/post/how-to-create-your-own-blog-p2-installation-and-configuration/2024-09-16T000901.png"></p><p>同时你可以直接使用<code>ctrl+alt+v</code>快捷键将剪贴板中的图片以Markdown格式直接粘贴到文章中，是不是很方便？  </p><blockquote><p>该功能基于<a href="https://github.com/mushanshitiancai/vscode-paste-image">paste image</a>，你需要先禁用这个插件以免冲突</p></blockquote><h2 id="自定义模板"><a href="#自定义模板" class="headerlink" title="自定义模板"></a>自定义模板</h2><p>Hexo的模板存放在<code>scaffolds</code>文件夹下，创建新文章时会将<code>post.md</code>文件复制一份到<code>source</code>文件夹。通过编辑这个文件，你可以自定义每篇文章的<a href="https://hexo.io/zh-cn/docs/front-matter">Front-matter</a>以及一些固定的正文内容。我将其配置成这样：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs text">---<br>title: &#123;&#123; title &#125;&#125;  #文章的文件名<br>title_en:<br>excerpt:<br>index_img:<br>date: &#123;&#123; date &#125;&#125;  #文件建立日期<br>categories:<br>tags:<br>---<br></code></pre></td></tr></table></figure><h2 id="设置文章永久链接"><a href="#设置文章永久链接" class="headerlink" title="设置文章永久链接"></a>设置文章永久链接</h2><p>文章永久链接，即<code>_config.yml</code>中的<code>permalink</code>参数，控制了博客文章的链接形式，默认是年-月-日-标题的形式。但这样设置的链接过长，同时美观性欠佳。我决定将所有文章放在<code>/post</code>路径下，并且为每篇文章取一个简短的英文标题作为链接的一部分。<br>首先修改<code>permalink</code>参数：  </p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">permalink:</span> <span class="hljs-string">&#x27;/post/:title_en/&#x27;</span><br></code></pre></td></tr></table></figure><p>再向每篇文章的<a href="https://hexo.io/zh-cn/docs/front-matter">Front-matter</a>部分插入<code>title_en</code>参数即可，上个部分设置的模板中已包含。</p><h2 id="RSS"><a href="#RSS" class="headerlink" title="RSS"></a>RSS</h2><blockquote><p>RSS（英文全称：RDF Site Summary 或 Really Simple Syndication），是一种消息来源格式规范，用以聚合多个网站更新的内容并自动通知网站订阅者。使用 RSS 后，网站订阅者便无需再手动查看网站是否有新的内容，同时 RSS 可将多个网站更新的内容进行整合，以摘要的形式呈现，有助于订阅者快速获取重要信息，并选择性地点阅查看。(引自<a href="https://zh.wikipedia.org/wiki/RSS">维基百科页面</a>)</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install hexo-generator-feed --save<br></code></pre></td></tr></table></figure><p>安装完成后，生成静态文件时将会自动生成RSS文件，默认放置在<code>/atom.xml</code>路径。我将RSS标识放在了博客的导航菜单上，需要在<code>_config.fluid.yml</code>文件中增加一行配置：  </p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">navbar:</span><br>  <span class="hljs-attr">menu:</span><br>    <span class="hljs-comment"># 其他内容保持不变</span><br>    <span class="hljs-bullet">-</span> &#123; <span class="hljs-attr">key:</span> <span class="hljs-string">&quot;RSS&quot;</span>, <span class="hljs-attr">link:</span> <span class="hljs-string">&quot;/atom.xml&quot;</span>, <span class="hljs-attr">icon:</span> <span class="hljs-string">&quot;iconfont icon-rss&quot;</span> &#125;<br></code></pre></td></tr></table></figure><blockquote><p>之所以没有修改<code>_config.yml</code>文件是因为我觉得默认配置足矣，如果你需要更多的自定义选项，请参考<a href="https://github.com/hexojs/hexo-generator-feed">插件文档</a></p></blockquote><h1 id="访问统计"><a href="#访问统计" class="headerlink" title="访问统计"></a>访问统计</h1><p>即文章标题下方的阅读量以及页脚的访问量与访问数，这里我们使用<a href="https://www.leancloud.cn/">Leancloud</a>统计数据</p><blockquote><p>Leancloud分<a href="https://www.leancloud.cn/">国内版</a>和<a href="https://leancloud.app/">国际版</a>，<strong>国际版的API地址屏蔽了国内IP</strong>，因此为了保证数据准确，<strong>请使用国内版</strong>（虽然国际版不需要实名认证就能用）</p></blockquote><p>首先打开官网，注册账号并实名认证，无需多言。</p><p>完成之后，返回<a href="https://console.leancloud.cn/apps">主页</a>，点击创建应用，应用名称随意，计价方案选开发版<br><img src="/post/how-to-create-your-own-blog-p2-installation-and-configuration/2024-09-16T235810.png"><br>然后点击创建好的应用下方的设置，再点击左侧边栏的应用凭证，记下<code>AppID</code>,<code>AppKey</code>和<code>REST API 服务器地址</code>三个参数，将其填入<code>_config.fluid.yml</code>文件中<code>web_analytics</code>下的<code>leancloud</code>部分中，再修改<code>statistics</code>和<code>views</code>中<code>source</code>参数为<code>leancloud</code>即可</p><h1 id="推送到Github"><a href="#推送到Github" class="headerlink" title="推送到Github"></a>推送到Github</h1><p>最后一步，我们需要将生成的静态文件推送到Github仓库中，以便Netlify拉取并构建网页。</p><p>首先安装git部署插件  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install hexo-deployer-git --save<br></code></pre></td></tr></table></figure><p>然后在Github上创建一个新的仓库用于存放博客的静态文件，由于我们不使用<code>Github Pages</code>服务，因此仓库的名称随意。</p><p>然后我们需要配置ssh密钥用于向我们刚刚创建的仓库提交文件，参考方法如下：  </p><p>首先使用<code>ssh-keygen</code>生成一个密钥对  </p><blockquote><p>默认生成RSA密钥，需要ECC密钥可手动指定 -t ed25519，所有问题回车确认即可（可能需要提供一个密钥文件名以免与现有的密钥冲突）</p></blockquote><p>然后前往<a href="https://github.com/settings/keys">Github的SSH Keys页面</a>，点击<code>New SSH Key</code>，将你的<strong>公钥文件</strong>（类似rsa.pub）粘贴进去<br><img src="/post/how-to-create-your-own-blog-p2-installation-and-configuration/2024-09-17T002003.png" alt="别把私钥粘贴进去了"></p><p>然后，在<code>~/.ssh</code>目录下新建一个<code>config</code>文件，内容填入主机名，用户以及密钥文件路径，类似于这样：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs text">Host github.com<br>    HostName github.com<br>    User git<br>    IdentityFile ~/.ssh/github <br></code></pre></td></tr></table></figure><p>完成之后可以使用<code>ssh -T git@github.com</code>指令测试连接，应该会得到以下回复：  </p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs text">Hi USERNAME! You&#x27;ve successfully authenticated, but GitHub does not provide shell access.<br></code></pre></td></tr></table></figure><p>然后在<code>_config.yml</code>文件末尾加入以下配置：  </p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">deploy:</span><br>  <span class="hljs-attr">type:</span> <span class="hljs-string">&#x27;git&#x27;</span><br>  <span class="hljs-attr">repo:</span> <span class="hljs-string">git@github.com:Github用户名/仓库名.git</span><br>  <span class="hljs-attr">branch:</span> <span class="hljs-string">master</span><br>  <span class="hljs-attr">message:</span> <span class="hljs-comment"># commit信息，留空为默认：Site updated: &#123;&#123; now(&#x27;YYYY-MM-DD HH:mm:ss&#x27;) &#125;&#125;</span><br></code></pre></td></tr></table></figure><p>最后，使用<code>hexo deploy</code>指令即可生成静态文件并推送到Github仓库内</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>至此，在本地进行的工作全部完成，在下一篇文章中，我们将会使用Netlify进行博客页面的部署，同时为博客添加评论区以及反垃圾系统，并将我们的博客添加到搜索引擎上，敬请期待~</p>]]></content>
    
    
    <summary type="html">从nodejs安装到配置访问量统计，带你走一遍安装与配置的流程，本篇所有配置均在本地完成，不涉及公网访问</summary>
    
    
    
    <category term="折腾记录" scheme="https://blog.kenxu.top/categories/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
    <category term="blog" scheme="https://blog.kenxu.top/tags/blog/"/>
    
    <category term="hexo" scheme="https://blog.kenxu.top/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>从零开始的博客搭建指北-篇一：准备工作</title>
    <link href="https://blog.kenxu.top/post/how-to-create-your-own-blog-p1-preparation/"/>
    <id>https://blog.kenxu.top/post/how-to-create-your-own-blog-p1-preparation/</id>
    <published>2024-09-04T12:27:57.000Z</published>
    <updated>2024-09-13T14:57:42.048Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>详见<a href="https://blog.kenxu.top/post/why-creating-my-own-blog/">我的上一篇文章</a>  </p><p>为什么说是“几乎”呢？我是第一次接触Node.js和Hexo这个博客框架，但是对Linux有一定的了解，也使用过Github和一些Serverless平台；因此，本文不会特意讲解Linux命令行的使用方法以及各类平台账号的注册方法，有需要的读者可自行了解。  </p><p>同时为了避免这一系列成为官方文档的精简版本，文章会讲述我在部署时遇到的一些问题和解决方法，希望能给你一定的参考。同时本文中的一些配置是我<del>一杯茶一包烟一个小参调一天调出来的</del>认为的比较优雅美观的配置，可能不适合你，也请注意。  </p><p>本文适用于Linux平台，Windows平台的可以看看<a href="https://hpcesia.github.io/posts/2024/48acad6d/">这位大佬</a>的文章（不过两个平台差别不大，尤其是Github和Severless平台的部分）</p><h1 id="框架选择"><a href="#框架选择" class="headerlink" title="框架选择"></a>框架选择</h1><p><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-04T230526.png" alt="白宫官网，使用Wordpress作为框架"><br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-04T230830.png" alt="大名鼎鼎的免费SSL证书提供商Let&#39;s Encrypt，其网站使用Hugo作为框架">  </p><p>目前市面上有很多开源的博客框架，强大到白宫官网都在用的<a href="https://wordpress.com/">Wordpress</a>，小巧到代码量只有400KB的<a href="https://typecho.org/">Typecho</a>，接下来讲讲我将几个选项排除的理由：  </p><ul><li><a href="https://wordpress.com/">Wordpress</a>：可以说是全球范围内使用量最大的网站框架，但用作一个纯静态博客则显得过于庞大与繁琐（需要一台服务器和LNMP全家桶），PASS  </li><li><a href="https://typecho.org/">Typecho</a>：十分轻量的开源博客框架，但与Wordpress类似，也需要服务器+LNMP环境，PASS  </li><li><a href="https://gohugo.io/">Hugo</a>：使用go语言编写而成，拥有极快的构建速度，支持Serverless平台部署。但是偏小众，相关的资源与插件不是很多，同时Hugo是一个网站框架而非一个博客框架，因此也被PASS<blockquote><p>其实Hugo也是一个相当不错的选择，在查阅了一些资料后我认为两者在搭建博客上可谓不相上下，同时二者都有一些对方没有的功能，使用Hexo而非Hugo的最主要原因可能是因为我是先了解的Hexo，等我开始了解Hugo的时候我已经把Hexo安装完了……</p></blockquote></li></ul><p>最终我选择了Hexo，它使用Node.js编写而成，拥有丰富的插件与主题，同时完美支持Serverless平台部署。最重要的是，它的部署流程相对简单，因此你可以把大部分时间花在撰写文章而不是网站的配置与维护上。</p><h1 id="Serverless平台选择"><a href="#Serverless平台选择" class="headerlink" title="Serverless平台选择"></a>Serverless平台选择</h1><blockquote><p>Serverless Computing（无服务器计算）是一种基于云的计算模式，在这种模式下，开发者无需管理服务器即可部署和运行应用程序。它允许开发者和企业快速、灵活地构建应用程序，而不需要担心服务器的维护和管理。</p></blockquote><p>对于一个静态博客来说，部署在Serverless平台上是一个很好的选择。你可以将Hexo生成的静态文件（或是源代码）交给这些平台，平台会自动帮你构建出网页，而你只需刷新一下就可以看见自己写的文章，在易用性上做到了极致。此外，大部分国外的Serverless平台都提供了慷慨的免费计划，以下表格对比了四家较为知名的平台在免费计划下提供的服务以及可访问性上的一些差异。  </p><blockquote><p>可访问性均在晚高峰时间测试</p></blockquote><table><thead><tr><th align="left"></th><th align="left">Github Pages</th><th align="left">Cloudflare Pages</th><th align="left">Vercel</th><th align="left">Netlify</th></tr></thead><tbody><tr><td align="left">文件</td><td align="left">50MB每个，总共1GB</td><td align="left">25MB每个，最多20000个</td><td align="left">总共100MB</td><td align="left">100MB每个，最多54000个</td></tr><tr><td align="left">每月流量</td><td align="left">100GB</td><td align="left"><strong>无限</strong></td><td align="left">100GB</td><td align="left">100GB</td></tr><tr><td align="left">每月构建时长</td><td align="left">无限，单次小于10分钟</td><td align="left">500次</td><td align="left">6000分钟</td><td align="left">300分钟</td></tr><tr><td align="left">站点分析</td><td align="left">无</td><td align="left"><strong>有</strong></td><td align="left">无</td><td align="left">无</td></tr><tr><td align="left">默认域名可访问性</td><td align="left">一般</td><td align="left">较好</td><td align="left">不可访问</td><td align="left">较好</td></tr><tr><td align="left">CDN供应商</td><td align="left">Fastly</td><td align="left">Cloudflare</td><td align="left">AWS</td><td align="left">AWS</td></tr><tr><td align="left">CDN可访问性</td><td align="left">一般</td><td align="left">一般</td><td align="left">差</td><td align="left">较好</td></tr></tbody></table><p>考虑到博客既没有备案又托管在境外，所以网站的可访问性便成为了重中之重（你也不想你的博客只有挂梯子&#x2F;转圈圈几秒钟才进得去吧）。接下来请看这四家平台CDN可访问性的详细测试  </p><blockquote><p>测试网站：<a href="https://www.itdog.cn/http/">https://www.itdog.cn/http/</a> 未勾选港澳台地区，每个网站测试三次，取最后一次为结果，所有域名均手动补上https:&#x2F;&#x2F;以避免运营商或防火墙直接劫持HTTP流量，闪电图标代表该省份有超时的节点</p></blockquote><p>作为所有平台的对比，先放一个部署在国内并且有CDN的博客的延迟<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T204353.png"></p><p>首先是<del>赛博佛祖</del>Cloudflare，其提供的免费计划可谓遥遥领先，但其CDN节点在大陆的可访问性只能说很一般，以下是默认域名和绑定上自定义域名后httping的测试结果</p><p><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T203716.png" alt="平均时延接近两秒，7个节点超时"><br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T204732.png" alt="自定义域名，时延有所降低"></p><blockquote><p>Cloudflare的CDN访问时延在全世界绝大多数地区都是低于其他CDN供应商的，中国大陆地区的体验如此之差，除了其在大陆地区无节点（只有备案+氪金才能使用京东云的合作节点）之外，也与运营商和防火墙的刻意劣化有关</p></blockquote><p>接下来是Github Pages，Github Pages有着庞大的客户群体，就我看过的博客而言可能有将近一半都是放在Github Pages上的，同时也支持自定义域名。然而Fastly CDN的可访问性怎么样呢？</p><p><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T205113.png" alt="默认域名，时延与Cloudflare半斤八两，但是有30多个节点超时"><br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T205544.png" alt="自定义域名，时延相差不大，超时节点降到了7个"></p><p>Vercel和Netlify两家Serverless平台本质上是AWS的套壳，使用的CDN都是AWS Cloudfront，我们先来看Vercel  </p><p><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T210751.png" alt="Vercel默认域名，What can I say?"><br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T211449.png" alt="自定义域名，时延依然拉跨">  </p><p>两个字：拉跨。下一个  </p><p><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T214030.png" alt="默认域名，平均时延一骑绝尘，但移动全军覆没"><br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T214530.png" alt="自定义域名，伟大，无需多言">  </p><p>简单点评一下，Cloudflare Pages和Github Pages在流量上量大管饱，但其CDN在中国大陆的可访问性堪忧，Vercel虽然使用了AWS的CDN，但是无论是否自定义域名，IP地址均会落到 <code>76.76.21.0/24</code> 这个IP段内，导致其能够被精准打击，再加上AI刚火起来的那段时间有大量网友将能够调用OpenAI API的站点部署在Vercel上用于直连使用ChatGPT，使得Vercel的IP段被列为重点关照对象，最终导致其可访问性在四家平台中垫底。  </p><blockquote><p>部署在Vercel上的网站在关键时期还会直接跳转反诈网站，就像这样：<img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-05T220749.png" alt="我阐释你的梦"></p></blockquote><p>最终，Netlify脱颖而出，凭借着对于博客来说完全够用的免费计划以及在中国大陆极低的访问时延，成为了最优选择。</p><h1 id="域名购买"><a href="#域名购买" class="headerlink" title="域名购买"></a>域名购买</h1><p>理论上来说，由于各大Serverless平台一般都会分配一个默认域名给你的项目，不购买域名也是行得通的。但是，这些默认域名一般比较冗长和难记，另一方面，根据上面测试得到的结果，默认域名的可访问性较为堪忧。如果你想让自己的博客更有特性，同时增强其可访问性，建议购买一个域名。  </p><p>而域名注册商的选择就十分繁多了，国内外的各大云服务平台基本都提供了域名注册服务，同时也有Namesilo,Namecheap,Dynadot等专门做域名售卖的平台，由于各大平台的优惠各不相同，建议多方比较后再做决定，这里说一点我的经验。  </p><p>首先就是不要被某些平台的优惠价格骗了，大部分平台在首年都会给出一个十分劲爆的优惠价格，一些新注册的，还处在推广期的顶级域甚至会直接让你免费用一年（例如Dynadot几年前注册的.gay顶级域，有段时间只要注册账号就能免费选一个域名用一年），但是等到你续费的时候，域名注册商给你开出的账单绝对会让你大吃一惊的。<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-06T125925.png" alt="上文提到的.gay顶级域，续费30刀"></p><p>其次，最好不要贪便宜去用免费的域名。首先目前应该是没有免费提供二级域名（xxx.com）的平台了，提供免费域名的一般都是一些公益组织（例如十分有名的<a href="https://nic.eu.org/">eu.org</a>），这些组织将一些很短的二级域名开放注册，也就是说你拿到的是三级域名（xxx.eu.org）。虽然免费，但这类域名的注册几乎没有门槛，导致滥用情况普遍，不仅可能被搜索引擎降低权重，还有可能被一网打尽（参考上面提到的反诈）；而且将来如果想折腾一些别的项目（例如自建邮箱），用这样的域名也会带来不小的麻烦。  </p><p>以下是一些省钱的心得：  </p><p>如果你想尽力省钱，只要有个域名就行，那我推荐.top域名，价格应该是所有顶级域中最便宜的之一。国内的平台可以看看<a href="https://wanwang.aliyun.com/">阿里云万网</a>，价格实惠，而且连续注册多年还有很大的优惠<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-06T165159.png" alt="随便输的一个，注册十年只要188"><br>国外的平台可以看看<a href="https://www.namesilo.com/">Namesilo</a>，首年1.88刀续费4.88刀是我看过的国外域名注册平台中最便宜的，而且不需要实名认证就能注册<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-06T170003.png">  </p><blockquote><p>可以再去搜一下Namesilo的折扣码，叠完折扣后1刀就能拿下一个.top域名</p></blockquote><p>如果你想要一个更加正式的域名，例如.com顶级域，那我强烈推荐你去Cloudflare注册，我挂一张图在这里：<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-06T170253.png" alt="就问你实诚不？"><br>注册完成后可以直接使用Cloudflare提供的一系列服务（DNS解析，免费<del>减速</del>CDN等等），省去了转移DNS解析服务商的功夫。唯一的缺点就是Cloudflare不支持银联和微信&#x2F;支付宝，你需要一张境外卡组织发行的银行卡才能完成购买<br><img src="/post/how-to-create-your-own-blog-p1-preparation/2024-09-06T172211.png" alt="Paypal也可以">  </p><blockquote><p>需要注意的是，在Cloudflare平台上注册的域名是不允许更改DNS解析服务商的，不过瞅着Cloudflare平台的众多福利，这也算不上缺点  </p></blockquote><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>至此，三个大头的准备工作就完成了，全程只有域名注册花了钱（这也是整个教程系列中唯一一个花钱的地方了）。接下来我们将会安装Hexo框架，将静态文件推送到Github，设置Netlify，配置评论系统，预计分为两篇文章推送，敬请期待。</p>]]></content>
    
    
    <summary type="html">详细地对比了不同的建站框架，部署平台，以及域名注册商，尤其是对比了在不备案域名的情况下各大平台的CDN在中国大陆的可访问性，助你选择其中最优的方案</summary>
    
    
    
    <category term="折腾记录" scheme="https://blog.kenxu.top/categories/%E6%8A%98%E8%85%BE%E8%AE%B0%E5%BD%95/"/>
    
    
    <category term="how-to" scheme="https://blog.kenxu.top/tags/how-to/"/>
    
    <category term="blog" scheme="https://blog.kenxu.top/tags/blog/"/>
    
    <category term="hexo" scheme="https://blog.kenxu.top/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>缘起：为什么我会写博客</title>
    <link href="https://blog.kenxu.top/post/why-creating-my-own-blog/"/>
    <id>https://blog.kenxu.top/post/why-creating-my-own-blog/</id>
    <published>2024-09-03T04:50:38.000Z</published>
    <updated>2024-09-19T13:02:13.571Z</updated>
    
    <content type="html"><![CDATA[<p style="color: #666; font-style: italic;">部分RSS客户端可能对内容渲染有问题，建议前往博客网页查看文章内容。</p><h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在折腾的路上，我总是会遇到各种各样的问题，碰到了问题必定是一头栽进搜索引擎。在多次Debug的过程中，除了官方文档和stack overflow等论坛之外，我最看重的就是个人博客。这些博客文章大都是以使用者的身份写成的，其中遇到的一些问题可能与我相似甚至相同；除此之外，涉及到一些比较和选择方面的问题（例如Debian和Ubuntu，MySQL和PostgreSQL），这些博客中的使用体验也能给我很好的参考；最后，这些博客都是用爱发电写成的，文章质量很高，其中一些技术方面的分享也着实让我增长了见识。<br>那何不自己也来创建一个博客，自己也来写点文章呢？受到这么多优秀大佬们的启发，我决定把自己折腾的经历写出来，发表到博客上，于是便有了你现在看到的这个东西。  </p><h1 id="关于博客"><a href="#关于博客" class="headerlink" title="关于博客"></a>关于博客</h1><p>当然，如果只是想写点东西，微信公众号足矣，更进一步地说，也有许多现成的博客平台任君选择，对我来说，选择自建博客的理由无外乎以下两点：<br><strong>自由</strong> 这里说的不仅是内容的自由（<del>都可以谈，没有什么不能谈的</del>），也是形式的自由，我可以按照我的喜好改变网站上的每一个细节（<del>当然由于想象力缺乏很多依然是default</del>），也可以选择我喜欢的写作方法（Markdown），还可以把网站部署在喜欢的平台上，而且做到除了买域名外全程不花一分钱。<br><strong>折腾</strong> 在一些人眼中，“折腾”可能是一个贬义词，而我却乐在其中，我十分赞同这张图上的观点：<br><img src="/post/why-creating-my-own-blog/2024-09-03T174401.png"><br>我也是Linux爱好者，自从高一了解到世界上除了Windows和macOS两种操作系统之外还有一种叫做Linux的操作系统（准确来说是Linux发行版，不过这里就不扣字眼了），便一发不可收拾地迷上了她。从虚拟机到实体机，我安装过Ubuntu,Debian,Deepin,Archlinux,Manjaro等等不下十个发行版。  </p><p><img src="/post/why-creating-my-own-blog/Archlinux-2022-04-16-15-40-47.png" alt="虚拟机内安装Archlinux，这是我用的第三个Linux发行版，折腾到这里花了四五个小时"><br><img src="/post/why-creating-my-own-blog/Manjaro-2022-04-22-20-51-04.png" alt="Archlinux之后体验了Manjaro，起码是图形化安装界面，简单了不少"></p><p>每个操作系统可能只会在我的硬盘中停留数个月到数天的时间，只要我觉得不满意或是厌烦了就会格掉重新安装另一个发行版（不过现在稳定在fedora了），在各类发行版中，我折腾过主题美化，显卡硬解，windows软件安装（<del>尤其是你！微信！</del>），我享受折腾的过程，就如同B站UP主极客湾在<a href="https://www.bilibili.com/video/BV19f421e7Wn/">最新一期视频中</a>所说的那样</p><blockquote><p>如果你对一件事情充满好奇，如果你想做的事情是你觉得不可思议的，是让你忍不住想笑的，你一定会干劲十足的</p></blockquote><p>其实与其说是“折腾”，倒不如说是在将这些东西当成大号的玩具“玩”。而且客观的来讲，一路折腾下来，我也确实掌握了不少Linux使用技巧，这些技巧也让我无论是在日常使用Linux上还是在搭建Homelab中更加得心应手。  </p><p>对于兴趣在此的我来说，搭建博客不仅是一个全新的折腾项目，也是对我综合能力的考验，我乐意阅读各类文档，查阅各种资料，积累知识，同时享受折腾的过程，贯穿本博客“折腾，记录，分享”的宗旨。</p><blockquote><p>当然了，更新频率上肯定比较松弛，咕咕咕~</p></blockquote><h1 id="写作计划"><a href="#写作计划" class="headerlink" title="写作计划"></a>写作计划</h1><p>由于是个人博客，打算只写自己感兴趣的东西，目前应该主要包含两方面：  </p><ul><li>Homelab折腾之旅：高考结束之后，我淘汰掉了先前作为服务器的旧电脑主机，购入了一台戴尔R730服务器作为新服务器，在迁移的过程中，除了将先前零零散散搭建起来的各种服务集中重新配置了一遍以外，还将之前受限于知识水平而有所妥协的项目进一步完善，我计划将折腾的过程写出来发表在博客上，希望能帮助到你，同时起到备忘录的作用。  </li><li>CS相关知识分享：还没想好，可能是一些偏科普类的文章，内容应该会以我最近研究的东西为主。</li></ul><p>若无意外，所有文章均会以CC-BY-NC-SA开源协议发布，你可以在非商用，署名，以相同形式共享的前提下，随意转载或引用我的文章（<del>有人看就不错了吧kora</del>）</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>这是本博客的第一篇文章，其实我是想一上来就写写如何搭建博客的，奈何在写前言的时候发现内容有些过于冗长和偏题了，于是便有了这篇缘起篇。由于鄙人语文本当苦手，文笔贼差，所以博客中的感性的表述可能会比较少，不出意外的话这可能是唯一一篇杂谈类文章（悲<br>鄙人水平有限，错漏之处还请各位海涵，也可以在<del>不知为何反应速度贼慢的</del>评论区友好交流</p>]]></content>
    
    
    <summary type="html">关于博客的一些想法，也有一些自己的心路历程</summary>
    
    
    
    <category term="杂谈" scheme="https://blog.kenxu.top/categories/%E6%9D%82%E8%B0%88/"/>
    
    
    <category term="blog" scheme="https://blog.kenxu.top/tags/blog/"/>
    
    <category term="thoughts" scheme="https://blog.kenxu.top/tags/thoughts/"/>
    
  </entry>
  
</feed>