<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>狂犬主子</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://www.xrgzs.top/icon.png</icon>
  <id>https://www.xrgzs.top/</id>
  <link href="https://www.xrgzs.top/" rel="alternate"/>
  <link href="https://www.xrgzs.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 狂犬主子</rights>
  <subtitle>潇洒自然</subtitle>
  <title>潇然工作室</title>
  <updated>2026-09-29T05:47:00.000Z</updated>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="Kali Linux" scheme="https://www.xrgzs.top/tags/kali-linux/"/>
    <category term="虚拟机" scheme="https://www.xrgzs.top/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
    <content>
      <![CDATA[<p>Kali Linux 官方提供了现成的虚拟机镜像，直接下载后导入虚拟机即可使用，无需手动安装ISO。但官方镜像默认是英文界面，且没有安装中文输入法，使用起来不太方便。本文将介绍如何下载官方镜像，并进行一些简单的配置。</p><h2 id="镜像使用说明">镜像使用说明</h2><p>Kali 官方提供了虚拟机镜像，下载地址：<a href="https://www.kali.org/get-kali/#kali-virtual-machines">https://www.kali.org/get-kali/#kali-virtual-machines</a></p><p><img src="kali-image-setup/20260925-213847.png" alt="官方虚拟机下载"></p><p>根据你的虚拟机软件选择对应的镜像，以 VMware Workstation 为例，下载后解压缩到你想放置的目录，然后双击 <code>vmx</code> 文件导入虚拟机即可。</p><p><img src="kali-image-setup/f107eb85b9e2a829d47c6c89fd4885d0.png" alt=""></p><div class="note info flat"><p>如果你使用的是 VMware Workstation，建议在导入虚拟机后先<strong>升级虚拟机版本</strong>，避免鼠标无法显示。</p></div><p>升级虚拟机版本的方法：</p><ol><li>选择「升级虚拟机」</li></ol><p><img src="kali-image-setup/20260929-132631.png" alt=""></p><ol start="2"><li>选择你能升级到的最新版本</li></ol><p><img src="kali-image-setup/20260929-132707.png" alt=""></p><ol start="3"><li>选择「更改此虚拟机」</li></ol><p><img src="kali-image-setup/065b5b03fb7914563233dba9958831b4.png" alt=""></p><h2 id="默认登录信息">默认登录信息</h2><p>用户名：kali</p><p>密码：kali</p><p>进入桌面后，打开终端，执行命令。</p><h2 id="更改语言">更改语言</h2><p>默认英文，如果看不懂的话，可以改成中文。</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> dpkg-reconfigure</span><span style="color:#98C379"> locales</span></span></code></pre></td></tr></tbody></table></figure><ol><li>选择 <code>zh_CN.UTF-8 UTF-8</code>，按空格键勾选，然后按回车键确认<br><img src="kali-image-setup/55bd0f8c85ee772704901d8345173d92.png" alt=""></li><li>在下一个窗口中，将默认系统语言设置为中文（<code>zh_CN.UTF-8</code>）<br><img src="kali-image-setup/76294e21a6c2564739184b820a5195b0.png" alt=""></li></ol><p>设置在注销或重启后生效，这里暂时不进行，后面改好其他语言设置后一次进行。</p><p>如果你使用 Firefox 浏览器，需要在设置中手动切换中文。</p><p><img src="kali-image-setup/e0079aa5eb407d606477f51d821c5fa4.png" alt=""></p><p><img src="kali-image-setup/17a809841c45a3bbd759a8754d52bb4c.png" alt=""></p><p><img src="kali-image-setup/84ea1042c0d2cf212a8d03294b54fb92.png" alt=""></p><p><img src="kali-image-setup/1bac831fcba99294b4576c876e20a647.png" alt=""></p><p><img src="kali-image-setup/de9d2f91663ee48e8483ee3a0076d8c2.png" alt=""></p><h2 id="更改时区">更改时区</h2><p>默认时区不是中国，需要更改为中国大陆的时间。</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> dpkg-reconfigure</span><span style="color:#98C379"> tzdata</span></span></code></pre></td></tr></tbody></table></figure><p>将时区更改为 <code>Asia/Shanghai</code>，然后按回车键确认。</p><blockquote><p>亦可使用 <code>sudo timedatectl set-timezone Asia/Shanghai</code> 命令更改时区。</p></blockquote><h2 id="换源">换源</h2><p>默认为 Kali 官方源，部分地区下载缓慢，这里更换为教育网的镜像站。</p><p>换源帮助：<a href="https://help.mirrors.cernet.edu.cn/kali/">https://help.mirrors.cernet.edu.cn/kali/</a></p><figure class="highlight shell"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 备份源列表</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cp</span><span style="color:#98C379"> /etc/apt/sources.list</span><span style="color:#98C379"> /etc/apt/sources.list.bak</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cp</span><span style="color:#98C379"> /etc/apt/sources.list.d/kali.sources</span><span style="color:#98C379"> /etc/apt/sources.list.d/kali.sources.bak</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># sudo echo "deb http://mirrors.cernet.edu.cn/kali kali-rolling main non-free contrib" > /etc/apt/sources.list</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> sed</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> 's|http://http.kali.org/kali|https://mirrors.cernet.edu.cn/kali|g'</span><span style="color:#98C379"> /etc/apt/sources.list</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> sed</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> 's|http://http.kali.org/kali|https://mirrors.cernet.edu.cn/kali|g'</span><span style="color:#98C379"> /etc/apt/sources.list.d/kali.sources</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span></code></pre></td></tr></tbody></table></figure><div class="note danger flat"><p>不建议执行 <code>sudo apt upgrade</code>，因为可能导致系统不稳定。如果你的镜像过旧，建议重新下载最新的 Weekly 版。</p></div><h2 id="安装输入法">安装输入法</h2><ol><li>首先安装输入法配置工具，方便后面<strong>配置输入环境</strong>：</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> im-config</span></span></code></pre></td></tr></tbody></table></figure><ol start="2"><li>安装Fcitx5</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo apt install --install-recommends fcitx5 fcitx5-chinese-addons</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> fcitx5</span><span style="color:#98C379"> fcitx5-chinese-addons</span><span style="color:#98C379"> fcitx5-config-qt</span><span style="color:#98C379"> fcitx5-frontend-</span><span style="color:#E5C07B">*</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li><p>配置输入法</p><p>为了使应用程序能够识别到输入法引擎，我们需要对<strong>输入环境</strong>进行配置。这里利用我们之前装的 <code>im-config</code> 对输入法进行配置，终端直接输入 <code>im-config</code> 执行就行。</p></li><li><p><strong>输入法配置完成后需要注销或重启系统才能生效。</strong><img src="kali-image-setup/20260929-133539.png" alt=""></p></li></ol><p>切换语言后，首次进入桌面会弹出提示要求更换默认用户文件夹为中文，这里最好点击「保留旧的名称(K)」，避免某些程序写死英文路径导致家目录下文件夹重复。</p><p><img src="kali-image-setup/20260929-133841.png" alt=""></p><p>切换输入法的方法：<code>Control+Space</code>，如果不习惯这个快捷键，可以在输入法设置（右键任务栏键盘图标-&gt;输入法设置）或者 <code>fcitx5-config-qt</code> 中修改。如果默认输入联想用不习惯，可按快捷键 <code>Control+Alt+Shift+C</code> 启用<strong>云拼音</strong>（可能会导致信息泄露）。</p><h2 id="关闭自动熄屏">关闭自动熄屏</h2><p>默认自动熄屏有点烦，可以按照下面的方法关闭。</p><p>打开设置 -&gt; 电源管理器 -&gt; 显示，关闭“显示电源管理器”，然后打开下方的“屏幕保护程序管理”，关闭“启用屏幕保护程序”。</p><p><img src="kali-image-setup/b229e395df1008d90598ce40dff9c1d6.png" alt=""></p><p><img src="kali-image-setup/31a263526456008a3f39ac6212ab2c53.png" alt=""></p><p><img src="kali-image-setup/fdd60045b4413eded3eab8a8ffe2ea24.png" alt=""></p><p><img src="kali-image-setup/732beacef8bfdeed55f9049b2febb390.png" alt=""></p><p><img src="kali-image-setup/ede1bc0d294a42ddfb475de88d62504a.png" alt=""></p><h2 id="解决网络问题">解决网络问题</h2><p>（可选）</p><p>这里不提供具体工具和链路，仅说明如何安装 <code>.deb</code> 包，适用于任何软件。</p><p><code>GH加速</code> 请替换为实际能使用的站点，具体可以<a href="https://cn.bing.com/search?q=ghproxy">百度</a>，这里不提供，**请勿无脑复制！**如果你已经解决网络问题，可删除 <code>https://&lt;GH加速&gt;/</code>。</p><ol><li>获取 <code>.deb</code> 包</li></ol><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> https://</span><span style="color:#ABB2BF">&#x3C;</span><span style="color:#98C379">GH加</span><span style="color:#ABB2BF">速></span><span style="color:#98C379">/https://github.com/xxx/xxx/releases/download/v1.0.0/miaomiao-tool.deb</span><span style="color:#D19A66"> -O</span><span style="color:#98C379"> miaomiao-tool.deb</span></span></code></pre></td></tr></tbody></table></figure><ol start="2"><li>安装</li></ol><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> ./miaomiao-tool.deb</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 此方法最后一行提示「权限不够」是正常的</span></span><span class="line"><span style="color:#61AFEF">rm</span><span style="color:#98C379"> ./miaomiao-tool.deb</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li>运行你安装的工具，导入你的链路</li><li>建议用 TUN / 虚拟网卡模式，<strong>否则</strong>需要增加环境变量，每次新启动终端都要输入：</li></ol><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 改成自己的代理端口</span></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#E06C75"> HTTP_PROXY</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"http://127.0.0.1:1080"</span></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#E06C75"> HTTPS_PROXY</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"http://127.0.0.1:1080"</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>宿主机开 TUN，虚拟机用NAT网络也可以解决网络问题。</p></blockquote><h2 id="安装-Docker">安装 Docker</h2><p>默认源自带的 <code>docker.io</code> 版本过旧，建议安装官方源的最新版。</p><p>文档：<a href="https://docs.docker.com/engine/install/debian/">https://docs.docker.com/engine/install/debian/</a></p><p>下面是快速换源&amp;安装的步骤：</p><figure class="highlight shell"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> remove</span><span style="color:#ABB2BF"> $(</span><span style="color:#61AFEF">dpkg</span><span style="color:#D19A66"> --get-selections</span><span style="color:#98C379"> docker.io</span><span style="color:#98C379"> docker-compose</span><span style="color:#98C379"> docker-doc</span><span style="color:#98C379"> docker-buildx</span><span style="color:#98C379"> podman-docker</span><span style="color:#98C379"> containerd</span><span style="color:#98C379"> runc</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">cut</span><span style="color:#D19A66"> -f1</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add Docker's official GPG key:</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> ca-certificates</span><span style="color:#98C379"> curl</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -m</span><span style="color:#D19A66"> 0755</span><span style="color:#D19A66"> -d</span><span style="color:#98C379"> /etc/apt/keyrings</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> curl</span><span style="color:#D19A66"> -fsSL</span><span style="color:#98C379"> https://mirrors.cernet.edu.cn/docker-ce/linux/debian/gpg</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> /etc/apt/keyrings/docker.asc</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#98C379"> a+r</span><span style="color:#98C379"> /etc/apt/keyrings/docker.asc</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add the repository to Apt sources:</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#98C379"> /etc/apt/sources.list.d/docker.sources</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#98C379">Types: deb</span></span><span class="line"><span style="color:#98C379">URIs: https://mirrors.cernet.edu.cn/docker-ce/linux/debian</span></span><span class="line"><span style="color:#98C379">Suites: trixie</span></span><span class="line"><span style="color:#98C379">Components: stable</span></span><span class="line"><span style="color:#98C379">Architectures: $(</span><span style="color:#61AFEF">dpkg</span><span style="color:#D19A66"> --print-architecture</span><span style="color:#98C379">)</span></span><span class="line"><span style="color:#98C379">Signed-By: /etc/apt/keyrings/docker.asc</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> /etc/docker</span></span><span class="line"><span style="color:#61AFEF">cat</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#98C379"> /etc/docker/daemon.json</span></span><span class="line"><span style="color:#98C379">&#123;</span></span><span class="line"><span style="color:#98C379">  "registry-mirrors": ["https://docker-pull.ygxz.in"]</span></span><span class="line"><span style="color:#98C379">&#125;</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> docker-ce</span><span style="color:#98C379"> docker-ce-cli</span><span style="color:#98C379"> containerd.io</span><span style="color:#98C379"> docker-buildx-plugin</span><span style="color:#98C379"> docker-compose-plugin</span></span></code></pre></td></tr></tbody></table></figure><p>将当前用户加入 Docker 用户组，以便无需 <code>sudo</code> 即可运行 Docker（不安全）：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> usermod</span><span style="color:#D19A66"> -aG</span><span style="color:#98C379"> docker</span><span style="color:#E06C75"> $USER</span></span><span class="line"><span style="color:#61AFEF">newgrp</span><span style="color:#98C379"> docker</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Node-js">安装 Node.js</h2><p>Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。运行AI智能体和开发前端/全栈项目<strong>必须安装</strong>。</p><p>下面三种方式请任选其一：</p><ol><li>使用 nvm / fvm 安装（推荐）<br>详见：<a href="./nvm">NVM / FNM 安装使用</a></li><li>使用 NodeSource（第三方源）安装：</li></ol><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt-get</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> curl</span></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -fsSL</span><span style="color:#98C379"> https://deb.nodesource.com/setup_26.x</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#D19A66"> -E</span><span style="color:#98C379"> bash</span><span style="color:#98C379"> -</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt-get</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> nodejs</span></span><span class="line"><span style="color:#61AFEF">node</span><span style="color:#D19A66"> -v</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li>使用系统源自带版本（版本可能过低，建议使用nvm）</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> nodejs</span><span style="color:#98C379"> npm</span></span></code></pre></td></tr></tbody></table></figure><div class="note danger flat"><p>对于使用 apt 安装的 Node.js，在进行全局组件的安装时，需要使用 <code>sudo</code>，否则会报权限错误。因为，此时的 Node.js 相当于操作系统的一部分。因此，如果长期需要开发软件，建议使用 NVM。</p></div><p>安装完成后，对包管理器进行换源：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">npm</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> -g</span><span style="color:#98C379"> set</span><span style="color:#98C379"> registry</span><span style="color:#98C379"> https://registry.npmmirror.com</span></span><span class="line"><span style="color:#56B6C2">echo</span><span style="color:#98C379"> "export COREPACK_NPM_REGISTRY=https://registry.npmmirror.com"</span><span style="color:#ABB2BF"> >> </span><span style="color:#98C379">~/.profile</span></span></code></pre></td></tr></tbody></table></figure><p>使用 corepack 启用 pnpm：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">corepack</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> pnpm</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-uv">安装 uv</h2><p>uv 是一款全新的 Python 包和项目管理器，使用 Rust 开发，速度飞快。相当于 Python 的 PNPM。开发、部署现代 Python 项目需要此工具。</p><p>文档：<a href="https://docs.astral.sh/uv/">https://docs.astral.sh/uv/</a></p><ol><li>如果需要换源，执行下面的命令，将镜像地址添加到环境变量中：</li></ol><figure class="highlight shell"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">tee</span><span style="color:#D19A66"> -a</span><span style="color:#98C379"> ~/.profile</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#98C379"># UV Mirrors</span></span><span class="line"><span style="color:#98C379"># export UV_INSTALLER_GITHUB_BASE_URL=https://&#x3C;GH加速>/https://github.com</span></span><span class="line"><span style="color:#98C379">export UV_DEFAULT_INDEX=https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple</span></span><span class="line"><span style="color:#98C379">export UV_PYTHON_INSTALL_MIRROR=https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#56B6C2">source</span><span style="color:#98C379"> ~/.profile</span></span></code></pre></td></tr></tbody></table></figure><ol start="2"><li>执行安装命令：</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -LsSf</span><span style="color:#98C379"> https://astral.sh/uv/install.sh</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sh</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Go">安装 Go</h2><p>（可选）</p><p>Go 是一款 Google 开源的编程语言，适合构建高性能的服务器和工具。</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> golang-go</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>Kali Linux 默认源自带的 Go 版本不是最新的，如果需要最新版本，可以通过官方提供的<a href="https://golang.google.cn/dl/">二进制包</a>进行安装。</p></blockquote><p>设置 GOPROXY（换源）</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">go</span><span style="color:#98C379"> env</span><span style="color:#D19A66"> -w</span><span style="color:#98C379"> GO111MODULE=on</span></span><span class="line"><span style="color:#61AFEF">go</span><span style="color:#98C379"> env</span><span style="color:#D19A66"> -w</span><span style="color:#98C379"> GOPROXY=https://goproxy.cn,direct</span></span></code></pre></td></tr></tbody></table></figure><p>设置 Go 二进制 PATH</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">tee</span><span style="color:#D19A66"> -a</span><span style="color:#98C379"> ~/.profile</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#98C379"># Go binary path</span></span><span class="line"><span style="color:#98C379">export PATH=</span><span style="color:#E06C75">$PATH</span><span style="color:#98C379">:$(</span><span style="color:#61AFEF">go</span><span style="color:#98C379"> env GOPATH)/bin</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#56B6C2">source</span><span style="color:#98C379"> ~/.profile</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># test</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># go install golang.org/x/website/tour@latest</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># tour</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Flatpak">安装 Flatpak</h2><p>（可选）</p><p>Flatpak 是一个用于在 Linux 上分发和运行应用程序的系统，支持沙箱化和跨发行版安装，解决了跨发行版分发应用兼容性等问题。对网络要求高，如无安装其他桌面软件的必要，可不安装。</p><ol><li>安装 Flatpak：</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> flatpak</span></span></code></pre></td></tr></tbody></table></figure><ol start="2"><li>添加 Flathub 仓库：</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> flatpak</span><span style="color:#98C379"> remote-add</span><span style="color:#D19A66"> --if-not-exists</span><span style="color:#98C379"> flathub</span><span style="color:#98C379"> https://flathub.org/repo/flathub.flatpakrepo</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li>切换到国内镜像：</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> flatpak</span><span style="color:#98C379"> remote-modify</span><span style="color:#98C379"> flathub</span><span style="color:#D19A66"> --url=https://mirrors.ustc.edu.cn/flathub</span></span></code></pre></td></tr></tbody></table></figure><p>安装 Flatseal 权限管理：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> flatpak</span><span style="color:#98C379"> install</span><span style="color:#98C379"> flathub</span><span style="color:#98C379"> com.github.tchx84.Flatseal</span></span></code></pre></td></tr></tbody></table></figure><p>安装 Flatpak 应用商店软件 GUI - Bazaar（集市）：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> flatpak</span><span style="color:#98C379"> install</span><span style="color:#98C379"> flathub</span><span style="color:#98C379"> io.github.kolunmi.Bazaar</span></span></code></pre></td></tr></tbody></table></figure><p>需要注销或重启后才能在开始菜单程序组中看到。</p><h2 id="安装-星火应用商店">安装 星火应用商店</h2><p>（可选）</p><p>星火应用商店（Spark Store）是社区驱动的 Linux 应用商店，支持所有主流 Linux 发行版。</p><p>使用星火应用商店可以方便的安装常用专有软件，如 QQ、微信、WPS Office 等。个人建议使用 Flatpak。</p><p><a href="https://www.spark-app.store/download">https://www.spark-app.store/download</a></p><h2 id="安装-AI-Agent">安装 AI Agent</h2><p>一般AI Agent需要配置好以下环境：</p><ul><li>Python 3（系统自带）<ul><li>uv（Python 包/版本管理工具，可选）</li></ul></li><li>Node.js<ul><li>npm</li><li>corepack + pnpm（可选）</li></ul></li><li>Git</li><li>ripgrep（rg）、radare2（r2）</li><li>Homebrew（可选）</li></ul><p>Agent 推荐可以使用 npm 安装。下面以 OpenCode（有免登录额度） + Chrome DevTools MCP 为例：</p><figure class="highlight shell"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 安装 OpenCode</span></span><span class="line"><span style="color:#61AFEF">npm</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -g</span><span style="color:#98C379"> opencode-ai</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> ~/.config/opencode</span></span><span class="line"><span style="color:#61AFEF">tee</span><span style="color:#98C379"> ~/.config/opencode/config.json</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#98C379">&#123;</span></span><span class="line"><span style="color:#98C379">  "lsp": true,</span></span><span class="line"><span style="color:#98C379">  "mcp": &#123;</span></span><span class="line"><span style="color:#98C379">    "chrome-devtools": &#123;</span></span><span class="line"><span style="color:#98C379">      "type": "local",</span></span><span class="line"><span style="color:#98C379">      "command": [</span></span><span class="line"><span style="color:#98C379">        "npx",</span></span><span class="line"><span style="color:#98C379">        "-y",</span></span><span class="line"><span style="color:#98C379">        "chrome-devtools-mcp@latest",</span></span><span class="line"><span style="color:#98C379">        "--no-usage-statistics",</span></span><span class="line"><span style="color:#98C379">        "--executable-path /usr/bin/chromium",</span></span><span class="line"><span style="color:#98C379">      ]</span></span><span class="line"><span style="color:#98C379">    &#125;</span></span><span class="line"><span style="color:#98C379">  &#125;</span></span><span class="line"><span style="color:#98C379">&#125;</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>是的，Kali Linux 还自带一个 Chromium。</p></blockquote><h2 id="安装字体">安装字体</h2><p>默认情况下，Kali Linux 自带的中文字体并不完整，虽然能显示中文，但是在使用一些对字体要求较高的软件是会出现问题，因此需要补全这些字体：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> fonts-noto-cjk</span></span><span class="line"><span style="color:#61AFEF">fc-cache</span><span style="color:#D19A66"> -fv</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-VS-Code">安装 VS Code</h2><p>Kali Linux 源有一个 <code>code-oss</code> 包，<strong>不是 Microsoft VS Code</strong>，可以通过以下命令安装：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> code-oss</span></span></code></pre></td></tr></tbody></table></figure><p>简单编辑文件是够用的，但体验可能会和官方的 VS Code 有差距。如果你希望安装官方的 VS Code，可以参考以下步骤：</p><figure class="highlight shell"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> wget</span><span style="color:#98C379"> gpg</span></span><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#D19A66"> -qO-</span><span style="color:#98C379"> https://packages.microsoft.com/keys/microsoft.asc</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> gpg</span><span style="color:#D19A66"> --dearmor</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> /usr/share/keyrings/microsoft.gpg</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#98C379"> /etc/apt/sources.list.d/vscode.sources</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#98C379">Types: deb</span></span><span class="line"><span style="color:#98C379">URIs: https://packages.microsoft.com/repos/code</span></span><span class="line"><span style="color:#98C379">Suites: stable</span></span><span class="line"><span style="color:#98C379">Components: main</span></span><span class="line"><span style="color:#98C379">Architectures: amd64,arm64,armhf</span></span><span class="line"><span style="color:#98C379">Signed-By: /usr/share/keyrings/microsoft.gpg</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> code</span><span style="color:#7F848E;font-style:italic"> # or code-insiders</span></span></code></pre></td></tr></tbody></table></figure><p>安装汉化插件：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">code</span><span style="color:#D19A66"> --install-extension</span><span style="color:#98C379"> ms-ceintl.vscode-language-pack-zh-hans</span></span></code></pre></td></tr></tbody></table></figure><p>然后启动 VS Code，按 <code>Ctrl+Shift+P</code> 打开命令面板，输入 <code>Configure Display Language</code>，选择 <code>zh-cn</code></p><p>设置为默认编辑器：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">xdg-mime</span><span style="color:#98C379"> default</span><span style="color:#98C379"> code.desktop</span><span style="color:#98C379"> text/plain</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> update-alternatives</span><span style="color:#D19A66"> --config</span><span style="color:#98C379"> editor</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-IDA-Pro">安装 IDA Pro</h2><ol><li><p>下载安装包和补丁，如 <code>ida-pro_9x_x64linux.run</code>、<code>kg_patch/keygen.js</code>。</p><ul><li>特别版发布地址：<a href="https://auth.lol">https://auth.lol</a></li><li>分流：<a href="https://dl.xrgzs.top/%E8%BD%AF%E4%BB%B6/IDA">https://dl.xrgzs.top/软件/IDA</a></li></ul></li><li><p>运行 <code>ida-pro_9x_x64linux.run</code>，完成安装</p><ul><li>需要注意，如果你是从浏览器下载的，默认没有可执行（<code>x</code>）权限，需要执行：<code>chmod +x ./ida-pro_9x_x64linux.run</code>，或右键下载的文件-&gt;属性，进入「权限」选项卡，勾选「Allow this file to run as a program」，授予可执行权限。</li></ul><p><img src="kali-image-setup/5a6156e9615cf30d5fa4c55ed2130e49.png" alt=""></p><p><img src="kali-image-setup/2964684a78ff01dec1336f9bfe05cb2f.png" alt=""></p></li></ol><ul><li>然后直接运行那个 <code>.run</code> 文件。</li><li>安装过程需要记一下安装位置是什么，如 <code>/home/kali/ida-pro-9.x/</code>，后面会用到。</li><li>全程下一步（Next）即可。</li></ul><ol start="2"><li>将下载的 <code>keygen.js</code> 放入 IDA Pro 的安装位置，然后使用 Node.js 运行，如下面的结果：</li></ol><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">┌──(kali㉿kali</span><span style="color:#ABB2BF">)-[~]</span></span><span class="line"><span style="color:#61AFEF">└─$</span><span style="color:#98C379"> cd</span><span style="color:#98C379"> /home/kali/ida-pro-9.x/</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">┌──(kali㉿kali</span><span style="color:#ABB2BF">)-[~/ida-pro-9.x]</span></span><span class="line"><span style="color:#61AFEF">└─$</span><span style="color:#98C379"> node</span><span style="color:#98C379"> keygen.js</span></span><span class="line"><span style="color:#61AFEF">License</span><span style="color:#98C379"> written</span><span style="color:#98C379"> to</span><span style="color:#98C379"> idapro.hexlic</span></span><span class="line"><span style="color:#61AFEF">Patching</span><span style="color:#98C379"> libida.so...</span></span><span class="line"><span style="color:#61AFEF">Patched</span><span style="color:#98C379"> libida.so</span><span style="color:#ABB2BF"> (2 </span><span style="color:#98C379">occurrences</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">Patching</span><span style="color:#98C379"> libida32.so...</span></span><span class="line"><span style="color:#61AFEF">Patched</span><span style="color:#98C379"> libida32.so</span><span style="color:#ABB2BF"> (2 </span><span style="color:#98C379">occurrences</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li>在开始菜单中找到 IDA Pro，打开并运行，后面使用的时候注意关闭自动更新。</li></ol><p>配置 IDA 官方 MCP（后续安装智能体时参考配置）</p><p><a href="https://github.com/HexRaysSA/ida-mcp">https://github.com/HexRaysSA/ida-mcp</a></p><figure class="highlight json"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">  "mcpServers"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    "ida"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">      "command"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"uvx"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      "args"</span><span style="color:#ABB2BF">: [</span></span><span class="line"><span style="color:#98C379">        "--exclude-newer=1s"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">        "ida-mcp"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">        "stdio"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">        "--agent=my-agent"</span></span><span class="line"><span style="color:#ABB2BF">      ]</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"><span style="color:#ABB2BF">  &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-DiE（Detect-it-Easy）">安装 DiE（Detect it Easy）</h2><p>这是一个方便的查壳软件。</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> detect-it-easy</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置浏览器">配置浏览器</h2><p>浏览器配置应人而意，这里不在赘述。</p><p>如果你需要避免泄露真实 IP，请关闭 WebRTC</p><p>Firefox浏览器关闭 WebRTC 方法：</p><ol><li>打开 Firefox，输入 <code>about:config</code>，按回车。</li><li>搜索 <code>media.peerconnection.enabled</code>，将其值设置为 <code>false</code>。</li></ol><p>Chrome 关闭 WebRTC 需要安装扩展，<a href="https://chromewebstore.google.com/detail/adguard-adblocker/bgnkhhnnamicmpeenaelnjfhikgbkllg">AdGuard</a> 支持关闭 WebRTC。</p><p>打 Web 推荐在 Firefox 安装 HackBar 插件：<a href="https://addons.mozilla.org/zh-CN/firefox/addon/hackbar-free/">https://addons.mozilla.org/zh-CN/firefox/addon/hackbar-free</a></p><h2 id="安装-CyberStrikeAI">安装 CyberStrikeAI</h2><p>（可选）</p><p>Cyberstrike是一个用于渗透测试和安全研究的自主AI代理。</p><p>仓库：<a href="https://github.com/AIPentest/CyberStrikeAI">https://github.com/AIPentest/CyberStrikeAI</a></p><p>一些可能会用到的依赖：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -y</span><span style="color:#98C379"> nmap</span><span style="color:#98C379"> masscan</span><span style="color:#98C379"> sqlmap</span><span style="color:#98C379"> nikto</span><span style="color:#98C379"> gobuster</span><span style="color:#98C379"> hydra</span><span style="color:#98C379"> hashcat</span><span style="color:#98C379"> john</span><span style="color:#98C379"> binwalk</span><span style="color:#98C379"> qemu-utils​</span></span></code></pre></td></tr></tbody></table></figure><ol><li>配置 Go 环境（见上文）</li><li>克隆仓库：<br><code>GH加速</code> 请替换为实际能使用的站点，具体可以<a href="https://cn.bing.com/search?q=ghproxy">百度</a>，这里不提供，**请勿无脑复制！**如果你已经解决网络问题，可删除 <code>https://&lt;GH加速&gt;/</code>。</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">git</span><span style="color:#98C379"> clone</span><span style="color:#98C379"> https://</span><span style="color:#ABB2BF">&#x3C;</span><span style="color:#98C379">GH加</span><span style="color:#ABB2BF">速></span><span style="color:#98C379">/https://github.com/AIPentest/CyberStrikeAI.git</span></span><span class="line"><span style="color:#56B6C2">cd</span><span style="color:#98C379"> CyberStrikeAI</span></span></code></pre></td></tr></tbody></table></figure><ol start="3"><li>运行安装脚本</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> run.sh</span><span style="color:#ABB2BF"> &#x26;&#x26; </span><span style="color:#61AFEF">./run.sh</span></span></code></pre></td></tr></tbody></table></figure><ol start="4"><li>首次启动会显示用户和密码，请务必记下，后续不再显示。浏览器访问 <a href="https://127.0.0.1:8080">https://127.0.0.1:8080</a>，忽略证书警告继续访问，登录系统，配置 AI 模型 API 密钥信息，导入知识库，配置一些必要的 MCP，即可投入使用。</li></ol><p>默认可能不支持搜索，可添加 open-websearch：</p><figure class="highlight json"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">  "web-search"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    "command"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"npx"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    "args"</span><span style="color:#ABB2BF">: [</span></span><span class="line"><span style="color:#98C379">      "open-websearch@latest"</span></span><span class="line"><span style="color:#ABB2BF">    ],</span></span><span class="line"><span style="color:#E06C75">    "env"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">      "MODE"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"stdio"</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"><span style="color:#ABB2BF">  &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><hr><p>如果你不需要远程访问，可以忽略下面的内容。</p><h2 id="配置-VNC">配置 VNC</h2><blockquote><p>实测 VNC 不如 RDP 好用，如无必要建议跳过此章直接使用 <a href="#%E9%85%8D%E7%BD%AE-xrdp">RDP</a>。</p></blockquote><p>Kali Linux 自带 <code>tightvnc</code>，没有 <code>tigervnc</code> 好用。比如屏幕分辨率自动适配、剪贴板共享等功能 <code>tightvnc</code> 都不支持。因此我们需要将其更换成<code>tigervnc</code>。</p><ol><li>安装 <code>tigervnc</code></li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo apt remove tightvncserver # 不能执行，会卸载 kali-linux-default 包</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> tigervnc-standalone-server</span><span style="color:#98C379"> tigervnc-common</span><span style="color:#98C379"> tigervnc-tools</span></span></code></pre></td></tr></tbody></table></figure><p><code>update-alternatives</code> 会自动将 <code>vncserver</code> 指向 <code>/usr/bin/tigervncserver</code>！</p><ol start="2"><li><p>配置 <code>xstartup</code> 文件</p><p>VNC 配置文件位于：</p><ul><li><code>~/.vnc</code>：tigervnc / tightvnc 通用</li><li><code>~/.config/tigervnc</code>：tigervnc 优先使用</li></ul></li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> ~/.vnc</span></span><span class="line"><span style="color:#61AFEF">vim</span><span style="color:#98C379"> ~/.vnc/xstartup</span></span></code></pre></td></tr></tbody></table></figure><p>写入以下内容：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">#!/bin/sh</span></span><span class="line"><span style="color:#56B6C2">unset</span><span style="color:#98C379"> SESSION_MANAGER</span></span><span class="line"><span style="color:#56B6C2">unset</span><span style="color:#98C379"> DBUS_SESSION_BUS_ADDRESS</span></span><span class="line"><span style="color:#56B6C2">exec</span><span style="color:#98C379"> dbus-run-session</span><span style="color:#98C379"> startxfce4</span></span></code></pre></td></tr></tbody></table></figure><p>然后</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> ~/.vnc/xstartup</span></span></code></pre></td></tr></tbody></table></figure><p>先手动测试：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">tigervncserver</span><span style="color:#98C379"> :1</span><span style="color:#D19A66"> -localhost</span><span style="color:#98C379"> no</span></span></code></pre></td></tr></tbody></table></figure><p>根据提示输入密码，之后想改密码，可以使用 <code>vncpasswd</code> 命令。</p><p>能连上 IP:5901 并看到 XFCE 后，再做开机自启。</p><ol start="3"><li>配置开机自启</li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> /etc/systemd/system/vncserver@.service</span></span></code></pre></td></tr></tbody></table></figure><p>写入以下内容：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[Unit]</span></span><span class="line"><span style="color:#E06C75">Description</span><span style="color:#56B6C2">=</span><span style="color:#98C379">Start</span><span style="color:#61AFEF"> TigerVNC</span><span style="color:#98C379"> server</span><span style="color:#98C379"> at</span><span style="color:#98C379"> startup</span></span><span class="line"><span style="color:#E06C75">After</span><span style="color:#56B6C2">=</span><span style="color:#98C379">syslog.target</span><span style="color:#61AFEF"> network.target</span></span><span class="line"><span style="color:#ABB2BF">[Service]</span></span><span class="line"><span style="color:#E06C75">Type</span><span style="color:#56B6C2">=</span><span style="color:#98C379">forking</span></span><span class="line"><span style="color:#E06C75">User</span><span style="color:#56B6C2">=</span><span style="color:#98C379">kali</span></span><span class="line"><span style="color:#E06C75">Group</span><span style="color:#56B6C2">=</span><span style="color:#98C379">kali</span></span><span class="line"><span style="color:#E06C75">WorkingDirectory</span><span style="color:#56B6C2">=</span><span style="color:#98C379">/home/kali</span></span><span class="line"><span style="color:#E06C75">PIDFile</span><span style="color:#56B6C2">=</span><span style="color:#98C379">/home/kali/.vnc/%H:%i.pid</span></span><span class="line"><span style="color:#E06C75">ExecStartPre</span><span style="color:#56B6C2">=</span><span style="color:#98C379">-/usr/bin/tigervncserver</span><span style="color:#61AFEF"> -kill</span><span style="color:#98C379"> :%i</span><span style="color:#ABB2BF"> > </span><span style="color:#98C379">/dev/null</span></span><span class="line"><span style="color:#E06C75">ExecStart</span><span style="color:#56B6C2">=</span><span style="color:#98C379">/usr/bin/tigervncserver</span><span style="color:#56B6C2"> :</span><span style="color:#98C379">%i</span><span style="color:#D19A66"> -localhost</span><span style="color:#98C379"> no</span></span><span class="line"><span style="color:#E06C75">ExecStop</span><span style="color:#56B6C2">=</span><span style="color:#98C379">/usr/bin/tigervncserver</span><span style="color:#61AFEF"> -kill</span><span style="color:#98C379"> :%i</span></span><span class="line"><span style="color:#ABB2BF">[Install]</span></span><span class="line"><span style="color:#E06C75">WantedBy</span><span style="color:#56B6C2">=</span><span style="color:#98C379">multi-user.target</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>如果你使用的是 <code>tightvnc</code>, 将上面的 <code>tigervncserver</code> 替换为 <code>vncserver</code>，并删除 <code>-localhost no</code> 参数。</p></blockquote><p>现在我们需要让系统知道我们的新单元文件。执行以下命令：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> daemon-reload</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> vncserver@1.service</span></span></code></pre></td></tr></tbody></table></figure><p>完成上述操作后，我们现在可以将VNC服务器作为系统服务启动、停止和重新启动。为了测试这一点，让我们先用以下命令停止之前启动的实例：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">tigervncserver</span><span style="color:#D19A66"> -kill</span><span style="color:#98C379"> :1</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> start</span><span style="color:#98C379"> vncserver@1</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> vncserver@1</span></span></code></pre></td></tr></tbody></table></figure><ol start="4"><li>推荐客户端<ul><li>Windows：<ul><li><a href="https://mobaxterm.mobatek.net/">MobaXterm</a></li><li><a href="https://tigervnc.org/">TigerVNC Viewer</a></li></ul></li><li>Linux：<ul><li><a href="https://remmina.org/">Remmina</a></li></ul></li><li>macOS：<ul><li><a href="https://www.realvnc.com/en/connect/download/viewer/">VNC Viewer</a></li></ul></li></ul></li></ol><h2 id="配置-XRDP">配置 XRDP</h2><p>相比 VNC，<code>xrdp</code> 使用 RDP 协议，屏幕分辨率自动适配、剪贴板共享、声音重定向等功能都比传统 VNC 更方便。</p><ol><li>安装 <code>xrdp</code></li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> xrdp</span><span style="color:#98C379"> xorgxrdp</span></span></code></pre></td></tr></tbody></table></figure><ol start="2"><li><p>配置开机自启</p><p>安装完成后，启用 <code>xrdp</code> 服务：</p></li></ol><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> xrdp</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> start</span><span style="color:#98C379"> xrdp</span></span></code></pre></td></tr></tbody></table></figure><p>检查服务状态：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> xrdp</span></span></code></pre></td></tr></tbody></table></figure><p>如果看到：<code>Active: active (running)</code>，说明 <code>xrdp</code> 已经正常运行。</p><ol start="3"><li><p>配置 XRDP</p><p>无需配置，保证默认即可，配置文件位于：<code>/etc/xrdp/startwm.sh</code>。</p></li><li><p>注销</p><p>首次使用需要注销后才能使用 RDP 连接，否则会闪退。</p></li><li><p>推荐客户端</p><p>直接使用 Linux PAM 认证，无需单独设置密码。默认端口为 3389。</p><ul><li>Windows：<ul><li><a href="https://learn.microsoft.com/zh-cn/windows-server/remote/remote-desktop-services/remotepc/uninstall-remote-desktop-connection">MSTSC（远程桌面连接）</a></li><li><a href="https://mobaxterm.mobatek.net/">MobaXterm</a></li></ul></li><li>Linux：<ul><li><a href="https://remmina.org/">Remmina</a></li><li><a href="https://www.freerdp.com/">FreeRDP</a></li></ul></li><li>macOS：<ul><li><a href="https://apps.apple.com/us/app/microsoft-remote-desktop/id1295203466?mt=12">Windows APP（Microsoft Remote Desktop）</a></li></ul></li></ul></li></ol>]]>
    </content>
    <id>https://www.xrgzs.top/posts/kali-image-setup</id>
    <link href="https://www.xrgzs.top/posts/kali-image-setup"/>
    <published>2026-09-29T05:47:00.000Z</published>
    <summary>
      <![CDATA[<p>Kali Linux 官方提供了现成的虚拟机镜像，直接下载后导入虚拟机即可使用，无需手动安装ISO。但官方镜像默认是英文界面，且没有安装中文输入法，使用起来不太方便。本文将介绍如何下载官方镜像，并进行一些简单的配置。</p>
<h2 id="镜像使用说明">镜像使用说明</]]>
    </summary>
    <title>Kali Linux 镜像使用配置</title>
    <updated>2026-09-29T05:47:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Rust" scheme="https://www.xrgzs.top/tags/rust/"/>
    <category term="Tauri" scheme="https://www.xrgzs.top/tags/tauri/"/>
    <content>
      <![CDATA[<p><img src="tauri-win7/cover.webp" alt=""></p><p>如果希望使用 <strong>Tauri 2</strong> 开发一个同时兼容 Windows 7 的桌面应用，可以通过 <strong>Rust + VC-LTL5 + YY-Thunks</strong> 的方式解决新版本 Rust 对旧版 Windows API 的兼容问题。</p><p>本文记录一套实际测试成功的配置。</p><h2 id="1-前置条件">1. 前置条件</h2><p>本文测试环境：</p><figure class="highlight text"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span>Tauri       2.11.1</span></span><span class="line"><span>Rust        1.97.1 (MSVC)</span></span><span class="line"><span>Windows     Windows 10 19045 x64 (Dev) / Windows 7 SP1 x64</span></span></code></pre></td></tr></tbody></table></figure><h3 id="1-1-Rust-MSVC环境">1.1 Rust + MSVC环境</h3><p>这个一般都有，就TLDR了。这里直接安装 VS2026。</p><h3 id="1-2-VC-LTL5-YY-Thunks">1.2 VC-LTL5+YY-Thunks</h3><p>这个一般没有，所以我们需要进行安装。</p><p>因为我自己的Scoop Bucket已经有了，所以我使用Scoop来安装：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop bucket add https:</span><span style="color:#56B6C2">//github.com/</span><span style="color:#ABB2BF">xrgzs</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">sdoog.git</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">vc</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">ltl sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">yy</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">thunks</span></span></code></pre></td></tr></tbody></table></figure><h3 id="1-3-创建-Tauri-2-项目">1.3 创建 Tauri 2 项目</h3><p>创建一个普通的 Tauri 2 项目：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">pnpm</span><span style="color:#98C379"> create</span><span style="color:#98C379"> tauri-app</span></span></code></pre></td></tr></tbody></table></figure><p>正常创建完成后，Tauri 的 <code>src-tauri/Cargo.toml</code> 类似：</p><figure class="highlight toml"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">package</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">name</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"tauri-app"</span></span><span class="line"><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"0.1.0"</span></span><span class="line"><span style="color:#E06C75">description</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"A Tauri App"</span></span><span class="line"><span style="color:#E06C75">authors</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"you"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">edition</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"2021"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">lib</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># The `_lib` suffix may seem redundant but it is necessary</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># to make the lib name unique and wouldn't conflict with the bin name.</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519</span></span><span class="line"><span style="color:#E06C75">name</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"tauri_app_lib"</span></span><span class="line"><span style="color:#E06C75">crate-type</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"staticlib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"cdylib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"rlib"</span><span style="color:#ABB2BF">]</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">build-dependencies</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">tauri-build</span><span style="color:#ABB2BF"> = &#123; </span><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"2"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [] &#125;</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">dependencies</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">tauri</span><span style="color:#ABB2BF"> = &#123; </span><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"2"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [] &#125;</span></span><span class="line"><span style="color:#E06C75">tauri-plugin-opener</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"2"</span></span><span class="line"><span style="color:#E06C75">serde</span><span style="color:#ABB2BF"> = &#123; </span><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"1"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"derive"</span><span style="color:#ABB2BF">] &#125;</span></span><span class="line"><span style="color:#E06C75">serde_json</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"1"</span></span></code></pre></td></tr></tbody></table></figure><h2 id="2-添加-thunk-rs">2. 添加 thunk-rs</h2><p>这里使用 <a href="https://docs.rs/thunk-rs"><code>thunk-rs</code></a> 来集成 VC-LTL5 和 YY-Thunks。</p><p>进入 <code>src-tauri</code>：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">cd</span><span style="color:#98C379"> src-tauri</span></span><span class="line"><span style="color:#61AFEF">cargo</span><span style="color:#98C379"> add</span><span style="color:#98C379"> thunk-rs</span><span style="color:#D19A66"> --build</span></span></code></pre></td></tr></tbody></table></figure><p>默认情况下，<code>thunk-rs</code> 会启用 XP 相关 feature，因此需要手动修改 <code>Cargo.toml</code>。</p><p>最终配置：</p><figure class="highlight toml"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">build-dependencies</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">tauri-build</span><span style="color:#ABB2BF"> = &#123; </span><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"2"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [] &#125;</span></span><span class="line"><span style="color:#E06C75">thunk-rs</span><span style="color:#ABB2BF"> = &#123; </span><span style="color:#E06C75">version</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">"0.3.5"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">default-features</span><span style="color:#ABB2BF"> = </span><span style="color:#D19A66">false</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> =  [</span><span style="color:#98C379">"win7"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"lib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"subsystem_windows"</span><span style="color:#ABB2BF">] &#125;</span></span></code></pre></td></tr></tbody></table></figure><p>这里三个 feature 分别非常重要：</p><ol><li><code>win7</code>：启用 Windows 7 对应的 YY-Thunks。</li><li><code>lib</code>：用于正确处理 Tauri 同时包含 library target 的情况。</li><li><code>subsystem_windows</code>：将最终 Windows 程序设置为 GUI subsystem，从而避免启动时出现额外的控制台窗口。</li></ol><h2 id="3-修改-build-rs">3. 修改 <code>build.rs</code></h2><p>Tauri 默认的：</p><figure class="highlight rust"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">fn</span><span style="color:#61AFEF"> main</span><span style="color:#ABB2BF">() &#123;</span></span><span class="line"><span style="color:#E5C07B">    tauri_build</span><span style="color:#ABB2BF">::</span><span style="color:#61AFEF">build</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>修改为：</p><figure class="highlight rust"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">fn</span><span style="color:#61AFEF"> main</span><span style="color:#ABB2BF">() &#123;</span></span><span class="line"><span style="color:#E5C07B">    thunk</span><span style="color:#ABB2BF">::</span><span style="color:#61AFEF">thunk</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#E5C07B">    tauri_build</span><span style="color:#ABB2BF">::</span><span style="color:#61AFEF">build</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>这样在编译过程中，<code>thunk-rs</code> 会自动将对应的 VC-LTL5 和 YY-Thunks 链接配置加入项目。</p><h2 id="4-构建">4. 构建</h2><p>修改完成后：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">pnpm</span><span style="color:#98C379"> tauri</span><span style="color:#98C379"> build</span></span></code></pre></td></tr></tbody></table></figure><p>或者：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">cargo</span><span style="color:#98C379"> build</span><span style="color:#D19A66"> --release</span></span></code></pre></td></tr></tbody></table></figure><p>成功时可以在构建输出中看到类似：</p><figure class="highlight text"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span>VC-LTL5 Enabled: 6.0.6000.0(x64)</span></span><span class="line"><span>YY-Thunks Enabled: Win7(x64)</span></span><span class="line"><span>Subsystem is set to WINDOWS</span></span></code></pre></td></tr></tbody></table></figure><p>这说明已经进入最终链接过程。</p><h2 id="5-测试">5. 测试</h2><p>为了避免 Edge WebView2 安装失败，这里手动安装好了 109 版本的 WebView2，下载地址：</p><p><a href="https://dl.xrgzs.top/%E8%BD%AF%E4%BB%B6/Edge/109">dl.xrgzs.top/软件/Edge/109</a></p><p>Tip：Edge 安装包默认安装 Edge 到用户目录，但是也可以作为 Edge WebView2 安装包来使用，通过这种方法安装不会带自动更新程序（<s>本身也停更了不需要</s>）：</p><figure class="highlight cmd"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">MicrosoftEdge_X64_109.0.1518.78_Stable.exe --msedgewebview --verbose-logging --do-not-launch-msedge --system-level</span></span></code></pre></td></tr></tbody></table></figure><p>将 <code>src-tauri\target\release\bundle\nsis\tauri-app_0.1.0_x64-setup.exe</code> 放入 Windows 7 机器安装。测试工作正常。</p><p><img src="tauri-win7/20260824-142904.png" alt=""></p><p>这个环境是打过补丁和安装了VC运行库的，那没打过补丁的 Windows 7 SP1 能用吗？实测也是可以的。下面是最小环境测试，整个系统仅安装了 Edge WebView2：</p><p><img src="tauri-win7/20260824-145925.png" alt=""></p><h2 id="补充内容">补充内容</h2><h3 id="为什么需要-YY-Thunks？">为什么需要 YY-Thunks？</h3><p>问题的核心并不是 Tauri 本身。现代 Rust 和现代 Windows 开发工具链可能会使用一些较新的 Windows API，而 Windows 7 并不存在这些 API。YY-Thunks 的作用就是在链接阶段提供兼容层，因此它可以让一些原本面向较新 Windows 的程序继续运行在 Windows 7 上。</p><h3 id="【重要】为什么必须加-lib">【重要】为什么必须加 <code>lib</code></h3><p>这是整个过程中比较容易踩的坑。</p><p>如果不加的话，会出现下面这种情况，启动会带一个命令行窗口：</p><p><img src="tauri-win7/4d97e7438c4646004955c7301fe322c7.png" alt=""></p><p>解决这个问题，加上 <code>lib</code> 是最终解决方案。</p><p>如果不加的话，会造成无法编译，因为 Tauri 项目并不是单纯生成一个 EXE，它同时包含：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>tauri-app.exe</span></span><span class="line"><span>tauri_app_lib.dll</span></span></code></pre></td></tr></tbody></table></figure><p>项目的 <code>Cargo.toml</code> 中有：</p><figure class="highlight toml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">lib</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">crate-type</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"staticlib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"cdylib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"rlib"</span><span style="color:#ABB2BF">]</span></span></code></pre></td></tr></tbody></table></figure><p>如果只配置：</p><figure class="highlight toml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"win7"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"subsystem_windows"</span><span style="color:#ABB2BF">]</span></span></code></pre></td></tr></tbody></table></figure><p>在链接 <code>tauri_app_lib.dll</code> 时可能出现：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>LNK2019: unresolved external symbol main</span></span></code></pre></td></tr></tbody></table></figure><p>因为 <code>subsystem_windows</code> 会使链接器使用类似：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>/SUBSYSTEM:WINDOWS</span></span><span class="line"><span>/ENTRY:mainCRTStartup</span></span></code></pre></td></tr></tbody></table></figure><p>而 DLL 并不存在 <code>main()</code>。</p><p>加入：</p><figure class="highlight toml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#E06C75">features</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"win7"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"lib"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"subsystem_windows"</span><span style="color:#ABB2BF">]</span></span></code></pre></td></tr></tbody></table></figure><p>之后，<code>thunk-rs</code> 可以正确区分 library target，从而解决这个链接错误。</p><p>附：<code>thunk-rs</code> 的 features</p><figure class="highlight toml"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#61AFEF">features</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">default</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"xp"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">lib</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">subsystem_windows</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">vc_ltl_only</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">vista</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">win10</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"win10_10240"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">win10_10240</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">win10_19041</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">win7</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">win8</span><span style="color:#ABB2BF"> = []</span></span><span class="line"><span style="color:#E06C75">windows_vista</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"vista"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">windows_xp</span><span style="color:#ABB2BF"> = [</span><span style="color:#98C379">"xp"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">xp</span><span style="color:#ABB2BF"> = []</span></span></code></pre></td></tr></tbody></table></figure><h3 id="需要注意：这不等于-Tauri-官方“无条件支持-Windows-7”">需要注意：这不等于 Tauri 官方“无条件支持 Windows 7”</h3><p>这里需要区分：“<strong>能够通过兼容层运行</strong>”和“<strong>官方当前版本完整支持 Windows 7</strong>”并不是完全相同的概念。正常情况下，Tauri v2是不支持Windows 7的。本文通过野路子实现在Windows7上运行，出现问题与官方无关。其次，Rust、第三方插件蹬都可能存在自己的最低系统要求。因此实际项目必须在Windows 7 SP1的真实环境或者虚拟机中测试。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/tauri-win7</id>
    <link href="https://www.xrgzs.top/posts/tauri-win7"/>
    <published>2026-08-24T06:46:00.000Z</published>
    <summary>使用 thunk-rs 让 Tauri 2 适配 Windows 7</summary>
    <title>Tauri 2 适配 Windows 7</title>
    <updated>2026-08-24T06:46:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="踩坑" scheme="https://www.xrgzs.top/tags/%E8%B8%A9%E5%9D%91/"/>
    <content>
      <![CDATA[<p><img src="kali-linux-xfce-light-mode/cover.webp" alt=""></p><p>一开始，我以为这应该是一件很简单的事情。Kali Linux 用的是 Xfce，我想把整个桌面从深色切换成浅色。按照正常理解，无非就是打开 <strong>Settings → Appearance → Style</strong>，选一个 <code>Kali-Light</code>，事情应该到此结束。</p><p><img src="kali-linux-xfce-light-mode/20260821-160336.png" alt=""></p><p>结果现实告诉我：没那么简单。Xfce 是浅色的，QTerminal 还是黑的；Firefox 的 Automatic 还是黑的；后来装了 Chrome，发现 Chrome 默认也是深色。</p><p><img src="kali-linux-xfce-light-mode/20260821-160433.png" alt="深色模式的QTerminal"></p><p>这时候问题就变得有意思了：到底是我哪里没设置好，还是 Linux 的“浅色模式”根本就不是一个统一的东西？最后一路排查下来，答案是后者。</p><h2 id="折腾过程">折腾过程</h2><h3 id="第一关：Kali-Light-明明已经选上了">第一关：Kali-Light 明明已经选上了</h3><p>首先检查 Xfce：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">xfconf-query</span><span style="color:#D19A66"> -c</span><span style="color:#98C379"> xsettings</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> /Net/ThemeName</span></span></code></pre></td></tr></tbody></table></figure><p>返回：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>Kali-Light</span></span></code></pre></td></tr></tbody></table></figure><p>这说明 Xfce 自己认为当前主题就是浅色。于是我最开始的判断是：</p><blockquote><p>那应该只是某些软件没有跟随 Xfce。</p></blockquote><p>这个判断其实没错，但还不完整。因为接下来发现，问题并不只是 Qt。</p><h3 id="第二关：QTerminal-为什么还在黑着？">第二关：QTerminal 为什么还在黑着？</h3><p>QTerminal 是 Qt 应用，而 Xfce 的 Appearance 主要解决的是 GTK/Xfconf 这一套。</p><p>检查环境变量：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">echo</span><span style="color:#E06C75"> $QT_QPA_PLATFORMTHEME</span></span></code></pre></td></tr></tbody></table></figure><p>结果是：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>qt5ct</span></span></code></pre></td></tr></tbody></table></figure><p>这就解释了 QTerminal 为什么不听 Xfce 的话——Qt 被 <code>qt5ct</code> 接管了。</p><p>所以我打开：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">qt5ct</span></span><span class="line"><span style="color:#61AFEF">qt6ct</span></span></code></pre></td></tr></tbody></table></figure><p>再到里面选择浅色配色。QTerminal是 Qt6 编写的，所以调整 <code>qt6ct</code> 才生效。</p><p><img src="kali-linux-xfce-light-mode/20260821-160643.png" alt="设置可以启动qt5ct、qt6ct"></p><p>到这里其实已经能看出 Linux 桌面主题系统的一个特点：</p><blockquote><p><strong>GTK 是一套，Qt 又是一套。</strong></p></blockquote><p>Xfce 选了 Light，不代表 Qt 就必须 Light。事情开始变复杂了。</p><h3 id="第三关：Firefox-也不对劲">第三关：Firefox 也不对劲</h3><p>然后我发现 Firefox 的 <strong>Website appearance → Automatic</strong> 还是深色。这时候很自然会怀疑 Firefox。于是去 <a href="about:config">about:config</a> 搜索：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>ui.systemUsesDarkTheme</span></span></code></pre></td></tr></tbody></table></figure><p>结果发现这个设置根本不存在。于是又检查：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>layout.css.prefers-color-scheme.content-override</span></span></code></pre></td></tr></tbody></table></figure><p>结果是：<code>2</code>，也就是跟随系统。</p><p>看起来 Firefox 的配置并没有明显问题。这时候我开始怀疑：</p><blockquote><p>会不会根本不是 Firefox 的问题？</p></blockquote><h3 id="第四关：Chrome-出现以后，事情突然变简单了">第四关：Chrome 出现以后，事情突然变简单了</h3><p>后来安装了 Chrome。结果发现：Chrome 默认也是 Dark。这一刻反而是个好消息。因为如果只有 Firefox 出问题，还可以继续怀疑 Firefox。</p><p>但现在：</p><ul><li>Xfce：Light</li><li>QTerminal：Dark</li><li>Firefox：Dark</li><li>Chrome：Dark</li></ul><p>多个完全不同的软件都出现类似现象。这已经不像某一个应用的 Bug 了。于是开始往“系统到底告诉应用自己是什么颜色”这个方向查。</p><h3 id="第五关：检查-GTK">第五关：检查 GTK</h3><p>先看看有没有 GTK 自己的配置：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">cat</span><span style="color:#98C379"> ~/.config/gtk-3.0/settings.ini</span></span></code></pre></td></tr></tbody></table></figure><p>结果：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>没有那个文件或目录</span></span></code></pre></td></tr></tbody></table></figure><p>GTK 4 也是一样。</p><p>然后检查有没有强制 Dark：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">grep</span><span style="color:#D19A66"> -Rni</span><span style="color:#98C379"> "prefer-dark\|dark-theme\|color-scheme"</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#ABB2BF">~/.config/gtk-3.0 </span><span style="color:#56B6C2">\</span></span><span class="line"><span style="color:#ABB2BF">~/.config/gtk-4.0 </span><span style="color:#56B6C2">\</span></span><span class="line"><span style="color:#ABB2BF">2>/dev/null</span></span></code></pre></td></tr></tbody></table></figure><p>没有任何结果。</p><p>这反而是个好消息。</p><p>说明至少没有发现一个类似：</p><figure class="highlight ini"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">gtk-application-prefer-dark-theme</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">true</span></span></code></pre></td></tr></tbody></table></figure><p>这样的东西在背后搞鬼。</p><h3 id="第六关：Xfce-本身真的没问题">第六关：Xfce 本身真的没问题</h3><p>再看 Xfce 的完整配置：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">xfconf-query</span><span style="color:#D19A66"> -c</span><span style="color:#98C379"> xsettings</span><span style="color:#D19A66"> -lv</span></span></code></pre></td></tr></tbody></table></figure><p>其中最关键的是：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>/Net/ThemeName      Kali-Light</span></span></code></pre></td></tr></tbody></table></figure><p>所以 Xfce 这一层可以基本排除。但这时候我突然意识到：</p><blockquote><p>Xfce 说自己是 Light，并不代表所有程序都会去问 Xfce。</p></blockquote><p>现代 Linux 应用还有另外一条路。</p><h3 id="第七关：GSettings-才是真正的坑">第七关：GSettings 才是真正的坑</h3><p>于是检查：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> get</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> gtk-theme</span></span></code></pre></td></tr></tbody></table></figure><p>得到：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>'Kali-Light'</span></span></code></pre></td></tr></tbody></table></figure><p>看起来一切正常。但是再检查：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> get</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span></span></code></pre></td></tr></tbody></table></figure><p>结果却是：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>'prefer-dark'</span></span></code></pre></td></tr></tbody></table></figure><p>这一下问题基本浮出水面了。</p><p>注意，这两个东西不是一回事：</p><ul><li>gtk-theme -&gt; Kali-Light</li></ul><p>而：</p><ul><li>color-scheme -&gt; prefer-dark</li></ul><p>也就是说，我的系统实际上处于一种很奇怪的状态：</p><blockquote><p><strong>主题是 Light，但系统颜色偏好却是 Dark。</strong></p></blockquote><p>这就像把房子的墙刷成白色，然后在门口挂了一个牌子：</p><blockquote><p>“本屋偏好黑色。”</p></blockquote><h3 id="第八关：XDG-Desktop-Portal-终于把证据补齐">第八关：XDG Desktop Portal 终于把证据补齐</h3><p>为了确认现代应用到底拿到了什么信息，又检查了：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">dbus-send</span><span style="color:#D19A66"> --session</span><span style="color:#D19A66"> --print-reply</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#D19A66">  --dest=org.freedesktop.portal.Desktop</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#98C379">  /org/freedesktop/portal/desktop</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#98C379">  org.freedesktop.portal.Settings.Read</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#98C379">  string:org.freedesktop.appearance</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#98C379">  string:color-scheme</span></span></code></pre></td></tr></tbody></table></figure><p>结果是：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>uint32 1</span></span></code></pre></td></tr></tbody></table></figure><p>这里我还踩了一个坑。一开始我把 <code>1</code> 错误地理解成了 Light。后来查了 XDG Desktop Portal 的规范才发现，正确的定义是：</p><figure class="highlight text"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span>0 = No preference</span></span><span class="line"><span>1 = Prefer dark</span></span><span class="line"><span>2 = Prefer light</span></span></code></pre></td></tr></tbody></table></figure><p>所以<code>uint32 1</code>实际上是在告诉应用：</p><blockquote><p><strong>我偏好 Dark。</strong></p></blockquote><p>这一下所有事情突然串起来了。</p><h2 id="真正的问题">真正的问题</h2><p>整个系统当时实际上是：</p><ul><li>Xfce<ul><li>ThemeName = Kali‑Light ✅</li></ul></li><li>GSettings<ul><li>gtk‑theme = Kali‑Light ✅</li><li>color‑scheme = prefer‑dark ❌</li></ul></li><li>XDG Desktop Portal<ul><li>color‑scheme = 1 ❌ Dark</li></ul></li><li>Chrome<ul><li>看到系统偏好 Dark ❌</li></ul></li><li>Firefox<ul><li>看到系统 / 主题偏好 Dark ❌</li></ul></li></ul><p>所以之前一直在折腾 Firefox、Chrome、GTK，其实有点走偏了。</p><p>真正的问题在更下面一层：</p><blockquote><p><strong>Xfce 的主题已经切成 Light，但是 GSettings 的系统颜色偏好仍然停留在 Dark。</strong></p></blockquote><h3 id="最后的解决办法反而非常简单">最后的解决办法反而非常简单</h3><p>执行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> set</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span><span style="color:#98C379"> 'prefer-light'</span></span></code></pre></td></tr></tbody></table></figure><p>然后检查：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> get</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span></span></code></pre></td></tr></tbody></table></figure><p>变成：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>'prefer-light'</span></span></code></pre></td></tr></tbody></table></figure><p>再重新打开 Chrome，结果正常了，Firefox 也恢复正常。这时候才真正意识到，前面折腾了那么久，其实只差这一行。</p><h2 id="最终配置">最终配置</h2><p>以后在 Kali Xfce 上切换浅色，我会按照下面这个顺序来。</p><h3 id="①-Xfce">① Xfce</h3><p><strong>Settings 设置 → Appearance 外观 → Style 样式</strong></p><p>选择：<code>Kali-Light</code></p><p><strong>Appearance 外观 → Icons 图标</strong></p><p>选择：<code>Flat-Remix-Blue-Light</code></p><p><strong>Settings 设置 → LightDM桌面管理器（GTK界面）：设置 → 界面</strong></p><p>选择：<code>Kali-Light</code></p><h3 id="②-系统颜色偏好">② 系统颜色偏好</h3><p>执行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> set</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span><span style="color:#98C379"> 'prefer-light'</span></span></code></pre></td></tr></tbody></table></figure><p>检查：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> get</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span></span></code></pre></td></tr></tbody></table></figure><p>应该是：<code>'prefer-light'</code>。</p><h3 id="③-Qt">③ Qt</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">qt5ct</span></span><span class="line"><span style="color:#61AFEF">qt6ct</span></span></code></pre></td></tr></tbody></table></figure><p>（也可以在设置中打开）</p><p>选择 <code>Kali-Light</code>。</p><h3 id="④-QTerminal">④ QTerminal</h3><p>QTerminal 的终端配色在 <strong>会话  → 参数配置 → 界面设置 → 终端颜色</strong> 里单独调整。</p><p><img src="kali-linux-xfce-light-mode/20260821-161058.png" alt=""></p><h3 id="⑤-Firefox-Chrome">⑤ Firefox / Chrome</h3><p>保持 <strong>系统/Automatic</strong> 即可。</p><h2 id="总结">总结</h2><p>这次踩坑最大的收获是终于理解了为什么这件事会这么绕：<strong>Linux 桌面里的“主题”和“颜色偏好”不是一回事，而 Xfce、GTK、Qt、GSettings、XDG Portal、Firefox、Chromium 又各自可能读取不同的东西。</strong></p><p>所以看到“Xfce 明明已经 Light，但 Chrome/Firefox 还是 Dark”，第一反应不应该是继续换主题，而应该开始检查：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">gsettings</span><span style="color:#98C379"> get</span><span style="color:#98C379"> org.gnome.desktop.interface</span><span style="color:#98C379"> color-scheme</span></span></code></pre></td></tr></tbody></table></figure><p>如果它还是：<code>'prefer-dark'</code>，那基本就找到方向了。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/kali-linux-xfce-light-mode</id>
    <link href="https://www.xrgzs.top/posts/kali-linux-xfce-light-mode"/>
    <published>2026-08-21T08:01:00.000Z</published>
    <summary>Kali Linux Xfce 浅色模式折腾记：为什么选了 Kali-Light，Chrome 和 Firefox 还是黑的？</summary>
    <title>Kali Linux Xfce 切换浅色模式</title>
    <updated>2026-08-21T08:01:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="AI" scheme="https://www.xrgzs.top/tags/ai/"/>
    <category term="Agent" scheme="https://www.xrgzs.top/tags/agent/"/>
    <content>
      <![CDATA[<p><img src="codex-node-repl-fix/Designed.png" alt=""></p><p>最近在使用 Codex 接入第三方模型的过程中，我遇到了一个很奇怪的问题：配置了第三方模型之后，<code>node_repl</code> 以及依赖它的 Browser、Computer Use 等功能就无法正常使用。</p><p>一开始我也以为是“第三方模型不支持 <code>node_repl</code>”，于是各种查 Issue、翻教程、改配置、折腾 Skills，最后发现事情没有这么简单。</p><p>这次排查下来，我认为这里其实叠加了两个问题：</p><ol><li>Codex 的 custom <code>model_provider</code> 本身就存在 Browser / Computer Use 与动态工具发现（<code>tool_search</code>）方面的兼容问题。官方仓库目前就有一个非常接近这个现象的 Issue：同一台机器、同一套插件，在官方后端可以正常使用 <code>mcp__node_repl__js</code>，切换到 custom provider 后却无法发现这个工具。</li><li>第三方配置工具又可能把 Codex 的 <code>config.toml</code> 当成普通的“供应商配置”进行保存和覆盖。但 Codex 的 <code>config.toml</code> 实际上不只是模型和 API 配置，其中还包含大量由 Codex 根据当前版本、本机路径和运行时环境生成的状态。CC Switch 的公开 Issue 中已经有人报告了切换 provider 会整体覆盖 <code>config.toml</code>，从而丢失 Codex 自己维护的动态状态。</li></ol><p>因此，我这篇文章主要记录的是第二类问题：第三方配置工具保存的旧配置，与新版 Codex Desktop 自动生成的运行时配置发生冲突。</p><p>如果你遇到的是“官方模型正常，第三方模型无论怎么重建配置都无法使用 Browser / Computer Use”，那么还需要考虑 custom provider 本身的限制，不能简单归结为配置文件损坏，可尝试更新版本。另外，Codex 有AB灰度测试，因此这些功能都存在随机性。</p><h2 id="背景">背景</h2><p>配置 Codex 的第三方模型，除了修改 <code>~/.codex/config.toml</code> 之外，通常还需要配置 <code>model_catalog_json</code>。</p><p>可以通过下面的命令提取当前 Codex 自带的模型目录：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">codex</span><span style="color:#98C379"> debug</span><span style="color:#98C379"> models</span><span style="color:#D19A66"> --bundled</span><span style="color:#ABB2BF"> > </span><span style="color:#98C379">~/.codex/models_bundled.json</span></span></code></pre></td></tr></tbody></table></figure><p>比如直接在命令行运行 <code>codex</code>，然后在 TUI 中输入 <code>/models</code>，这里显示的模型信息就会涉及 Codex 的模型目录；使用 Desktop 时，模型选择器同样需要正确的模型元数据。</p><p><img src="codex-node-repl-fix/20260815-123338.png" alt=""></p><p><img src="codex-node-repl-fix/20260815-123354.png" alt=""></p><p>这个文件里面包含了模型的一些详细的参数，包括上下文窗口、输出限制、reasoning 等模型能力相关信息，甚至还包含了系统的提示词。</p><p><img src="codex-node-repl-fix/20260815-123450.png" alt=""></p><p>这也是为什么配置第三方模型的时候，通常不会让普通用户从头手写这些东西。<s>除非你是神或者你使用 AI。</s></p><p>我们一般会使用第三方工具辅助配置，比如<a href="https://github.com/farion1231/cc-switch">CC Switch</a>、<a href="https://github.com/BigPizzaV3/CodexPlusPlus">Codex++</a>之类的，它会自动生成这种文件，非常的方便。还有一种情况就是模型提供商会给个脚本或者配置文件，比如说<a href="https://api-docs.deepseek.com/zh-cn/quick_start/agent_integrations/codex">DeepSeek 的 Codex Agent 集成文档</a>。</p><p>按照正常的情况，使用OpenAI账号登录的Codex应该是能正常使用的，<s>不然官方早就炸锅了</s>（其实已经炸锅了），真正开始出现问题，往往是在我们加入第三方模型、代理以及第三方配置工具之后。</p><p>你去翻这些配置工具的 GitHub Issues，可能真的会找到几个出现和你一样问题的兄弟，但这些 Issues 要么会神不知鬼不觉地关掉，要么没人解决，你根本都不知道人家是怎么修好的。即使你去 L 站找到教程，人家告诉你改这改那的，甚至让你弄一堆 skills 让它~~“玄武修复”~~。你根本不清楚是哪一项配置导致了你的ChatGPT APP（Codex Desktop）无法调用<code>node_repl</code> 。即使你这次修好，下次一更新又没法用了。</p><h2 id="核心">核心</h2><p>Codex 的 <code>config.toml</code> 并不是一份普通的供应商配置，这可能是整个问题最容易被忽略的地方。</p><p>很多第三方模型切换工具最初都是围绕 Claude Code 之类的软件设计的。<s>Claude老师，我还记得你～</s>Claude Code 的配置相对简单，很多东西本质上就是环境变量或者几层 JSON 配置。开发模型切换工具的时候，自然会形成一种思维惯性：</p><blockquote><p>保存一份公共配置 → 保存不同供应商的配置 → 切换时把两份配置合并 → 写回目标软件。</p></blockquote><p>对于简单的配置文件，这种方式其实没有什么问题。但 Codex 的 <code>config.toml</code> 不太一样。它不仅包含模型的配置信息，还可能包含：</p><ul><li>项目信任状态；</li><li>Plugins；</li><li>MCP Server；</li><li>Marketplace；</li><li>Desktop 设置；</li><li>Codex Desktop 生成的 runtime 路径；</li><li>Browser / Computer Use 使用的环境变量；</li><li>runtime 版本；</li><li>SHA256 trust 信息；</li><li>本机绝对路径。</li></ul><p>也就是说，它更像是一份“用户配置 + 应用状态 + 本机运行时状态”混合在一起的配置文件。</p><p>而这就产生了一个问题。</p><p>第三方配置工具通常需要把供应商配置保存到自己的数据库中，例如 SQLite。但问题在于，它保存的其实是<strong>某个时间点的 Codex 配置快照</strong>。而 Codex Desktop 自己还会继续修改这份 <code>config.toml</code>。于是，Codex 更新了，运行时配置变了，第三方工具数据库里的旧快照却没有同步更新。下一次切换供应商时，第三方工具再把自己的配置写回 <code>config.toml</code>，新旧配置就可能发生冲突。</p><p><img src="codex-node-repl-fix/20260815-124250.png" alt="CC Switch 的 SQLite DB"></p><p>由于各种原因无法跟上最新的 TOML 版本，最终导致配置文件与最新版本的 ChatGPT APP（Codex Desktop）对不上，然后就出现了一些奇奇怪怪的问题，在我的实际环境里，出现过包括但不限于：</p><ol><li><code>node_repl</code> 无法正常使用；</li><li>Browser / In-App Browser 无法使用；</li><li>Computer Use 无法使用；</li><li><code>apply_patch</code> 等工具不可用；</li><li>模型退回使用 PowerShell 写文件，在 Windows PowerShell 5.x 环境下进一步出现中文编码问题。</li></ol><p>为什么会造成这一系列问题呢？主要还是在第1点 <code>node_repl</code> 上。这里要特别说明一下：并不是说“只要配置文件损坏，就一定会同时出现上述所有问题”，而更像是不同层面的连锁反应，其中最关键的还是 <code>node_repl</code>。</p><p><code>node_repl</code> 到底是什么？</p><p>实际上，Codex 的这些工具，是使用“内置”的 NodeJS 作为运行环境的，通过 MCP 的方式给模型提供 <code>node_repl</code> 能力。这种 Work Agent 是不应该要求用户手动配置编程环境的，类似的产品如 WorkBuddy 也是一样的，都会自带环境。</p><p>Codex 的 Computer Use 则是通过 Skills 的方式实现的，但是他那个程序有访问限制只能使用内置的 node_repl 环境才能调用 CUA。</p><p><img src="codex-node-repl-fix/20260815-124423.png" alt="CUA NodeJS目录"></p><p><img src="codex-node-repl-fix/20260815-183924.png" alt="Codex 依赖项"></p><p><img src="codex-node-repl-fix/20260815-184155.png" alt="node_repl MCP"></p><p><img src="codex-node-repl-fix/20260815-185129.png" alt="node_repl 能力"></p><p>下面是我机器上一次正常运行时的配置片段，现在就清楚多了：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">model_provider</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "custom"</span></span><span class="line"><span style="color:#61AFEF">model</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "deepseek-v4-flash-free"</span></span><span class="line"><span style="color:#61AFEF">model_reasoning_effort</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "high"</span></span><span class="line"><span style="color:#61AFEF">disable_response_storage</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"><span style="color:#61AFEF">notify</span><span style="color:#98C379"> =</span><span style="color:#ABB2BF"> [ </span><span style="color:#98C379">"C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node_modules\@oai\sky\bin\windows\codex-computer-use.exe",</span><span style="color:#98C379"> "turn-ended"</span><span style="color:#98C379"> ]</span></span><span class="line"><span style="color:#61AFEF">approval_policy</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "on-request"</span></span><span class="line"><span style="color:#61AFEF">sandbox_mode</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "workspace-write"</span></span><span class="line"><span style="color:#61AFEF">web_search</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "live"</span></span><span class="line"><span style="color:#61AFEF">model_catalog_json</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "cc-switch-model-catalog.json"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[model_providers.custom]</span></span><span class="line"><span style="color:#61AFEF">name</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "custom"</span></span><span class="line"><span style="color:#61AFEF">wire_api</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "responses"</span></span><span class="line"><span style="color:#61AFEF">requires_openai_auth</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"><span style="color:#61AFEF">base_url</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "http://127.0.0.1:15721/v1"</span></span><span class="line"><span style="color:#61AFEF">experimental_bearer_token</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "PROXY_MANAGED"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[marketplaces.openai-bundled]</span></span><span class="line"><span style="color:#61AFEF">last_updated</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "2026-08-15T02:18:26Z"</span></span><span class="line"><span style="color:#61AFEF">source_type</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "local"</span></span><span class="line"><span style="color:#56B6C2">source</span><span style="color:#98C379"> =</span><span style="color:#98C379"> '\?\C:\Users\x\.codex\.tmp\bundled-marketplaces\openai-bundled'</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\test'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\documents\codex\2026-08-15\ni-h'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\documents\codex\2026-08-15\node-repl'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\documents\codex\2026-08-15\node-repl-2'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\documents\codex\2026-08-15\node-repl-3'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[projects.</span><span style="color:#98C379">'c:\users\x\documents\codex\2026-08-15\browser-comments-user-comment-1-file'</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">trust_level</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "trusted"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[plugins.</span><span style="color:#98C379">"browser@openai-bundled"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">enabled</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[plugins.</span><span style="color:#98C379">"visualize@openai-bundled"</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#61AFEF">enabled</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[features]</span></span><span class="line"><span style="color:#61AFEF">js_repl</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> false</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[shell_environment_policy.set]</span></span><span class="line"><span style="color:#61AFEF">BROWSER_USE_AVAILABLE_BACKENDS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "chrome,iab"</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "0b6d31ced7f1e01dcc1e3aba9416cb1ed01f17e76030ef4a4d1d3e19fd8e0bc6"</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_TRUSTED_CODE_PATHS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\.codex;C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node_modules'</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[desktop]</span></span><span class="line"><span style="color:#61AFEF">conversationDetailMode</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "STEPS_COMMANDS"</span></span><span class="line"><span style="color:#61AFEF">sansFontSize</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 14</span></span><span class="line"><span style="color:#61AFEF">codeFontSize</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 13</span></span><span class="line"><span style="color:#61AFEF">ambient-suggestions-enabled</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> false</span></span><span class="line"><span style="color:#61AFEF">show-context-window-usage</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"><span style="color:#61AFEF">followUpQueueMode</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "queue"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[windows]</span></span><span class="line"><span style="color:#61AFEF">sandbox</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "elevated"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[sandbox_workspace_write]</span></span><span class="line"><span style="color:#61AFEF">network_access</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> true</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[mcp_servers]</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[mcp_servers.node_repl]</span></span><span class="line"><span style="color:#61AFEF">args</span><span style="color:#98C379"> =</span><span style="color:#ABB2BF"> []</span></span><span class="line"><span style="color:#56B6C2">command</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node_repl.exe'</span></span><span class="line"><span style="color:#61AFEF">startup_timeout_sec</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 120</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">[mcp_servers.node_repl.env]</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_NATIVE_PIPE_CONNECT_TIMEOUT_MS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "1000"</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_NODE_MODULE_DIRS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node_modules'</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_NODE_PATH</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node.exe'</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_TRUSTED_CODE_PATHS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\.codex;C:\Users\x\AppData\Local\OpenAI\Codex\runtimes\cua_node\9ec47c3bbf131bc8\bin\node_modules'</span></span><span class="line"><span style="color:#61AFEF">CODEX_HOME</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\.codex'</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "0b6d31ced7f1e01dcc1e3aba9416cb1ed01f17e76030ef4a4d1d3e19fd8e0bc6"</span></span><span class="line"><span style="color:#61AFEF">BROWSER_USE_AVAILABLE_BACKENDS</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "chrome,iab"</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_INSTRUCTIONS_USE_CASE_BROWSER</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "Control the in-app browser in conjunction with the Browser Plugin."</span></span><span class="line"><span style="color:#61AFEF">NODE_REPL_INSTRUCTIONS_USE_CASE_CHROME</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "Control the Chrome browser in conjunction with the Chrome Plugin. Prefer this method of controlling Chrome over alternatives (such as Computer Use) unless the user explicitly mentions an alternative."</span></span><span class="line"><span style="color:#61AFEF">BROWSER_USE_CODEX_APP_BUILD_FLAVOR</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "prod"</span></span><span class="line"><span style="color:#61AFEF">BROWSER_USE_CODEX_APP_VERSION</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "26.810.50856"</span></span><span class="line"><span style="color:#61AFEF">SKY_CUA_NATIVE_PIPE</span><span style="color:#98C379"> =</span><span style="color:#98C379"> "1"</span></span><span class="line"><span style="color:#61AFEF">SKY_CUA_NATIVE_PIPE_DIRECTORY</span><span style="color:#98C379"> =</span><span style="color:#98C379"> '\.\pipe\codex-computer-use-bbbbbbbb-cccc-5555-aaaa-777777777777'</span></span><span class="line"><span style="color:#61AFEF">CODEX_CLI_PATH</span><span style="color:#98C379"> =</span><span style="color:#98C379"> 'C:\Users\x\AppData\Local\OpenAI\Codex\bin\697a646d3cf0f240\codex.exe'</span></span></code></pre></td></tr></tbody></table></figure><p>里面有一堆的随机字符串、版本号，还有 SHA256 这种哈希校验，而且路径还是绝对路径。如果一个包含这么多运行时路径和 hash 的配置文件跨版本完全不变，<s>我可以当场把这个电脑屏幕吃掉</s>。所以你把我这份配置文件复制过去，也是没法使用的。</p><p>这也是为什么我认为第三方工具最容易踩坑的地方就在这里：它们需要管理供应商配置，但 Codex 的 <code>config.toml</code> 同时又承载了很多不应该被当成供应商配置保存的动态状态。</p><p>于是后面的事情就很典了：Codex Desktop 更新了，新的 runtime 也更新了，新的 <code>node_repl</code>、Browser、Computer Use 配置也生成了，然后第三方工具拿着自己数据库里上一版本的配置，再次覆盖 <code>config.toml</code>。</p><p>最后你就会看到：<s><code>node_repl</code> 死了喵</s>。</p><h2 id="解决">解决</h2><p>如果你已经确定自己遇到的是<strong>配置被第三方工具覆盖/污染</strong>这一类问题，要解决这个问题，核心就在于我们要让模型配置工具数据库里的配置文件与最新版本的 Codex 配置相统一。</p><p>这里给出一个解决这个问题的最彻底方案——让 Codex 重新生成一份当前版本的配置。</p><blockquote><p>提醒一下，我这个方法比较兜底，<strong>会导致你的 Codex 里面的历史记录全部清空。出现数据丢失本人一概不负责。</strong></p></blockquote><p>如果你对 Codex 的文件结构比较熟悉，也可以不全删，而是只处理已经确认有问题的配置项。</p><h3 id="1-确保组件已经升级">1. 确保组件已经升级</h3><p>在所有步骤之前，<strong>请务必确保你使用的 Codex、ChatGPT、CC Switch、Codex++ 为最新版本</strong>，必须检查是否是最新，尤其是 Codex Desktop，这个天天更新。这些属于已知严重影响体验的问题，正常是会进行修复适配的，只不过人家不一定能帮你重建配置罢。但如果你不升级到最新版本，那瞎折腾没有任何用处，本篇教程作废。</p><h3 id="2-彻底退出相关程序">2. 彻底退出相关程序</h3><p>把 Codex Desktop、ChatGPT、CC Switch、Codex++ 等相关程序全部退出。不仅要关窗口，还要注意任务栏托盘。然后打开任务管理器，检查是否还有：codex、ChatGPT、CC Switch、codex++ 之类的进程，有的话全部结束掉。</p><h3 id="3-重置Codex">3. 重置Codex</h3><p>首先建议把<code>%USERPROFILE%\.codex</code> 先完整备份一份，然后再考虑删除。在用户文件夹的 AppData 底下还有 Codex 文件夹，我们也最好将其删除：</p><ul><li><code>%USERPROFILE%.codex</code> 或 <code>~/.codex</code></li><li><code>%APPDATA%\Codex</code></li><li><code>%LOCALAPPDATA%\OpenAI</code></li><li><code>%LOCALAPPDATA%\Codex</code></li></ul><p><strong>这么删除，100% 会丢失历史记录。</strong></p><blockquote><p>为什么要全删呢？个人遇到仅删除里面的 <code>config.toml</code> 文件，并不会导致配置完整重建，甚至还会出现无法UAC提权的问题。如果你对 Codex 比我熟悉的话，那你可以选择性地删除一部分。</p></blockquote><p>下次重新执行ChatGPT APP（Codex Desktop）的时候，100% 会重新创建这个目录，<s>配置也会随着重建而重建</s>。</p><h3 id="4-先让-Codex-在干净环境下启动一次">4. 先让 Codex 在干净环境下启动一次</h3><p>这些东西清理干净以后，使用官号是不会出现问题的。我们要的就是这一个效果，就是要把这个状态给它持久化。</p><p>先别开蹬。直接启动 ChatGPT App（Codex Desktop）。如果没有官号，在首次运行的过程中：</p><ol><li>选择“其他登录方式”；</li><li>在 API key 里面随便乱输点东西，回车；</li><li>完成那个个性化设置，直接进入主界面；</li><li><strong>趁着这次机会设置你的偏好</strong>。后面就不要再配置了，这些配置是存在 <code>config.toml</code> 里面的，一切换提供商他又没得了，<s>因为你大概率记不起来点更新通用配置</s>；</li><li>将 ChatGPT APP（Codex Desktop） 彻底退出。</li></ol><blockquote><p>你可能需要使用魔法才能下载中文语言包。</p></blockquote><h3 id="5-重新配置第三方模型">5. 重新配置第三方模型</h3><p>现在才轮到 CC Switch / Codex++。这里最重要的一点是：<strong>不要直接拿旧的第三方工具数据库覆盖刚刚生成的新配置</strong>。所以，我们需要同时清空这些第三方模型配置工具的数据，例如 CC Switch 的用户数据目录包括：</p><ul><li><code>%USERPROFILE%.cc-switch</code> 或 <code>~\.cc-switch</code></li><li><code>%LOCALAPPDATA%\com.ccswitch.desktop</code></li><li><code>%APPDATA%\com.ccswitch.desktop</code></li></ul><blockquote><p>如果第三方工具已经长期使用，而且里面保存了大量旧的 Codex 配置，那么建议先备份它们的数据。</p></blockquote><blockquote><p>当然，你要是有办法使用 <code>sqlite</code> 或者是一些工具更改它的数据库的话，那也是可以的。但我这边为了避免麻烦，直接全部清空。</p></blockquote><blockquote><p>CC Switch 的公开 Issue 已经有案例表明，其内部数据库中的 Codex 配置和 MCP 状态可能影响最终写入的 <code>config.toml</code>。所以，如果你只是通过客户端 UI 删除某个供应商，并不一定等价于“数据库里关于这个供应商的所有旧状态都消失了。”这也是我这次选择重新初始化的原因。</p></blockquote><h3 id="6-让第三方工具重新读取当前配置">6. 让第三方工具重新读取当前配置</h3><p>现在打开 CC Switch，重新配置你的第三方模型。你新建供应商了以后，会发现它底下的那些配置已经自动覆盖为通用配置了。因为这个时候CC Switch是干净的，它读取 Codex 的配置文件也是正确的，**正常情况下，CC Switch 的这个默认配置是能用的。**也不用管那个 <code>node_repl</code> 的 MCP，它默认就是启用的。</p><h2 id="测试">测试</h2><p>最简单的测试方法，就是让模型真正做一次需要 <code>node_repl</code> 的操作。这里我是打开它侧边栏的浏览器，让 AI 自动执行操作，测试是没有问题的。</p><p><img src="codex-node-repl-fix/f8fe04a0f829c5f79449537cc1612929.png" alt=""></p><p>这也就解释清楚了，为什么维护者没有办法复现用户那些奇奇怪怪的情况，因为人家可能用的是干净环境或者是虚拟机，没有那些乱七八糟的通用配置。</p><p>我建议你在知道需要调用这个 <code>node_repl</code>  的情况下，执行一次这种测试——问问 AI 这个 <code>node_repl</code>  到底正不正常。不然的话，到时候只是浪费模型的 Token，只能看着 AI 在各种挣扎，但无能为力。</p><h2 id="周期">周期</h2><p>修好以后能用多久呢？我不知道。但如果这些工具一直不适配，仍然采用“保存一份 Codex 配置快照 → 切换 provider 时重新写回”的方式，就存在再次发生配置覆盖的可能，那你每升级一次都得执行这一轮操作。我们只能尽量避免新旧配置的冲突。</p><p>为了避免配置文件被覆盖，我这里提供了个方法：</p><ol><li>Codex Desktop 更新以后，先不要启动 CC Switch。因为开启 CC Switch 后，如果你使用它的路由，把第三方模型的 chat 转为 responses，会覆盖你的配置文件，我估计问题就出在这里。</li><li>第一次启动把它当成“迁移启动”。升级完第一次启动 ChatGPT APP（Codex Desktop）就别想着直接开蹬——先让 Codex Desktop 自己完整启动一次，让它完成自己的初始化和迁移。</li><li>备份新的 <code>config.toml</code>。迁移完成以后，彻底退出  ChatGPT APP（Codex Desktop），赶紧去 <code>.codex</code> 目录底下备份好你的<code>config.toml</code>配置文件，避免到时候又要重置。</li><li>更新第三方工具以后，对比配置再切换。打开 CC Switch / Codex++，对比一下你的配置文件与供应商那里的配置文件有什么区别。如果有区别，就改为新的配置并手动保存一下，然后给它提取到全局配置。</li></ol><p>这样操作的话，配置文件可能就不会被改乱了。不然的话，又只能重置了。</p><h2 id="参考">参考</h2><ul><li><a href="https://github.com/openai/codex/issues/31750">Codex 官方仓库：Browser / Computer Use 与 custom provider 的 tool_search 问题 #31750</a></li><li><a href="https://github.com/farion1231/cc-switch/issues/5770">CC Switch：切换 Codex provider 整体覆盖 config.toml #5770</a></li><li><a href="https://github.com/farion1231/cc-switch/issues/4779">CC Switch：保存 Codex 配置时重新注入 node_repl #4779</a></li><li><a href="https://github.com/openai/codex/blob/main/codex-rs/core/config.schema.json">Codex 官方 config schema</a></li><li><a href="https://github.com/farion1231/cc-switch/blob/main/docs/user-manual/en/2-providers/2.2-switch.md">CC Switch Codex 配置说明</a></li><li><a href="https://zhuanlan.zhihu.com/p/2045448636801217580">知乎相关文章</a></li><li><a href="https://linux.do/t/topic/2272299">Linux.do 相关讨论 1</a></li><li><a href="https://github.com/chen0416ccc-cpu/codex-windows-fast-patch-skill">Windows Codex Fast Patch Skill</a></li><li><a href="https://linux.do/t/topic/2275776">Linux.do 相关讨论 2</a></li><li><a href="https://linux.do/t/topic/2591415">Linux.do 相关讨论 3</a></li><li><a href="https://github.com/farion1231/cc-switch/issues?q=is%3Aissue+node_repl">CC Switch <code>node_repl</code> Issues</a></li><li><a href="https://github.com/openai/codex/issues?q=is%3Aissue+node_repl">Codex <code>node_repl</code> Issues</a></li></ul>]]>
    </content>
    <id>https://www.xrgzs.top/posts/codex-node-repl-fix</id>
    <link href="https://www.xrgzs.top/posts/codex-node-repl-fix"/>
    <published>2026-08-15T10:52:00.000Z</published>
    <summary>其实不关codex的事，第三方工具背锅。</summary>
    <title>ChatGPT APP (Codex Desktop) node_repl 修复</title>
    <updated>2026-08-15T12:31:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="AMD" scheme="https://www.xrgzs.top/tags/amd/"/>
    <category term="驱动" scheme="https://www.xrgzs.top/tags/%E9%A9%B1%E5%8A%A8/"/>
    <category term="Misc" scheme="https://www.xrgzs.top/tags/misc/"/>
    <content>
      <![CDATA[<p><a href="https://www.xrgzs.top/posts/intel-chipset-extract">上一篇教程</a>已经介绍了 Intel 的芯片组驱动解包方法，这篇将介绍一下 AMD 的。AMD 的相对与 Intel 可能要更复杂。</p><p><img src="amd-chipset-extract/20260813-102245.png" alt="7-Zip直接打开exe"></p><h2 id="真正的驱动文件">真正的驱动文件</h2><p>可以看到，使用 7-Zip 直接打开驱动 exe 安装文件，能出现清晰的文件夹结构，里面有很多文件，那到底哪个才包含驱动呢？</p><p>AMD 这个驱动安装程序很炫，塞了一大堆动画，是用QT弄的，然后又依赖 MSVC，所以还加了一堆 VC 运行库安装程序。实际上，驱动根本不在那几个目录里面，<code>AMD_Chipset_Drivers.exe</code> 才是我们的下一个目标。</p><p>但这个文件也出现了 7-Zip 无法正确读取识别的问题，显示的是文件的PE结构。</p><p><img src="amd-chipset-extract/20260813-102822.png" alt="7-Zip打开显示PE结构"></p><p>并且按照上一篇文章的方法，使用 7-Zip 右键菜单，更换打开方式，直接无法打开。</p><p><img src="amd-chipset-extract/20260813-102946.png" alt="7-Zip打开失败"></p><h2 id="DIE识别文件">DIE识别文件</h2><p>又回到DIE，上一篇文章DIE并没有帮上忙，这里给 <code>AMD_Chipset_Drivers.exe</code> 拖进去看看。</p><p><img src="amd-chipset-extract/20260813-103050.png" alt="DIE打开驱动安装程序"></p><p>发现这个驱动是使用 InstallShield 打包的。</p><h2 id="ISx解包">ISx解包</h2><p>那就好办了，这里根据经验，InstallShield 可以使用 <a href="https://github.com/lifenjoiner/ISx">ISx</a> 解开。</p><p>这里我直接使用 Scoop 安装：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install isx</span></span></code></pre></td></tr></tbody></table></figure><p>然后执行命令提取：</p><figure class="highlight powershell"><table><tbody><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><span class="line">15</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">*</span><span style="color:#ABB2BF"> > isx .\</span><span style="color:#56B6C2">AMD_Chipset_Drivers.exe</span></span><span class="line"><span style="color:#ABB2BF">.\</span><span style="color:#56B6C2">AMD_Chipset_Drivers.exe</span></span><span class="line"><span style="color:#D19A66">0x0010EC00</span></span><span class="line"><span style="color:#ABB2BF">Dir: </span><span style="color:#98C379">".\AMD_Chipset_Drivers_u\"</span></span><span class="line"><span style="color:#D19A66">0x00000000</span><span style="color:#D19A66">    1108992</span><span style="color:#98C379"> "AMD_Chipset_Drivers_sfx.exe"</span><span style="color:#ABB2BF"> ...   Y</span></span><span class="line"><span style="color:#ABB2BF">Files total: </span><span style="color:#D19A66">3</span></span><span class="line"><span style="color:#ABB2BF">Extracting:</span></span><span class="line"><span style="color:#D19A66">0x0010EC72</span><span style="color:#D19A66">       4690</span><span style="color:#98C379"> "0x0409.ini"</span></span><span class="line"><span style="color:#ABB2BF">                      [</span><span style="color:#C678DD">b</span><span style="color:#ABB2BF">] </span><span style="color:#56B6C2">*</span><span style="color:#ABB2BF"> Y </span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">> [</span><span style="color:#C678DD">inflate</span><span style="color:#ABB2BF">] Y</span></span><span class="line"><span style="color:#D19A66">0x0010FF22</span><span style="color:#D19A66">   21063831</span><span style="color:#98C379"> "AMD_Chipset_Drivers.msi"</span></span><span class="line"><span style="color:#ABB2BF">                      [</span><span style="color:#C678DD">b</span><span style="color:#ABB2BF">] </span><span style="color:#56B6C2">*</span><span style="color:#ABB2BF"> Y </span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">> [</span><span style="color:#C678DD">inflate</span><span style="color:#ABB2BF">] Y</span></span><span class="line"><span style="color:#D19A66">0x015267FB</span><span style="color:#D19A66">       1517</span><span style="color:#98C379"> "Setup.ini"</span></span><span class="line"><span style="color:#ABB2BF">                      [</span><span style="color:#C678DD">b</span><span style="color:#ABB2BF">] </span><span style="color:#56B6C2">*</span><span style="color:#ABB2BF"> Y </span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">> [</span><span style="color:#C678DD">inflate</span><span style="color:#ABB2BF">] Y</span></span><span class="line"><span style="color:#ABB2BF">Extra </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF">:</span></span><span class="line"><span style="color:#D19A66">0x01526DE8</span><span style="color:#D19A66">      11528</span><span style="color:#98C379"> "AMD_Chipset_Drivers_ext.bin"</span><span style="color:#ABB2BF"> ...   Y</span></span></code></pre></td></tr></tbody></table></figure><p>得到以下结构：</p><p><img src="amd-chipset-extract/20260813-103538.png" alt="ISx提取结果"></p><h2 id="lessmsi解包">lessmsi解包</h2><p>这里我们靠猜都可以猜到，驱动肯定在哪个最大的msi里面。根据经验，msi可以使用 <a href="https://github.com/activescott/lessmsi">lessmsi</a> 解开。</p><p>当然，如果不信邪，用 7-Zip 打开那个 msi，可以看到一个 <code>Data1.cab</code> 的资源。</p><p><img src="amd-chipset-extract/20260813-103915.png" alt="7-Zip打开msi"></p><p>然后你双击打开那个cab，发现缺失文件结构。400多个文件裸奔在你眼前。</p><p><img src="amd-chipset-extract/20260813-104009.png" alt="7-Zip打开cab"></p><p>术业有专攻。所以还得是用专业的工具。</p><p><code>lessmsi</code>也是通过Scoop安装：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install lessmsi</span></span></code></pre></td></tr></tbody></table></figure><p>这个Less MSIérables带图形界面，如果用不来命令行可以用GUI。</p><p><img src="amd-chipset-extract/20260813-104249.png" alt="Less MSIérables GUI"></p><p>这里我们还是使用命令行：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">lessmsi.exe</span><span style="color:#ABB2BF"> x AMD_Chipset_Drivers.msi</span></span></code></pre></td></tr></tbody></table></figure><p>解压完成后，我们就可以看到驱动文件了。</p><p><img src="amd-chipset-extract/20260813-104444.png" alt="最终驱动文件目录"></p><p>里面有一堆msi，可以删除。</p><p><img src="amd-chipset-extract/20260813-104518.png" alt="最终可删除msi"></p><h2 id="样本">样本</h2><p>直接去AMD官网下载即可。</p><p><a href="https://www.amd.com/zh-cn/support/downloads/drivers.html/chipsets/am5/b650.html">https://www.amd.com/zh-cn/support/downloads/drivers.html/chipsets/am5/b650.html</a></p><p><img src="amd-chipset-extract/20260813-104751.png" alt="AMD官网下载驱动"></p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/amd-chipset-extract</id>
    <link href="https://www.xrgzs.top/posts/amd-chipset-extract"/>
    <published>2026-08-13T02:49:00.000Z</published>
    <summary>AMD Chipset驱动exe解包教程</summary>
    <title>AMD芯片组驱动解包</title>
    <updated>2026-08-13T02:49:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="驱动" scheme="https://www.xrgzs.top/tags/%E9%A9%B1%E5%8A%A8/"/>
    <category term="Misc" scheme="https://www.xrgzs.top/tags/misc/"/>
    <category term="Intel" scheme="https://www.xrgzs.top/tags/intel/"/>
    <category term="7-Zip" scheme="https://www.xrgzs.top/tags/7-zip/"/>
    <content>
      <![CDATA[<p>Intel的芯片组驱动无法直接使用7-Zip解压。为了提取里面的驱动，我们需要使用其他的方法。本文将为大家介绍。其实也算一个Misc的Write-up。</p><p><img src="intel-chipset-extract/20260813-091734.png" alt="7-Zip直接右键打开exe"></p><h2 id="思路">思路</h2><p>首先我们需要弄清楚这个exe是怎么打包的。</p><p>这里我们先尝试使用 <a href="https://github.com/horsicq/Detect-It-Easy">DIE (Detect It Easy)</a> ，启动DIE，将 <code>CHIPSET_Intel_v10.1.20658.8883.exe</code> 拖进去。</p><p>然而并没有扫描出有效信息，说明是Intel自己写的打包器。</p><p><img src="intel-chipset-extract/20260813-092132.png" alt="DIE扫描结果"></p><p>但这并不代表没有办法提取，因为这个驱动最终是会被释放出来的，<strong>只要他能释放出来就有办法解包</strong>。</p><h2 id="7z大法好">7z大法好</h2><p>7-Zip正常情况是不直接支持exe的，但是为什么能打开查看呢？</p><p>相当于7-Zip会自动尝试各种方法去匹配、解析文件的结构。那我们换个方式是不是就可以了呢？</p><p>这边可以使用7-Zip的右键菜单选择打开方式。</p><p><img src="intel-chipset-extract/20260813-092659.png" alt="7-Zip右键菜单"></p><blockquote><p>其实这个参数就是命令行的 <code>-t</code> ，可以按 F1 进入 CHM 找到帮助。</p><p><img src="intel-chipset-extract/20260813-092908.png" alt="7-Zip帮助"></p></blockquote><p>这里我们尝试使用这些方式打开，最终发现 <code>#:e</code> 方式打开，有两处偏移都可以找到驱动的压缩包，压缩方式是zip，大小都一样：</p><p><img src="intel-chipset-extract/20260813-093406.png" alt="7-Zip打开exe结构"></p><p>最终我们可以直接将驱动解压出来。</p><p><img src="intel-chipset-extract/20260813-093439.png" alt="驱动压缩包内容"></p><h2 id="binwalk一把梭">binwalk一把梭</h2><p>使用Rust🦀包管理器安装（需要提前配置Rust环境）：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">cargo</span><span style="color:#98C379"> install</span><span style="color:#98C379"> binwalk</span></span></code></pre></td></tr></tbody></table></figure><p>然后使用<code>-e</code>参数提取：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">binwalk</span><span style="color:#D19A66"> -e</span><span style="color:#98C379"> .</span><span style="color:#56B6C2">\C</span><span style="color:#98C379">HIPSET_Intel_vxxxx.exe</span></span></code></pre></td></tr></tbody></table></figure><p>执行结果：</p><figure class="highlight powershell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">> binwalk </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">e .\</span><span style="color:#56B6C2">CHIPSET_Intel_v10.1.20658.8883.exe</span></span><span class="line"><span style="color:#ABB2BF">...</span></span><span class="line"><span style="color:#56B6C2">------------------------------------------------------------------------------------------------------------------------</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">] Extraction of cab </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0x332790</span><span style="color:#ABB2BF"> failed!</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of jpeg </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0x5003F0</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of jpeg </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0x64FB70</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of zip </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0x7A44F0</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of jpeg </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0xB48FF0</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of jpeg </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0xC98770</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of zip </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0xDED0F0</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#56B6C2">+</span><span style="color:#ABB2BF">] Extraction of 7zip </span><span style="color:#C678DD">data</span><span style="color:#ABB2BF"> at offset </span><span style="color:#D19A66">0x1237D18</span><span style="color:#ABB2BF"> completed successfully</span></span><span class="line"><span style="color:#56B6C2">------------------------------------------------------------------------------------------------------------------------</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">Analyzed </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF"> file </span><span style="color:#C678DD">for</span><span style="color:#D19A66"> 85</span><span style="color:#ABB2BF"> file signatures (</span><span style="color:#D19A66">187</span><span style="color:#ABB2BF"> magic patterns) </span><span style="color:#C678DD">in</span><span style="color:#D19A66"> 5.4</span><span style="color:#ABB2BF"> seconds</span></span></code></pre></td></tr></tbody></table></figure><p>然后进入 <code>extractions\CHIPSET_Intel_v10.1.20658.8883.exe.extracted</code> 文件夹，应该就能找到</p><p><img src="intel-chipset-extract/20260813-094427.png" alt="解压结果"></p><p>那两个文件夹使用文件比较工具对比，一模一样。</p><p><img src="intel-chipset-extract/20260813-094542.png" alt="文件对比"></p><p>有懒人的方法，为啥先讲7-Zip呢？因为实际情况下，我们可能连网都有求不起，刚好机器上带着个7-Zip，那就能提取。所以在这个场景下，7-Zip适用于大部分情况。如果使用7-Zip出现困难，则可以转到binwalk。当然，也可以上AI辅助。</p><h2 id="样本">样本</h2><p>之前不好找，现在Intel官网就能下载到芯片组驱动。</p><p><a href="https://www.intel.cn/content/www/cn/zh/download/19347/chipset-inf-utility.html">https://www.intel.cn/content/www/cn/zh/download/19347/chipset-inf-utility.html</a></p><p><img src="intel-chipset-extract/20260813-094711.png" alt="Intel官网驱动"></p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/intel-chipset-extract</id>
    <link href="https://www.xrgzs.top/posts/intel-chipset-extract"/>
    <published>2026-08-13T01:15:00.000Z</published>
    <summary>Intel芯片组驱动解包方法</summary>
    <title>Intel芯片组驱动解包</title>
    <updated>2026-08-13T01:58:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Blog" scheme="https://www.xrgzs.top/tags/blog/"/>
    <category term="CMS" scheme="https://www.xrgzs.top/tags/cms/"/>
    <category term="Hexo" scheme="https://www.xrgzs.top/tags/hexo/"/>
    <content>
      <![CDATA[<p><img src="sveltia-cms/cover.webp" alt=""></p><p>其实狗子是有想写博客的，但是由于在电脑上打字比较不方便——一般想写点什么的时候，电脑又不在身边，所以就很少写文章。</p><p>最近发现可以使用 git-based CMS，实现自动推送文章到 GitHub 仓库，经过一定的技术选型，最终选用了 Sveltia CMS。</p><h2 id="配置">配置</h2><p>因为不同博客系统的配置不一样，像我使用的 Hexo，Sveltia CMS 官网没有文档。不过好在现在 AI 很发达，其实让 AI 自己弄就好了。</p><p>首先随便找个能联网的 Chat AI 大致了解一下这个项目，然后让它生成一下提示词，粘贴给 Coding Agent 生成一下配置就搞好了。</p><figure class="highlight markdown"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">你是一名资深 Hexo、Git-based CMS、Sveltia CMS 和 Cloudflare Pages 工程师。</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">我有一个现有 Hexo 博客，需要迁移接入 Sveltia CMS，请帮我完成方案设计和实际修改指导。</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">当前架构：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 静态博客框架：Hexo</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 仓库：GitHub 私有仓库</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 部署：Cloudflare Pages 自动构建</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 构建方式：git push 后 Cloudflare Pages 执行 hexo generate</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 不希望改变现有发布流程</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">当前目录结构：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/</span></span><span class="line"><span style="color:#ABB2BF">├── _posts/</span></span><span class="line"><span style="color:#ABB2BF">│   ├── test.md</span></span><span class="line"><span style="color:#ABB2BF">│   └── test/</span></span><span class="line"><span style="color:#ABB2BF">│       └── image.png</span></span><span class="line"><span style="color:#ABB2BF">│</span></span><span class="line"><span style="color:#ABB2BF">├── admin/</span></span><span class="line"><span style="color:#ABB2BF">│   （目前不存在，需要创建）</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">Hexo 配置：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">已开启：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">post_asset_folder: true</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">文章资源采用 Hexo Post Asset Folder 模式。</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">当前文章示例：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">文件：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/_posts/test.md</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">资源：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/_posts/test/image.png</span></span><span class="line"></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">Markdown 图片引用目前可能是：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">![</span><span style="color:#61AFEF">图片</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">(</span><span style="color:#C678DD;text-decoration:underline">./test/image.png</span><span style="color:#E06C75">)</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">或者：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">![</span><span style="color:#61AFEF">图片</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">(</span><span style="color:#C678DD;text-decoration:underline">test/image.png</span><span style="color:#E06C75">)</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">请考虑迁移后是否应该调整为 Hexo 推荐形式：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">![</span><span style="color:#61AFEF">图片</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">(</span><span style="color:#C678DD;text-decoration:underline">image.png</span><span style="color:#E06C75">)</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">或者：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">&#123;% asset_img image.png %&#125;</span></span><span class="line"></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">目标：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">我要在 Hexo 博客中加入 Sveltia CMS，实现：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">1.</span><span style="color:#ABB2BF"> 浏览器访问：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">https://我的域名/admin/</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">可以进入 Sveltia CMS 后台。</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">2.</span><span style="color:#ABB2BF"> 使用 GitHub backend：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 操作私有 GitHub 仓库</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> CMS 修改内容后自动 commit 到 main 分支</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 保持 Cloudflare Pages 自动部署流程不变</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">3.</span><span style="color:#ABB2BF"> CMS 管理：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 创建文章</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 编辑文章</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 修改 front matter</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 上传图片</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">4.</span><span style="color:#ABB2BF"> 最重要：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">保持 Hexo Post Asset Folder 结构：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">创建文章：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">test.md</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">上传图片：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">image.png</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">最终 Git 仓库希望得到：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/_posts/</span></span><span class="line"><span style="color:#ABB2BF">├── test.md</span></span><span class="line"><span style="color:#ABB2BF">└── test/</span></span><span class="line"><span style="color:#ABB2BF">    └── image.png</span></span><span class="line"></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">请重点研究并使用 Sveltia CMS 官方文档中的 internal media 功能：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">https://sveltiacms.app/en/docs/media/internal</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">确认是否可以使用：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> media_folder</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> public_folder</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> &#123;&#123;filename&#125;&#125;</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> entry-relative media folder</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">实现：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">当前文章 slug 自动对应图片目录：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/_posts/&#123;filename&#125;/</span></span><span class="line"></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">不要简单建议改成：</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">source/images/</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">因为我需要保留 Hexo 原生文章资源目录。</span></span><span class="line"></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">请输出：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">1.</span><span style="color:#ABB2BF"> 推荐迁移方案</span></span><span class="line"><span style="color:#E5C07B">2.</span><span style="color:#ABB2BF"> 完整 source/admin/index.html</span></span><span class="line"><span style="color:#E5C07B">3.</span><span style="color:#ABB2BF"> 完整 source/admin/config.yml</span></span><span class="line"><span style="color:#E5C07B">4.</span><span style="color:#ABB2BF"> GitHub OAuth 配置步骤</span></span><span class="line"><span style="color:#E5C07B">5.</span><span style="color:#ABB2BF"> Cloudflare Pages 是否需要修改</span></span><span class="line"><span style="color:#E5C07B">6.</span><span style="color:#ABB2BF"> Hexo 图片引用是否需要调整</span></span><span class="line"><span style="color:#E5C07B">7.</span><span style="color:#ABB2BF"> 如果 Sveltia 原生无法完全支持，请给出最小改动方案（例如脚本、hook、GitHub Action），不要改变整体架构</span></span><span class="line"><span style="color:#E5C07B">8.</span><span style="color:#ABB2BF"> 给出迁移后的完整目录结构示例</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">注意：</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 不要重新设计我的博客架构</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 不要引入数据库 CMS</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 不要使用 WordPress、Strapi 等方案</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 优先保持现有 Hexo + GitHub + Cloudflare Pages 流程</span></span><span class="line"><span style="color:#E5C07B">-</span><span style="color:#ABB2BF"> 配置必须可以实际运行，不要只给概念说明</span></span></code></pre></td></tr></tbody></table></figure><h2 id="好处">好处</h2><p>这套 CMS 系统解决了我最头疼的两个问题。</p><h3 id="图片资源管理">图片资源管理</h3><p>之前写博客最头疼的就是插图片，因为一些经历，导致狗子不敢使用图床。现在存放图片都是遵循 Hexo 的管理样式：一篇文章对应文章同名目录下的图片。但是每次更换电脑都需要在 Typora 下配置文件存储方式，这就有点麻烦。</p><p>这个 CMS 支持自动上传管理图片，所以刚好适合我这种不使用图床，使用纯仓库的方式来管理博客文章还有资源的用户。当然它也支持 S3 上传，但貌似不支持自行托管的 S3。不过大多数自己写博客的一般还是会用 Cloudflare R2，这种它也是支持的。</p><h3 id="文档修改管理">文档修改管理</h3><p>事实证明，狗子使用静态博客是对的。动态博客的话，PHP 系的，WordPress 一大堆漏洞，Typecho 也好不到哪里去。动态的有个 Halo，个人体验了一下，感觉还可以，但因为个人平时都习惯使用 Markdown，那种传统的 Prose 反而用不习惯。而且搭建博客还要准备一台服务器，麻烦得要死。当然，Serverless 的方案也有很多，有一些是 Workers 边缘渲染，不过个人不太喜欢。个人最讨厌的就是那种限制访问次数、限制流量的东西。而且那些方案即使在你自己的服务器上跑，由于本来就不是为你自己服务器搭建而设计的，到时候弄出来因为服务器配置不够，用户访问一多卡得要命，缓存还不好配置（比如评论区🐛）。所以，我是非常反感这种边缘渲染、服务端渲染的 JS 框架的。所以博客这种东西，到最后 SSG 是正确的，该静静该动动。打包速度反而不用操心，实在不行到时候换 Hugo，挑个前端写得好的 AI 重新构建一下就可以了。反正博客这种东西，纯 CSS 就可以实现，基本上用不到 JS 就可以把文章文字显示出来。不过一般也到不了那么多文章。</p><p>但静态博客最大的问题就是缺乏内容管理系统，也就是没有一个界面去管理文章和数据。因为狗子在写 Markdown 的时候，尤其是一些偏向内容的，还是比较习惯于使用 Typora。虽然现在主力都用 VS Code，但没有图片资源默认存放路径的设置，反而是用 Typora 写有图片的文章比较方便。</p><h2 id="一点小插曲">一点小插曲</h2><p>在搞这个 CMS 的过程中遇到了一些问题。其中最头大的就是那个静态资源路径的管理（图片）。我在 CMS 中上传的图片，还有之前已经传过的图片，出现了无法解析路径并显示的问题。</p><p>我的这种静态资源管理方法在文档里面并没有现成的案例。也就是说，<strong>没有人测试过</strong>。这些配置都是 AI 自动帮我生成出来的，好在它的配置文件是有 YAML Schema 的，至少核对了一遍，配置没有问题。尝试让 AI 修复根本无法修好，甚至 AI 把我的配置重写了一遍，把之前的一些都给改乱了。还好我的仓库有 git 提交，给它恢复回来了。</p><p>经过进一步人工排查，猜测是 CMS 程序存在 bug。随后让 AI 把 CMS 的仓库拉下来扫描了一遍，果真和我猜想的一模一样：它的路径不会解析模板字符串，导致 CMS 里面无法显示图片，但实际上上传的路径是对的。</p><p>于是就直接给 CMS 仓库提 issues。作者是日本人，也不知道是什么时区，我提 issues 的时间有点非人类，但是挺快速的，他 30 分钟左右就修好并发新版，点赞👍！</p><p><img src="sveltia-cms/20260729-105206.png" alt=""></p><h2 id="日后计划">日后计划</h2><p>有了这个 CMS，以后发文章会更方便了，有想法直接写就行。</p><p>现在其实 AI 水文章也挺方便的。现在微信上刷到的公众号，10 篇里面可能有七八篇都是 AI 生成的。而且 AI 可以帮你搜集信息并汇总，比一篇一篇查博客强多了。所以要开始重新思考博客存在的价值。我以后在博客中一般只会记录一些比较繁琐的事情，尽量多插图片，毕竟目前 AI 还不会做到精准截图。😂</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/sveltia-cms</id>
    <link href="https://www.xrgzs.top/posts/sveltia-cms"/>
    <published>2026-07-29T01:04:00.000Z</published>
    <summary>该静静，该动动，SSG万岁！</summary>
    <title>SveltiaCMS与Hexo集成</title>
    <updated>2026-07-29T03:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="CUPS" scheme="https://www.xrgzs.top/tags/cups/"/>
    <category term="HomeLab" scheme="https://www.xrgzs.top/tags/homelab/"/>
    <content>
      <![CDATA[<p>接上文，我们已经在Linux系统上安装了CUPS，并成功添加了EPSON L383打印机。但这台打印机不仅仅是打印机，它还具备扫描功能。为了在局域网内共享扫描功能，我们需要使用scanservjs。</p><p>官网：<a href="https://sbs20.github.io/scanservjs/">https://sbs20.github.io/scanservjs/</a></p><blockquote><p>scanservjs是一个扫描仪的Web UI前端。它允许您在网络上共享一个或多个扫描仪（使用<a href="http://www.sane-project.org/">SANE</a>），无需驱动程序或复杂的安装。</p></blockquote><p>也就是说，只需要打印机的扫描驱动支持<a href="http://www.sane-project.org/">SANE协议</a>，就可以通过ScanServJS共享扫描功能。</p><h2 id="前置">前置</h2><ul><li>安装EPSON L383打印机的SANE驱动</li><li><s>安装 sane-utils</s></li><li><s>配置 Node.js</s></li></ul><p>其实SANE官方的安装脚本也会自动安装，所以只需要确保打印机能正常在Linux上扫描即可。另外，现在官方已经提供deb包了，所以直接安装即可，本文不使用脚本。</p><p>执行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">scanimage</span><span style="color:#D19A66"> -L</span></span></code></pre></td></tr></tbody></table></figure><p>正常情况下会显示类似如下的输出：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@CUPS:~#</span><span style="color:#98C379"> scanimage</span><span style="color:#D19A66"> -L</span></span><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> directory:</span><span style="color:#98C379"> /var/lib/snmp/cert_indexes</span></span><span class="line"><span style="color:#61AFEF">device</span><span style="color:#98C379"> `</span><span style="color:#61AFEF">epson2:libusb:002:002' is a Epson PID 1120 flatbed scanner</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装scanservjs">安装scanservjs</h2><p>访问：<a href="https://github.com/sbs20/scanservjs/releases/latest">https://github.com/sbs20/scanservjs/releases/latest</a> 获取最新版本的deb包下载地址，本文使用的是v3.2.0版本。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> https://gh.xrgzs.top/https://github.com/sbs20/scanservjs/releases/download/v3.2.0/scanservjs_3.2.0-1_all.deb</span></span><span class="line"><span style="color:#61AFEF">apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> ./scanservjs_3.2.0-1_all.deb</span></span></code></pre></td></tr></tbody></table></figure><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">Reading</span><span style="color:#98C379"> package</span><span style="color:#98C379"> lists...</span><span style="color:#98C379"> Done</span></span><span class="line"><span style="color:#61AFEF">Building</span><span style="color:#98C379"> dependency</span><span style="color:#98C379"> tree...</span><span style="color:#98C379"> Done</span></span><span class="line"><span style="color:#61AFEF">Reading</span><span style="color:#98C379"> state</span><span style="color:#98C379"> information...</span><span style="color:#98C379"> Done</span></span><span class="line"><span style="color:#61AFEF">Note,</span><span style="color:#98C379"> selecting</span><span style="color:#98C379"> 'scanservjs'</span><span style="color:#98C379"> instead</span><span style="color:#98C379"> of</span><span style="color:#98C379"> './scanservjs_3.2.0-1_all.deb'</span></span><span class="line"><span style="color:#61AFEF">The</span><span style="color:#98C379"> following</span><span style="color:#98C379"> additional</span><span style="color:#98C379"> packages</span><span style="color:#98C379"> will</span><span style="color:#98C379"> be</span><span style="color:#98C379"> installed:</span></span><span class="line"><span style="color:#61AFEF">  fontconfig</span><span style="color:#98C379"> hicolor-icon-theme</span><span style="color:#98C379"> imagemagick</span><span style="color:#98C379"> imagemagick-6-common</span></span><span class="line"><span style="color:#61AFEF">  imagemagick-6.q16</span><span style="color:#98C379"> libarchive13t64</span><span style="color:#98C379"> libcares2</span><span style="color:#98C379"> libdatrie1</span><span style="color:#98C379"> libdjvulibre-text</span></span><span class="line"><span style="color:#61AFEF">  libdjvulibre21</span><span style="color:#98C379"> libfftw3-double3</span><span style="color:#98C379"> libfribidi0</span><span style="color:#98C379"> libgif7</span><span style="color:#98C379"> libgomp1</span><span style="color:#98C379"> libgraphite2-3</span></span><span class="line"><span style="color:#61AFEF">  libharfbuzz0b</span><span style="color:#98C379"> libimath-3-1-29t64</span><span style="color:#98C379"> libjxr-tools</span><span style="color:#98C379"> libjxr0t64</span><span style="color:#98C379"> liblept5</span><span style="color:#98C379"> liblqr-1-0</span></span><span class="line"><span style="color:#61AFEF">  libmagickcore-6.q16-7-extra</span><span style="color:#98C379"> libmagickcore-6.q16-7t64</span></span><span class="line"><span style="color:#61AFEF">  libmagickwand-6.q16-7t64</span><span style="color:#98C379"> libnetpbm11t64</span><span style="color:#98C379"> libnode109</span><span style="color:#98C379"> libopenexr-3-1-30</span></span><span class="line"><span style="color:#61AFEF">  libpango-1.0-0</span><span style="color:#98C379"> libpangocairo-1.0-0</span><span style="color:#98C379"> libpangoft2-1.0-0</span><span style="color:#98C379"> libraw23t64</span></span><span class="line"><span style="color:#61AFEF">  libtesseract5</span><span style="color:#98C379"> libthai-data</span><span style="color:#98C379"> libthai0</span><span style="color:#98C379"> libuv1t64</span><span style="color:#98C379"> libwebpdemux2</span><span style="color:#98C379"> libwebpmux3</span></span><span class="line"><span style="color:#61AFEF">  libwmflite-0.2-7</span><span style="color:#98C379"> netpbm</span><span style="color:#98C379"> node-acorn</span><span style="color:#98C379"> node-busboy</span><span style="color:#98C379"> node-cjs-module-lexer</span></span><span class="line"><span style="color:#61AFEF">  node-undici</span><span style="color:#98C379"> node-xtend</span><span style="color:#98C379"> nodejs</span><span style="color:#98C379"> nodejs-doc</span><span style="color:#98C379"> tesseract-ocr</span><span style="color:#98C379"> tesseract-ocr-ara</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-ces</span><span style="color:#98C379"> tesseract-ocr-chi-sim</span><span style="color:#98C379"> tesseract-ocr-deu</span><span style="color:#98C379"> tesseract-ocr-eng</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-fra</span><span style="color:#98C379"> tesseract-ocr-ita</span><span style="color:#98C379"> tesseract-ocr-nld</span><span style="color:#98C379"> tesseract-ocr-osd</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-pol</span><span style="color:#98C379"> tesseract-ocr-por</span><span style="color:#98C379"> tesseract-ocr-rus</span><span style="color:#98C379"> tesseract-ocr-spa</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-tur</span></span><span class="line"><span style="color:#61AFEF">Suggested</span><span style="color:#98C379"> packages:</span></span><span class="line"><span style="color:#61AFEF">  imagemagick-6-doc</span><span style="color:#98C379"> autotrace</span><span style="color:#98C379"> enscript</span><span style="color:#98C379"> ffmpeg</span><span style="color:#98C379"> gimp</span><span style="color:#98C379"> gnuplot</span><span style="color:#98C379"> grads</span><span style="color:#98C379"> graphviz</span></span><span class="line"><span style="color:#61AFEF">  groff-base</span><span style="color:#98C379"> hp2xx</span><span style="color:#98C379"> html2ps</span><span style="color:#98C379"> libwmf-bin</span><span style="color:#98C379"> mplayer</span><span style="color:#98C379"> povray</span><span style="color:#98C379"> radiance</span><span style="color:#98C379"> texlive-base-bin</span></span><span class="line"><span style="color:#61AFEF">  transfig</span><span style="color:#98C379"> libraw-bin</span><span style="color:#98C379"> xdg-utils</span><span style="color:#98C379"> lrzip</span><span style="color:#98C379"> libfftw3-bin</span><span style="color:#98C379"> libfftw3-dev</span><span style="color:#98C379"> inkscape</span><span style="color:#98C379"> npm</span></span><span class="line"><span style="color:#61AFEF">The</span><span style="color:#98C379"> following</span><span style="color:#98C379"> NEW</span><span style="color:#98C379"> packages</span><span style="color:#98C379"> will</span><span style="color:#98C379"> be</span><span style="color:#98C379"> installed:</span></span><span class="line"><span style="color:#61AFEF">  fontconfig</span><span style="color:#98C379"> hicolor-icon-theme</span><span style="color:#98C379"> imagemagick</span><span style="color:#98C379"> imagemagick-6-common</span></span><span class="line"><span style="color:#61AFEF">  imagemagick-6.q16</span><span style="color:#98C379"> libarchive13t64</span><span style="color:#98C379"> libcares2</span><span style="color:#98C379"> libdatrie1</span><span style="color:#98C379"> libdjvulibre-text</span></span><span class="line"><span style="color:#61AFEF">  libdjvulibre21</span><span style="color:#98C379"> libfftw3-double3</span><span style="color:#98C379"> libfribidi0</span><span style="color:#98C379"> libgif7</span><span style="color:#98C379"> libgomp1</span><span style="color:#98C379"> libgraphite2-3</span></span><span class="line"><span style="color:#61AFEF">  libharfbuzz0b</span><span style="color:#98C379"> libimath-3-1-29t64</span><span style="color:#98C379"> libjxr-tools</span><span style="color:#98C379"> libjxr0t64</span><span style="color:#98C379"> liblept5</span><span style="color:#98C379"> liblqr-1-0</span></span><span class="line"><span style="color:#61AFEF">  libmagickcore-6.q16-7-extra</span><span style="color:#98C379"> libmagickcore-6.q16-7t64</span></span><span class="line"><span style="color:#61AFEF">  libmagickwand-6.q16-7t64</span><span style="color:#98C379"> libnetpbm11t64</span><span style="color:#98C379"> libnode109</span><span style="color:#98C379"> libopenexr-3-1-30</span></span><span class="line"><span style="color:#61AFEF">  libpango-1.0-0</span><span style="color:#98C379"> libpangocairo-1.0-0</span><span style="color:#98C379"> libpangoft2-1.0-0</span><span style="color:#98C379"> libraw23t64</span></span><span class="line"><span style="color:#61AFEF">  libtesseract5</span><span style="color:#98C379"> libthai-data</span><span style="color:#98C379"> libthai0</span><span style="color:#98C379"> libuv1t64</span><span style="color:#98C379"> libwebpdemux2</span><span style="color:#98C379"> libwebpmux3</span></span><span class="line"><span style="color:#61AFEF">  libwmflite-0.2-7</span><span style="color:#98C379"> netpbm</span><span style="color:#98C379"> node-acorn</span><span style="color:#98C379"> node-busboy</span><span style="color:#98C379"> node-cjs-module-lexer</span></span><span class="line"><span style="color:#61AFEF">  node-undici</span><span style="color:#98C379"> node-xtend</span><span style="color:#98C379"> nodejs</span><span style="color:#98C379"> nodejs-doc</span><span style="color:#98C379"> scanservjs</span><span style="color:#98C379"> tesseract-ocr</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-ara</span><span style="color:#98C379"> tesseract-ocr-ces</span><span style="color:#98C379"> tesseract-ocr-chi-sim</span><span style="color:#98C379"> tesseract-ocr-deu</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-eng</span><span style="color:#98C379"> tesseract-ocr-fra</span><span style="color:#98C379"> tesseract-ocr-ita</span><span style="color:#98C379"> tesseract-ocr-nld</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-osd</span><span style="color:#98C379"> tesseract-ocr-pol</span><span style="color:#98C379"> tesseract-ocr-por</span><span style="color:#98C379"> tesseract-ocr-rus</span></span><span class="line"><span style="color:#61AFEF">  tesseract-ocr-spa</span><span style="color:#98C379"> tesseract-ocr-tur</span></span><span class="line"><span style="color:#61AFEF">0</span><span style="color:#98C379"> upgraded,</span><span style="color:#D19A66"> 62</span><span style="color:#98C379"> newly</span><span style="color:#98C379"> installed,</span><span style="color:#D19A66"> 0</span><span style="color:#98C379"> to</span><span style="color:#98C379"> remove</span><span style="color:#98C379"> and</span><span style="color:#D19A66"> 0</span><span style="color:#98C379"> not</span><span style="color:#98C379"> upgraded.</span></span><span class="line"><span style="color:#61AFEF">Need</span><span style="color:#98C379"> to</span><span style="color:#98C379"> get</span><span style="color:#D19A66"> 50.8</span><span style="color:#98C379"> MB/53.3</span><span style="color:#98C379"> MB</span><span style="color:#98C379"> of</span><span style="color:#98C379"> archives.</span></span><span class="line"><span style="color:#61AFEF">After</span><span style="color:#98C379"> this</span><span style="color:#98C379"> operation,</span><span style="color:#D19A66"> 189</span><span style="color:#98C379"> MB</span><span style="color:#98C379"> of</span><span style="color:#98C379"> additional</span><span style="color:#98C379"> disk</span><span style="color:#98C379"> space</span><span style="color:#98C379"> will</span><span style="color:#98C379"> be</span><span style="color:#98C379"> used.</span></span><span class="line"><span style="color:#61AFEF">Do</span><span style="color:#98C379"> you</span><span style="color:#98C379"> want</span><span style="color:#98C379"> to</span><span style="color:#98C379"> continue?</span><span style="color:#ABB2BF"> [Y/n] </span></span></code></pre></td></tr></tbody></table></figure><p>可以看到，安装过程中会自动安装一些依赖包，包括Node.js、Tesseract OCR等。这里我使用的Ubuntu 24.04 LTS 系统，系统软件源里面的Node.js版本是v18.19.1，其实也不算新，如果你使用的是更老的系统，自带的Node.js版本可能会更老，需要手动配置软件源。</p><p>安装完成后，ScanServJS会自动注册为一个systemd服务，并且默认会在系统启动时自动启动。你可以使用以下命令检查服务状态：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> scanservjs</span></span></code></pre></td></tr></tbody></table></figure><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@CUPS:~#</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> scanservjs</span></span><span class="line"><span style="color:#61AFEF">●</span><span style="color:#98C379"> scanservjs.service</span><span style="color:#98C379"> -</span><span style="color:#98C379"> scanservjs</span></span><span class="line"><span style="color:#61AFEF">     Loaded:</span><span style="color:#98C379"> loaded</span><span style="color:#ABB2BF"> (/u</span></span><span class="line"><span style="color:#61AFEF">sr/lib/systemd/system/scanservjs.service</span><span style="color:#ABB2BF">; </span><span style="color:#61AFEF">enabled</span><span style="color:#ABB2BF">; </span><span style="color:#61AFEF">preset:</span><span style="color:#ABB2BF"> </span></span><span class="line"><span style="color:#61AFEF">enabled</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">     Active:</span><span style="color:#98C379"> active</span><span style="color:#ABB2BF"> (running) since Sun 2026-07-26 17:29:17 UTC; </span><span style="color:#61AFEF">4min</span></span><span class="line"><span style="color:#61AFEF"> 31s</span><span style="color:#98C379"> ago</span></span><span class="line"><span style="color:#61AFEF">   Main</span><span style="color:#98C379"> PID:</span><span style="color:#D19A66"> 1340</span><span style="color:#ABB2BF"> (node)</span></span><span class="line"><span style="color:#61AFEF">      Tasks:</span><span style="color:#D19A66"> 10</span><span style="color:#ABB2BF"> (limit: </span><span style="color:#D19A66">521</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">     Memory:</span><span style="color:#98C379"> 62.7M</span><span style="color:#ABB2BF"> (peak: </span><span style="color:#98C379">66.2M</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">        CPU:</span><span style="color:#98C379"> 899ms</span></span><span class="line"><span style="color:#61AFEF">     CGroup:</span><span style="color:#98C379"> /system.slice/scanservjs.service</span></span><span class="line"><span style="color:#61AFEF">             └─1340</span><span style="color:#98C379"> node</span><span style="color:#98C379"> /usr/lib/scanservjs/server/server.js</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Jul</span><span style="color:#D19A66"> 26</span><span style="color:#98C379"> 17:29:17</span><span style="color:#98C379"> CUPS</span><span style="color:#98C379"> systemd[1]:</span><span style="color:#98C379"> Started</span><span style="color:#98C379"> scanservjs.service</span><span style="color:#98C379"> -</span><span style="color:#98C379"> scanservjs.</span></span><span class="line"><span style="color:#61AFEF">Jul</span><span style="color:#D19A66"> 26</span><span style="color:#98C379"> 17:29:18</span><span style="color:#98C379"> CUPS</span><span style="color:#98C379"> server.js[1340]:</span><span style="color:#ABB2BF"> [2026-07-26T17:29:18.839Z] INFO (</span><span style="color:#61AFEF">server</span><span style="color:#ABB2BF">): </span></span><span class="line"><span style="color:#61AFEF">scanservjs</span><span style="color:#98C379"> started</span><span style="color:#98C379"> listening:</span><span style="color:#98C379"> https://:::8080</span></span><span class="line"><span style="color:#61AFEF">root@CUPS:~#</span><span style="color:#ABB2BF"> </span></span></code></pre></td></tr></tbody></table></figure><p>如果服务没启动的话，具体 systemctl 的使用方法 TL;DR，建议自行 AI。</p><h2 id="使用scanservjs">使用scanservjs</h2><p>访问：<code>http://&lt;服务器IP&gt;:8080/</code></p><p><img src="./scanservjs/web-ui.png" alt="ScanServJS界面"></p><p>这个界面其实很容易使用，而且有中文。只需要选择一下你的扫描设备，然后设置一下分辨率（一般300dpi就够了）、颜色模式（黑白、灰度、彩色），滤镜一般不用开，格式选择PNG或者TIFF都是无损的，最后点击扫描按钮就可以了。</p><p>扫描好的文件可以通过「文件」菜单进入文件页面查看和下载。注意这个扫描完的文件是会存到打印服务器，其他人也可以看得到，所以用完请记得删除。</p><p><img src="./scanservjs/file.png" alt="文件页面"></p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/scanservjs</id>
    <link href="https://www.xrgzs.top/posts/scanservjs"/>
    <published>2026-07-27T00:47:13.000Z</published>
    <summary>scanservjs共享EPSON L383扫描功能</summary>
    <title>scanservjs共享EPSON L383扫描功能</title>
    <updated>2026-07-27T00:47:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="OpenWrt" scheme="https://www.xrgzs.top/tags/openwrt/"/>
    <category term="EasyTier" scheme="https://www.xrgzs.top/tags/easytier/"/>
    <content>
      <![CDATA[<p>最近在 OpenWrt 上跑 EasyTier，对 EasyTier 的 LuCI 插件加入AI“味真族”感到不爽，因此需要研究怎么手动安装。</p><h2 id="为什么不用官方-LuCI？">为什么不用官方 LuCI？</h2><ol><li><code>easytier-cli</code> 不支持 OpenWrt 的 <code>/etc/init.d</code>（procd） 自动配置，<s>逼你用它的 LuCI 受着呗🥴（bushi）</s></li><li>有 <code>luci-app-easytier</code> 这个 <a href="https://github.com/EasyTier/luci-app-easytier/pulse">GitHub 仓库</a>，但是我个人觉的这个写的太乱太杂，首页 <strong>AI“味真足</strong>”🤢（之前好好的，突然加了这个“神金”AI主页💩，真的yue了🤮），还带<strong>上传功能</strong>👿，<s>感觉路由器被强j了</s></li><li>有中心配置服务器，不需要配置，直接 <code>easytier-core -w tcp://xxx.xxx.xxx.xxx:xxxxx/xxxx</code> 就可以启，没必要OpenWrt 上面调，加载状态信息还要加载半天🤥</li><li>之前用 EasyTier LuCI 改完配置给我自动清空 <code>/etc/config/network</code> 过💥🧨，有心理阴影😰，<s>一日被蛇咬，十年怕井绳🐍</s></li></ol><h2 id="下载安装-EasyTier-核心">下载安装 EasyTier 核心</h2><p>这里我们直接下载、解压、安装 GitHub Release 里面的二进制。</p><h3 id="确认路由器架构">确认路由器架构</h3><p>使用 <code>uname -m</code> 等命令确认路由器架构🤖：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@ImmortalWrt:~#</span><span style="color:#98C379"> uname</span><span style="color:#D19A66"> -m</span></span><span class="line"><span style="color:#61AFEF">x86_64</span></span></code></pre></td></tr></tbody></table></figure><h3 id="下载对应的-EasyTier-二进制">下载对应的 EasyTier 二进制</h3><ul><li><p>去 GitHub Releases 找对应的下载地址：<a href="https://github.com/EasyTier/EasyTier/releases">https://github.com/EasyTier/EasyTier/releases</a></p></li><li><p>官网也能找到：<a href="https://easytier.cn/guide/download.html">https://easytier.cn/guide/download.html</a></p></li></ul><p>这边<strong>建议使用最新版</strong>，<s>因为稳定版肯定有最新测试版修了的严重bug😅（）</s></p><p>准备一下依赖（这里我用的是 24.10+opkg，如果是新版得用 <code>apk add</code> 命令安装）：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">opkg</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">opkg</span><span style="color:#98C379"> install</span><span style="color:#98C379"> curl</span><span style="color:#98C379"> unzip</span><span style="color:#98C379"> kmod-tun</span></span></code></pre></td></tr></tbody></table></figure><p>下载（个人喜欢<code>curl</code>，<code>wget</code> 应该 <code>busybox</code> 会自带）：</p><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># ET_VERSION="2.6.4"</span></span><span class="line"><span style="color:#E06C75">ET_VERSION</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">$(</span><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -sL</span><span style="color:#98C379"> https://api.github.com/repos/EasyTier/EasyTier/releases/latest</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> '"tag_name":'</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sed</span><span style="color:#D19A66"> -E</span><span style="color:#98C379"> 's/.*"v([^"]+)".*/\1/'</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># ET_TARGET="x86_64"</span></span><span class="line"><span style="color:#E06C75">ET_TARGET</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">$(</span><span style="color:#61AFEF">uname</span><span style="color:#D19A66"> -m</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">ET_ZIPURL</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"https://github.com/EasyTier/EasyTier/releases/download/v</span><span style="color:#E06C75">$ET_VERSION</span><span style="color:#98C379">/easytier-linux-</span><span style="color:#E06C75">$ET_TARGET</span><span style="color:#98C379">-v</span><span style="color:#E06C75">$ET_VERSION</span><span style="color:#98C379">.zip"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># wget "$ET_ZIPURL" -O /tmp/et.zip</span></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$ET_ZIPURL</span><span style="color:#98C379">"</span><span style="color:#D19A66"> -Lo</span><span style="color:#98C379"> /tmp/et.zip</span></span></code></pre></td></tr></tbody></table></figure><h3 id="解压并安装-EasyTier-到系统">解压并安装 EasyTier 到系统</h3><p>先<code>unzip -l</code> look look内容👀，不然到时候解压一锅糟🤯：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@ImmortalWrt:~#</span><span style="color:#98C379"> unzip</span><span style="color:#D19A66"> -l</span><span style="color:#98C379"> /tmp/et.zip</span></span><span class="line"><span style="color:#61AFEF">Archive:</span><span style="color:#98C379">  /tmp/et.zip</span></span><span class="line"><span style="color:#61AFEF">  Length</span><span style="color:#98C379">      Date</span><span style="color:#98C379">    Time</span><span style="color:#98C379">    Name</span></span><span class="line"><span style="color:#61AFEF">---------</span><span style="color:#D19A66">  ----------</span><span style="color:#D19A66"> -----</span><span style="color:#D19A66">   ----</span></span><span class="line"><span style="color:#61AFEF">        0</span><span style="color:#98C379">  05-12-2026</span><span style="color:#98C379"> 21:43</span><span style="color:#98C379">   easytier-linux-x86_64/</span></span><span class="line"><span style="color:#61AFEF">  7558408</span><span style="color:#98C379">  05-12-2026</span><span style="color:#98C379"> 21:26</span><span style="color:#98C379">   easytier-linux-x86_64/easytier-core</span></span><span class="line"><span style="color:#61AFEF">  7103316</span><span style="color:#98C379">  05-12-2026</span><span style="color:#98C379"> 21:26</span><span style="color:#98C379">   easytier-linux-x86_64/easytier-web</span></span><span class="line"><span style="color:#61AFEF">  7749796</span><span style="color:#98C379">  05-12-2026</span><span style="color:#98C379"> 21:18</span><span style="color:#98C379">   easytier-linux-x86_64/easytier-web-embed</span></span><span class="line"><span style="color:#61AFEF">  3095200</span><span style="color:#98C379">  05-12-2026</span><span style="color:#98C379"> 21:24</span><span style="color:#98C379">   easytier-linux-x86_64/easytier-cli</span></span><span class="line"><span style="color:#61AFEF">---------</span><span style="color:#D19A66">                     -------</span></span><span class="line"><span style="color:#61AFEF"> 25506720</span><span style="color:#D19A66">                     5</span><span style="color:#98C379"> files</span></span></code></pre></td></tr></tbody></table></figure><p>可以看到这个压缩包里面有个文件夹，然后执行 <code>unzip /tmp/et.zip -d /tmp</code> 解压📦：</p><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@ImmortalWrt:~#</span><span style="color:#98C379"> unzip</span><span style="color:#98C379"> /tmp/et.zip</span><span style="color:#D19A66"> -d</span><span style="color:#98C379"> /tmp</span></span><span class="line"><span style="color:#61AFEF">Archive:</span><span style="color:#98C379">  /tmp/et.zip</span></span><span class="line"><span style="color:#61AFEF">   creating:</span><span style="color:#98C379"> /tmp/easytier-linux-x86_64/</span></span><span class="line"><span style="color:#61AFEF">  inflating:</span><span style="color:#98C379"> /tmp/easytier-linux-x86_64/easytier-core</span></span><span class="line"><span style="color:#61AFEF">  inflating:</span><span style="color:#98C379"> /tmp/easytier-linux-x86_64/easytier-web</span></span><span class="line"><span style="color:#61AFEF">  inflating:</span><span style="color:#98C379"> /tmp/easytier-linux-x86_64/easytier-web-embed</span></span><span class="line"><span style="color:#61AFEF">  inflating:</span><span style="color:#98C379"> /tmp/easytier-linux-x86_64/easytier-cli</span></span></code></pre></td></tr></tbody></table></figure><p>这里我们不需要所有的文件，只要<code>easytier-core</code>、<code>easytier-cli</code>即可，给它拷到系统里面（如果你要其他的也可以自己拷）：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># ET_TARGET=$(uname -m)</span></span><span class="line"><span style="color:#61AFEF">cp</span><span style="color:#98C379"> /tmp/easytier-linux-</span><span style="color:#E06C75">$ET_TARGET</span><span style="color:#98C379">/easytier-core</span><span style="color:#98C379"> /usr/sbin/easytier-core</span></span><span class="line"><span style="color:#61AFEF">cp</span><span style="color:#98C379"> /tmp/easytier-linux-</span><span style="color:#E06C75">$ET_TARGET</span><span style="color:#98C379">/easytier-cli</span><span style="color:#98C379"> /usr/sbin/easytier-cli</span></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> /usr/sbin/easytier-core</span></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> /usr/sbin/easytier-cli</span></span></code></pre></td></tr></tbody></table></figure><p>验证一下能不能用🧐：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@ImmortalWrt:~#</span><span style="color:#98C379"> which</span><span style="color:#98C379"> easytier-core</span></span><span class="line"><span style="color:#61AFEF">/usr/sbin/easytier-core</span></span><span class="line"><span style="color:#61AFEF">root@ImmortalWrt:~#</span><span style="color:#98C379"> easytier-core</span><span style="color:#D19A66"> --version</span></span><span class="line"><span style="color:#61AFEF">easytier-core</span><span style="color:#98C379"> 2.6.4-8428a89d</span></span></code></pre></td></tr></tbody></table></figure><p>要是这里打不开了，说明你下错了架构，折回去重新下🤣。</p><p>删除安装文件🗑️：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># ET_TARGET=$(uname -m)</span></span><span class="line"><span style="color:#61AFEF">rm</span><span style="color:#D19A66"> -rf</span><span style="color:#98C379"> /tmp/easytier-linux-</span><span style="color:#E06C75">$ET_TARGET</span><span style="color:#98C379"> /tmp/et.zip</span></span></code></pre></td></tr></tbody></table></figure><h2 id="创建-EasyTier-配置">创建 EasyTier 配置</h2><p>如果有 EasyTier Web 中心配置服务器可以跳过♿️，这个不是重点😶‍🌫️。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> /etc/easytier</span></span><span class="line"><span style="color:#61AFEF">cat</span><span style="color:#ABB2BF"> > </span><span style="color:#98C379">/etc/easytier/config.toml</span><span style="color:#ABB2BF"> &#x3C;&#x3C; </span><span style="color:#ABB2BF">'EOF'</span></span><span class="line"><span style="color:#98C379">instance_name = "openwrt-node"</span></span><span class="line"><span style="color:#98C379">hostname = "openwrt"</span></span><span class="line"><span style="color:#98C379">ipv4 = "10.144.144.1"</span></span><span class="line"></span><span class="line"><span style="color:#98C379">[network_identity]</span></span><span class="line"><span style="color:#98C379">network_name = "你的网络名"</span></span><span class="line"><span style="color:#98C379">network_secret = "你的密钥"</span></span><span class="line"></span><span class="line"><span style="color:#98C379">[[peer]]</span></span><span class="line"><span style="color:#98C379">uri = "tcp://1.2.3.4:11010"</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">EOF</span></span></code></pre></td></tr></tbody></table></figure><h2 id="创建-init-d-自启服务">创建 init.d 自启服务</h2><p>OpenWrt 的 init 脚本需要 source <code>/etc/rc.common</code> 作为 wrapper，脚本放在 <code>/etc/init.d/</code> 下，用 <code>enable</code> 命令创建 <code>/etc/rc.d/</code> 中的符号链接实现开机自启。TL;DR</p><p>参考：<a href="https://openwrt.org/docs/techref/initscripts">https://openwrt.org/docs/techref/initscripts</a></p><p>下面是用配置文件的情况：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">cat</span><span style="color:#ABB2BF"> > </span><span style="color:#98C379">/etc/init.d/easytier</span><span style="color:#ABB2BF"> &#x3C;&#x3C; </span><span style="color:#ABB2BF">'EOF'</span></span><span class="line"><span style="color:#98C379">#!/bin/sh /etc/rc.common</span></span><span class="line"></span><span class="line"><span style="color:#98C379">START=95</span></span><span class="line"><span style="color:#98C379">STOP=10</span></span><span class="line"><span style="color:#98C379">USE_PROCD=1</span></span><span class="line"></span><span class="line"><span style="color:#98C379">PROG=/usr/sbin/easytier-core</span></span><span class="line"><span style="color:#98C379">CONFIG=/etc/easytier/config.toml</span></span><span class="line"></span><span class="line"><span style="color:#98C379">start_service() &#123;</span></span><span class="line"><span style="color:#98C379">    procd_open_instance</span></span><span class="line"><span style="color:#98C379">    procd_set_param command "$PROG" --config-file "$CONFIG"</span></span><span class="line"><span style="color:#98C379">    procd_set_param respawn 3600 5 5</span></span><span class="line"><span style="color:#98C379">    procd_set_param stdout 1</span></span><span class="line"><span style="color:#98C379">    procd_set_param stderr 1</span></span><span class="line"><span style="color:#98C379">    procd_close_instance</span></span><span class="line"><span style="color:#98C379">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#98C379">reload_service() &#123;</span></span><span class="line"><span style="color:#98C379">    procd_kill easytier</span></span><span class="line"><span style="color:#98C379">    start_service</span></span><span class="line"><span style="color:#98C379">&#125;</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> /etc/init.d/easytier</span></span></code></pre></td></tr></tbody></table></figure><p>如果有 EasyTier Web 中心配置服务器，不需要配置文件：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">cat</span><span style="color:#ABB2BF"> > </span><span style="color:#98C379">/etc/init.d/easytier</span><span style="color:#ABB2BF"> &#x3C;&#x3C; </span><span style="color:#ABB2BF">'EOF'</span></span><span class="line"><span style="color:#98C379">#!/bin/sh /etc/rc.common</span></span><span class="line"></span><span class="line"><span style="color:#98C379">START=95</span></span><span class="line"><span style="color:#98C379">STOP=10</span></span><span class="line"><span style="color:#98C379">USE_PROCD=1</span></span><span class="line"></span><span class="line"><span style="color:#98C379">PROG=/usr/sbin/easytier-core</span></span><span class="line"></span><span class="line"><span style="color:#98C379">start_service() &#123;</span></span><span class="line"><span style="color:#98C379">    procd_open_instance</span></span><span class="line"><span style="color:#98C379">    procd_set_param command "$PROG" -w tcp://xxx.xxx.xxx.xxx:xxxxx/xxxx # 这里换成你的中心配置服务器</span></span><span class="line"><span style="color:#98C379">    procd_set_param respawn 3600 5 5</span></span><span class="line"><span style="color:#98C379">    procd_set_param stdout 1</span></span><span class="line"><span style="color:#98C379">    procd_set_param stderr 1</span></span><span class="line"><span style="color:#98C379">    procd_close_instance</span></span><span class="line"><span style="color:#98C379">&#125;</span></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> /etc/init.d/easytier</span></span></code></pre></td></tr></tbody></table></figure><h2 id="启用并启动服务">启用并启动服务</h2><p><code>service easytier xxx</code> 命令和 LuCI Web UI 里面那个<code>系统-&gt;启动项</code>应该也能用。</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 开机自启</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> enable</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 立即启动</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> start</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 查看状态</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> status</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 重启服务器</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> restart</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 停止服务</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> stop</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 禁用服务</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/easytier</span><span style="color:#98C379"> disable</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 看日志（procd 会转发到 logd）</span></span><span class="line"><span style="color:#61AFEF">logread</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> easytier</span></span></code></pre></td></tr></tbody></table></figure><h2 id="sysupgrade-持久化">sysupgrade 持久化</h2><p>如果使用 <code>sysupgrade</code> 升级固件⬆️，需要在 <code>/etc/sysupgrade.conf</code> 中配置要保留的文件，<strong>否则升级后会丢失配置😨</strong>。</p><p>参考：<a href="https://openwrt.org/docs/guide-user/installation/generic.sysupgrade">https://openwrt.org/docs/guide-user/installation/generic.sysupgrade</a></p><p>把以下内容追加到 <code>/etc/sysupgrade.conf</code>：</p><figure class="highlight text"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span>/usr/sbin/easytier-core</span></span><span class="line"><span>/etc/easytier/</span></span><span class="line"><span>/etc/init.d/easytier</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>不过搞不好升级以后硬件ID又变了，会获取不到配置，要么你自己弄个文件存一下，不要默认 <code>/etc/machine-id</code>，要么你就不要远程通过 EasyTier 网络升级，反正我不敢这么做🫠</p></blockquote><h2 id="放行-EasyTier-的-WAN-入站端口">放行 EasyTier 的 WAN 入站端口</h2><p>正常情况下，OpenWrt 对 WAN 默认封禁入站，外部无法访问到 EasyTier 核心监听的端口，会导致<strong>无法打洞直连</strong>，因此需要增加防火墙规则。</p><p>如果你的环境本身就无法P2P直连，那这步可以不做🤨。</p><p>假设配置文件片段为：</p><figure class="highlight toml"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#E06C75">listeners</span><span style="color:#ABB2BF"> = [</span></span><span class="line"><span style="color:#98C379">    "tcp://0.0.0.0:11010"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">    "udp://0.0.0.0:11010"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">    "wg://0.0.0.0:11011"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">    "quic://0.0.0.0:11012"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#98C379">    "faketcp://0.0.0.0:11013"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">]</span></span></code></pre></td></tr></tbody></table></figure><h3 id="执行UCI命令">执行UCI命令</h3><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 端口:协议 列表</span></span><span class="line"><span style="color:#E06C75">ports</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"11010:tcp 11010:udp 11011:udp 11012:udp 11013:tcp"</span></span><span class="line"><span style="color:#C678DD">for</span><span style="color:#E06C75"> p</span><span style="color:#C678DD"> in</span><span style="color:#E06C75"> $ports</span><span style="color:#ABB2BF">;</span><span style="color:#C678DD">do</span></span><span class="line"><span style="color:#E06C75">  port</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">$&#123;</span><span style="color:#E06C75">p</span><span style="color:#ABB2BF">%:*&#125;;</span><span style="color:#E06C75">proto</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">$&#123;</span><span style="color:#E06C75">p</span><span style="color:#ABB2BF">#*:&#125;</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> add</span><span style="color:#98C379"> firewall</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.@rule[-1].name="easytier-</span><span style="color:#E06C75">$proto</span><span style="color:#98C379">-</span><span style="color:#E06C75">$port</span><span style="color:#98C379">"</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.@rule[-1].src=wan</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.@rule[-1].dest_port=</span><span style="color:#E06C75">$port</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.@rule[-1].proto=</span><span style="color:#E06C75">$proto</span></span><span class="line"><span style="color:#61AFEF">  uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.@rule[-1].target=ACCEPT</span></span><span class="line"><span style="color:#C678DD">done</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> commit</span><span style="color:#98C379"> firewall</span></span><span class="line"><span style="color:#61AFEF">service</span><span style="color:#98C379"> firewall</span><span style="color:#98C379"> restart</span></span></code></pre></td></tr></tbody></table></figure><h3 id="改-etc-config-firewall">改 <code>/etc/config/firewall</code></h3><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier-tcp-11010'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'wan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest_port</span><span style="color:#98C379"> '11010'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'tcp'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> target</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier-udp-11010'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'wan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest_port</span><span style="color:#98C379"> '11010'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'udp'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> target</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier-wg-11011'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'wan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest_port</span><span style="color:#98C379"> '11011'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'udp'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> target</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier-quic-11012'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'wan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest_port</span><span style="color:#98C379"> '11012'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'udp'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> target</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> rule</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier-faketcp-11013'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'wan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest_port</span><span style="color:#98C379"> '11013'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'tcp'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> target</span><span style="color:#98C379"> 'ACCEPT'</span></span></code></pre></td></tr></tbody></table></figure><p>然后执行：<code>service firewall restart</code> 重启防火墙。</p><h3 id="LuCI-Web-UI">LuCI Web UI</h3><p><strong>网络 → 防火墙 → 通信规则 → 添加</strong>，按下表逐条填写，每条保存后再加下一条：</p><table><thead><tr><th>Name</th><th>协议</th><th>源区域</th><th>目标端口</th><th>动作</th></tr></thead><tbody><tr><td>easytier-tcp-11010</td><td>TCP</td><td>wan</td><td>11010</td><td>接受</td></tr><tr><td>easytier-udp-11010</td><td>UDP</td><td>wan</td><td>11010</td><td>接受</td></tr><tr><td>easytier-wg-11011</td><td>UDP</td><td>wan</td><td>11011</td><td>接受</td></tr><tr><td>easytier-quic-11012</td><td>UDP</td><td>wan</td><td>11012</td><td>接受</td></tr><tr><td>easytier-faketcp-11013</td><td>TCP</td><td>wan</td><td>11013</td><td>接受</td></tr></tbody></table><p>填完所有条目后点右下角<strong>保存并应用</strong>。</p><blockquote><p>不用命令，要加这么多，手都点麻了🤡，我觉得还是用命令刷吧😂</p></blockquote><h2 id="增加EasyTier防火墙区域">增加EasyTier防火墙区域</h2><p>OpenWrt 系统默认防火墙区域转发策略为 <strong>DROP（丢弃）</strong>。EasyTier 虚拟网卡（默认设备名 <code>tun0</code>）初始无绑定任何防火墙区域，所有流量会被系统默认拦截，导致内网互通、设备访问异常。<strong>所以需要增加防火墙区域转发规则📝</strong>。如果你不做这步就能访问内网，那应该是用了用户态协议、软件NAT。</p><p>下面提供了UCI命令和改配置文件两种方式：</p><h3 id="配置-EasyTier-网络接口">配置 EasyTier 网络接口</h3><p>首先需要配置网络接口（interface），将<code>tun0</code>虚拟<strong>网卡设备</strong>绑定到<strong>逻辑接口</strong>，才能被 OpenWrt 管理。</p><p><code>/etc/config/network</code> 里加上对应接口 <code>easytier</code>：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> interface</span><span style="color:#98C379"> 'easytier'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> device</span><span style="color:#98C379"> 'tun0'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> proto</span><span style="color:#98C379"> 'none'</span></span></code></pre></td></tr></tbody></table></figure><p>对应 UCI 命令：</p><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 新建 easytier 网络接口，绑定 tun0 虚拟网卡，无IP协议</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> network.easytier='interface'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> network.easytier.device='tun0'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> network.easytier.proto='none'</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 保存网络配置</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> commit</span><span style="color:#98C379"> network</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p>当然也可以 LuCI Web UI 里面自己加，我觉得还是命令刷方便🤓。</p></blockquote><h3 id="配置-EasyTier-防火墙区域及双向转发">配置 EasyTier 防火墙区域及双向转发</h3><p>这里解释一下一些概念：</p><ul><li>入站：允许区域（内的主机）访问本机（路由器）</li><li>出站：允许本机（路由器）访问区域（内的主机）</li><li>转发：允许区域（内的主机）通过本机（路由器）访问其他区域（内的主机）</li><li>IP 动态伪装：在区域通过本机访问其他区域时进行网络地址转换（NAT）</li></ul><p><code>/etc/config/firewall</code> 追加：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 新建 easytier 区域</span></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> zone</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> name</span><span style="color:#98C379"> 'easytier'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> input</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> output</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> forward</span><span style="color:#98C379"> 'ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">    list</span><span style="color:#98C379"> network</span><span style="color:#98C379"> 'easytier'</span><span style="color:#7F848E;font-style:italic">   # 对应 /etc/config/network 里的接口名</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 允许 easytier ↔ lan 双向转发</span></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> forwarding</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'easytier'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest</span><span style="color:#98C379"> 'lan'</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">config</span><span style="color:#98C379"> forwarding</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> src</span><span style="color:#98C379"> 'lan'</span></span><span class="line"><span style="color:#61AFEF">    option</span><span style="color:#98C379"> dest</span><span style="color:#98C379"> 'easytier'</span></span></code></pre></td></tr></tbody></table></figure><p>对应 UCI 命令：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 新建 easytier 防火墙区域，放行所有入站、出站、转发流量</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.easytier='zone'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.easytier.name='easytier'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.easytier.input='ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.easytier.output='ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.easytier.forward='ACCEPT'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> add_list</span><span style="color:#98C379"> firewall.easytier.network='easytier'</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 配置 easytier 访问 LAN 区域转发</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_easytier_lan='forwarding'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_easytier_lan.src='easytier'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_easytier_lan.dest='lan'</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 配置 LAN 访问 easytier 区域转发（双向互通）</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_lan_easytier='forwarding'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_lan_easytier.src='lan'</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> set</span><span style="color:#98C379"> firewall.fwd_lan_easytier.dest='easytier'</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 保存防火墙配置</span></span><span class="line"><span style="color:#61AFEF">uci</span><span style="color:#98C379"> commit</span><span style="color:#98C379"> firewall</span></span></code></pre></td></tr></tbody></table></figure><p>你可能需要根据实际情况更改一下你的防火墙区域互访配置，比如说 lan 不能访问 easytier 区域。这一步可能在 LuCI Web UI 配置起来比较直观。</p><p>配置完后在EasyTier配置里面勾选上“系统转发”，性能可能会好些，<s>也可能更差（Bug）</s>。</p><h3 id="应用配置">应用配置</h3><p>重启网络、防火墙服务使配置生效：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">/etc/init.d/network</span><span style="color:#98C379"> restart</span></span><span class="line"><span style="color:#61AFEF">/etc/init.d/firewall</span><span style="color:#98C379"> restart</span></span></code></pre></td></tr></tbody></table></figure><h2 id="个人感受">个人感受</h2><p>这些插件其实就是个守护进程的事，不需要可视化管理的，以后能自己手搓就尽量避免插件，尤其是 APK 以后的 OpenWrt。手搓配置+AI帮写 <code>init.d</code> 📃，要是有 Docker 🐳 的话用 Docker Compose。OpenWrt 下的组网/内穿插件，我用过的感觉就 Zerotier 的 LuCI 插件我还能勉强接受，其他 Frp、Tailscale 的，感觉一个能打的都没有，连上游更新了配置，几年都跟不上，比如那个 Frp LuCI 还在用 ini。EasyTier 的这个 LuCI 功能其实还行，但就是首页太杂了，之前都没这么花里胡哨。</p><p>总之还是希望这些插件能越做越好，OpenWrt 路由器上跑组网插件还是比较正常的，这些东西放普通 Linux 发行版下反倒更难管理。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/openwrt-easytier</id>
    <link href="https://www.xrgzs.top/posts/openwrt-easytier"/>
    <published>2026-06-04T15:47:13.000Z</published>
    <summary>OpenWrt 手搓 init.d 服务，安装 EasyTier 核心</summary>
    <title>OpenWrt 手动安装 EasyTier</title>
    <updated>2026-07-20T14:23:16.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="macOS" scheme="https://www.xrgzs.top/tags/macos/"/>
    <content>
      <![CDATA[<p>由于要将 MacBook 的 Wi-Fi 网络共享给一台没有网络的机器，使用网络共享功能发现无法灵活配置IP地址。由于机器不方便使用 DHCP、更改 IP，因此无法使用。</p><p>通过搜索得知，在 macOS 上配置网卡 NAT（网络地址转换）主要有两种方式：</p><ul><li>系统内置的 Internet 共享功能（基于PF，自动配置）</li><li>PF（Packet Filter）防火墙（手动配置）</li></ul><p>由于 Internet 共享功能无法满足需求，因此选择了 PF 防火墙手动配置的方式来实现 Wi-Fi 网络共享。</p><h2 id="要求">要求</h2><ol><li>USB 网卡（以太网适配器）连接到没有网络的机器。</li><li>MacBook 连接到 Wi-Fi 网络。</li><li>网线</li></ol><h2 id="步骤">步骤</h2><h3 id="配置网络接口">配置网络接口</h3><ol><li>使用网线将 Mac 连接的 USB 网卡连接到没有网络的机器。</li><li>设备网络配置：<ul><li>IP: 192.168.100.2/24</li><li>Gateway: 192.168.100.1</li><li>DNS: 223.5.5.5</li></ul></li><li>MacBook网络配置：<ul><li>IP: 192.168.100.1/24</li></ul></li></ol><h3 id="配置-PF-防火墙-NAT-转发">配置 PF 防火墙 NAT 转发</h3><ol><li><p>确认内外网接口名称。在 macOS 终端执行 <code>ifconfig</code>，找到：</p><ul><li>外网接口（Wi-Fi）：一般是 <code>en0</code>，<code>inet</code> 是 Wi-Fi 的 IP（比如 <code>192.168.1.x</code>）</li><li>内网接口（USB 网卡）：inet 是 <code>192.168.100.1</code>，名字一般是 <code>en5</code></li></ul></li><li><p>开启 IP 转发</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 临时开启（重启失效）</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> sysctl</span><span style="color:#D19A66"> -w</span><span style="color:#98C379"> net.inet.ip.forwarding=</span><span style="color:#D19A66">1</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 永久开启（重启不失效）</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># echo "net.inet.ip.forwarding=1" | sudo tee -a /etc/sysctl.conf</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>创建 PF NAT 规则</p><p>创建一个临时的 PF 配置文件 <code>nat.conf</code>，内容如下：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># NAT规则：将192.168.100.0/24网段的流量通过Wi-Fi转发</span></span><span class="line"><span style="color:#61AFEF">nat</span><span style="color:#98C379"> on</span><span style="color:#98C379"> en0</span><span style="color:#98C379"> from</span><span style="color:#98C379"> 192.168.100.0/24</span><span style="color:#98C379"> to</span><span style="color:#98C379"> any</span><span style="color:#ABB2BF"> -> (en0)</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 允许内网流量转发</span></span><span class="line"><span style="color:#61AFEF">pass</span><span style="color:#98C379"> in</span><span style="color:#98C379"> on</span><span style="color:#98C379"> en5</span><span style="color:#98C379"> from</span><span style="color:#98C379"> 192.168.100.0/24</span><span style="color:#98C379"> to</span><span style="color:#98C379"> any</span></span><span class="line"><span style="color:#61AFEF">pass</span><span style="color:#98C379"> out</span><span style="color:#98C379"> on</span><span style="color:#98C379"> en0</span><span style="color:#98C379"> from</span><span style="color:#98C379"> 192.168.100.0/24</span><span style="color:#98C379"> to</span><span style="color:#98C379"> any</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>加载规则并验证</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 停止PF（如果正在运行）</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> pfctl</span><span style="color:#D19A66"> -d</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 清空现有规则</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> pfctl</span><span style="color:#D19A66"> -F</span><span style="color:#98C379"> all</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 启用PF并加载规则</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> pfctl</span><span style="color:#D19A66"> -e</span><span style="color:#D19A66"> -f</span><span style="color:#98C379"> ./nat.conf</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 验证规则（能看到NAT规则即正常）</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> pfctl</span><span style="color:#D19A66"> -s</span><span style="color:#98C379"> nat</span></span></code></pre></td></tr></tbody></table></figure></li></ol><p>到这里，设备应该就可以访问互联网了。</p><p>鉴于这种方法并非最佳解决方案，不建议长期使用，因此我也不提供永久修改系统的方案。建议在有条件的情况下，给设备加一台 Wi-Fi 路由器做无线桥接会稳一些。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/macos-pf-nat</id>
    <link href="https://www.xrgzs.top/posts/macos-pf-nat"/>
    <published>2026-04-10T00:51:46.000Z</published>
    <summary>
      <![CDATA[<p>由于要将 MacBook 的 Wi-Fi 网络共享给一台没有网络的机器，使用网络共享功能发现无法灵活配置IP地址。由于机器不方便使用 DHCP、更改 IP，因此无法使用。</p>
<p>通过搜索得知，在 macOS 上配置网卡 NAT（网络地址转换）主要有两种方式：</p>]]>
    </summary>
    <title>macOS 手动 PF 共享网络</title>
    <updated>2026-04-10T00:51:46.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="服务器" scheme="https://www.xrgzs.top/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    <category term="主板" scheme="https://www.xrgzs.top/tags/%E4%B8%BB%E6%9D%BF/"/>
    <category term="BIOS" scheme="https://www.xrgzs.top/tags/bios/"/>
    <content>
      <![CDATA[<p>最近狗子在折腾一台使用了超微（Supermicro）服务器主板 X9SCL 的设备。</p><p><img src="./supermicro-x9scl-bios/server.jpeg" alt=""></p><p>由于自带的系统过老，无法跟上时代的发展，为了提高设备的利用率，让旧设备焕发新生机，准备备份原有系统以后重装 PVE 虚拟化系统（<s>原系统也是古董 Linux 2.6 的 PVE 内核</s>）。</p><p>在使用过程中发现无法进入 AMI BIOS，卡在 AB 蓝屏界面。经过一番折腾，以及群友的帮助，终于解决了这个问题。</p><h2 id="现象">现象</h2><p>开机按 Delete 键能够进入 AMI BIOS 设置，但是卡 AB 蓝屏界面，此时键盘按Num Lock、Caps Lock 键均无反应，说明是死机了：</p><p><img src="./supermicro-x9scl-bios/blue-ab.jpeg" alt=""></p><p>但是 SATA 系统能正常启动，U盘也能正常启动，说明内存和处理器均正常。</p><h2 id="折腾过程">折腾过程</h2><h3 id="移除硬盘">移除硬盘</h3><p>怀疑是硬盘有问题，但是移除硬盘后依然无法进入 AMI BIOS。</p><p>查询主板说明书：</p><p><a href="https://www.supermicro.com/manuals/motherboard/C202_C204/MNL-1270.pdf">https://www.supermicro.com/manuals/motherboard/C202_C204/MNL-1270.pdf</a></p><h3 id="禁用-Watch-Dog">禁用 Watch Dog</h3><p>怀疑是硬件 Watch Dog 导致的，尝试禁用 Watch Dog：</p><p><img src="./supermicro-x9scl-bios/disable-watch-dog.jpeg" alt=""></p><p><img src="./supermicro-x9scl-bios/disable-watch-dog-2.jpeg" alt=""></p><p>没用</p><h3 id="禁用-iPMI-BMC">禁用 iPMI/BMC</h3><p>首先我这个主板没有 iPMI/BMC 芯片：</p><p><img src="./supermicro-x9scl-bios/no-ipmi.jpeg" alt=""></p><p>怀疑是卡BUG，尝试禁用 iPMI/BMC：</p><p><img src="./supermicro-x9scl-bios/disable-ipmi.jpeg" alt=""></p><p>没用</p><h3 id="电池放电">电池放电</h3><p>经过进一步查找，发现是 BIOS 有问题：</p><p><img src="./supermicro-x9scl-bios/bios-issue.jpeg" alt=""></p><p>最终通过扣掉电池放电，将BIOS日期恢复到了默认的2015年，解决了问题。</p><p><img src="./supermicro-x9scl-bios/bios-date-2015.jpeg" alt=""></p><h2 id="解决方法">解决方法</h2><h3 id="方法一：扣电池">方法一：扣电池</h3><ol><li>断电，打开机箱，找到主板上的电池，扣掉电池放电，等待几分钟后重新安装电池。</li><li>开机按 Delete 键进入 BIOS，检查日期是否恢复到默认的2015年，如果是，则保存退出 BIOS，重新进入 BIOS，检查是否能够正常进入 BIOS界面。</li></ol><h3 id="方法二：EFI-Shell-设置时间">方法二：EFI Shell 设置时间</h3><ol><li><p>开机按 F12 还有 Ctrl + S 键进入 Boot Menu，然后选择 EFI Shell。</p></li><li><p>在 EFI Shell 中输入以下命令设置时间：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">date</span><span style="color:#98C379"> 01/01/2018</span></span></code></pre></td></tr></tbody></table></figure></li></ol><h2 id="修复方案">修复方案</h2><p>更新 BIOS。根据最新的 Release Notes，已经修复了这个问题：</p><p><a href="https://www.supermicro.com/en/support/resources/downloadcenter/firmware/MBD-X9SCL/BIOS">https://www.supermicro.com/en/support/resources/downloadcenter/firmware/MBD-X9SCL/BIOS</a></p><blockquote><p>Fixes</p><ol><li>Fixed problem of system hanging when entering setup with system date year 2021.</li></ol></blockquote>]]>
    </content>
    <id>https://www.xrgzs.top/posts/supermicro-x9scl-bios</id>
    <link href="https://www.xrgzs.top/posts/supermicro-x9scl-bios"/>
    <published>2026-04-08T15:30:50.000Z</published>
    <summary>
      <![CDATA[<p>最近狗子在折腾一台使用了超微（Supermicro）服务器主板 X9SCL 的设备。</p>
<p><img src="./supermicro-x9scl-bios/server.jpeg" alt=""></p>
<p>由于自带的系统过老，无法跟上时代的发展，为了提高设]]>
    </summary>
    <title>超微 X9SCL 无法进入 BIOS 解决方法</title>
    <updated>2026-04-08T15:30:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="Armbian" scheme="https://www.xrgzs.top/tags/armbian/"/>
    <category term="存储" scheme="https://www.xrgzs.top/tags/%E5%AD%98%E5%82%A8/"/>
    <content>
      <![CDATA[<p>最近在折腾配置 N1 上的 Armbian，想实现插入 USB 移动硬盘自动挂载的功能。经过一番摸索，终于找到了一套稳定可靠的方案，特此记录下来，供大家参考。</p><p>AI 生成的 Armbian USB 移动硬盘自动挂载完全指南。</p><blockquote><p>基于 <strong>udev + systemd</strong> 的稳定热插拔方案，同时覆盖 NTFS 与 exFAT 两种文件系统。<br>适用 Armbian current / edge（内核 ≥ 5.15）。</p></blockquote><h2 id="前置问题：驱动该用哪个？">前置问题：驱动该用哪个？</h2><p>在开始配置之前，先把驱动问题说清楚，因为 NTFS 和 exFAT 的情况截然不同。</p><h3 id="NTFS：ntfs3-vs-ntfs-3g">NTFS：ntfs3 vs ntfs-3g</h3><p>Linux 5.15 起内核自带 <strong>ntfs3</strong> 驱动（Paragon 贡献），Armbian 已安装的 <code>ntfs-3g</code> 是早年 FUSE 方案。</p><table><thead><tr><th>维度</th><th>ntfs3（内核驱动）</th><th>ntfs-3g（FUSE 驱动）</th></tr></thead><tbody><tr><td>运行层</td><td>内核空间</td><td>用户空间（FUSE）</td></tr><tr><td>读写性能</td><td>快约 20-25%</td><td>较慢（上下文切换开销）</td></tr><tr><td>稳定性</td><td>成熟（5.15 后持续完善）</td><td>久经考验（2007 年起）</td></tr><tr><td><code>Type=</code> 参数</td><td><code>ntfs3</code></td><td><code>ntfs-3g</code></td></tr><tr><td>是否需要额外安装</td><td>否（内核自带）</td><td>是（已安装）</td></tr></tbody></table><p><strong>结论：挂载改用 <code>ntfs3</code>，但保留 <code>ntfs-3g</code> 安装。</strong><br><code>ntfs-3g</code> 包含 <code>ntfsfix</code> 工具，在硬盘出现 dirty 标记时必须用它修复。工具留着，驱动换掉。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 确认 ntfs3 模块存在（应输出 ✅）</span></span><span class="line"><span style="color:#ABB2BF">[ </span><span style="color:#56B6C2">-d</span><span style="color:#98C379"> "/lib/modules/$(</span><span style="color:#61AFEF">uname</span><span style="color:#D19A66"> -r</span><span style="color:#98C379">)/kernel/fs/ntfs3"</span><span style="color:#ABB2BF"> ] </span><span style="color:#56B6C2">\</span></span><span class="line"><span style="color:#ABB2BF">  &#x26;&#x26; </span><span style="color:#56B6C2">echo</span><span style="color:#98C379"> "✅ ntfs3 available"</span><span style="color:#56B6C2"> \</span></span><span class="line"><span style="color:#ABB2BF">  || </span><span style="color:#56B6C2">echo</span><span style="color:#98C379"> "❌ 内核不含 ntfs3，请改用 ntfs-3g"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="exFAT：内核-exfat-vs-exfatprogs">exFAT：内核 exfat vs exfatprogs</h3><p>同理，Linux 5.4 起内核已内置 exFAT 驱动。<code>exfatprogs</code>（或旧版 <code>exfat-utils</code>）是用户空间工具包，提供 <code>mkfs.exfat</code>、<code>fsck.exfat</code> 等命令，<strong>挂载本身不需要它</strong>，但建议保留。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 确认内核 exfat 模块</span></span><span class="line"><span style="color:#61AFEF">modinfo</span><span style="color:#98C379"> exfat</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> filename</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 安装工具包（如果还没装）</span></span><span class="line"><span style="color:#61AFEF">apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> exfatprogs</span></span></code></pre></td></tr></tbody></table></figure><p><strong>挂载 exFAT 直接用 <code>Type=exfat</code>，无需 FUSE 层。</strong></p><h2 id="方案架构">方案架构</h2><p>整个方案由两个部件组成：</p><figure class="highlight text"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span>USB 插入/拔出</span></span><span class="line"><span>    │</span></span><span class="line"><span>    ├─ udev rule 捕获块设备事件（by UUID）</span></span><span class="line"><span>    │       ↓</span></span><span class="line"><span>    ├─ 触发 systemctl start/stop</span></span><span class="line"><span>    │       ↓</span></span><span class="line"><span>    └─ systemd .mount unit 执行挂载/卸载</span></span><span class="line"><span>            │</span></span><span class="line"><span>            ├─ nofail          → 开机无盘不阻塞</span></span><span class="line"><span>            └─ DefaultDependencies=no → 不产生循环依赖</span></span></code></pre></td></tr></tbody></table></figure><h2 id="Step-1：确认硬盘-UUID">Step 1：确认硬盘 UUID</h2><p><strong>始终用 UUID，不用 <code>/dev/sdX</code>。</strong> 设备名随插入顺序变化，UUID 是硬盘的永久标识。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 插入硬盘后执行</span></span><span class="line"><span style="color:#61AFEF">lsblk</span><span style="color:#D19A66"> -f</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 或用 blkid 获取完整信息</span></span><span class="line"><span style="color:#61AFEF">blkid</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#D19A66"> -E</span><span style="color:#98C379"> "ntfs|exfat"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 示例输出：</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># /dev/sda1: UUID="A1B2C3D4E5F67890" TYPE="ntfs" ...</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># /dev/sda1: UUID="1A2B-3C4D" TYPE="exfat" ...</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p><strong>注意</strong>：NTFS 的 UUID 通常是 16 位十六进制（如 <code>A1B2C3D4E5F67890</code>），<br>exFAT 的 UUID 通常是 8 位带短横线（如 <code>1A2B-3C4D</code>），两者格式不同，注意区分。</p></blockquote><h2 id="Step-2：创建挂载点">Step 2：创建挂载点</h2><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> /mnt/usbdisk</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p><strong>挂载点命名与 unit 文件名必须对应</strong>：<br><code>/mnt/usbdisk</code> → unit 文件名为 <code>mnt-usbdisk.mount</code>（斜杠换短横线）。<br>如果你改成 <code>/mnt/my_disk</code>，unit 文件名就要改成 <code>mnt-my_disk.mount</code>。</p></blockquote><h2 id="Step-3：编写-systemd-mount-unit">Step 3：编写 systemd .mount unit</h2><p>根据你的文件系统类型，选择对应的配置。</p><h3 id="NTFS-版本">NTFS 版本</h3><figure class="highlight ini"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># /etc/systemd/system/mnt-usbdisk.mount</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Unit]</span></span><span class="line"><span style="color:#C678DD">Description</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">USB NTFS External Disk</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Mount]</span></span><span class="line"><span style="color:#C678DD">What</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">/dev/disk/by-uuid/A1B2C3D4E5F67890</span></span><span class="line"><span style="color:#C678DD">Where</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">/mnt/usbdisk</span></span><span class="line"><span style="color:#C678DD">Type</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">ntfs3</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Install]</span></span><span class="line"><span style="color:#C678DD">WantedBy</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">multi-user.target</span></span></code></pre></td></tr></tbody></table></figure><h3 id="exFAT-版本">exFAT 版本</h3><figure class="highlight ini"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># /etc/systemd/system/mnt-usbdisk.mount</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Unit]</span></span><span class="line"><span style="color:#C678DD">Description</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">USB exFAT External Disk</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Mount]</span></span><span class="line"><span style="color:#C678DD">What</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">/dev/disk/by-uuid/1A2B-3C4D</span></span><span class="line"><span style="color:#C678DD">Where</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">/mnt/usbdisk</span></span><span class="line"><span style="color:#C678DD">Type</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">exfat</span></span><span class="line"><span style="color:#C678DD">Options</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">defaults,noatime,nofail,</span><span style="color:#C678DD">fmask</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">0000,</span><span style="color:#C678DD">dmask</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">0000,</span><span style="color:#C678DD">x-systemd.device-timeout</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">5</span></span><span class="line"><span style="color:#C678DD">TimeoutSec</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">30s</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Install]</span></span><span class="line"><span style="color:#C678DD">WantedBy</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">multi-user.target</span></span></code></pre></td></tr></tbody></table></figure><h2 id="Step-4：编写-udev-热插拔规则">Step 4：编写 udev 热插拔规则</h2><p>systemd mount unit 只处理开机挂载。要实现<strong>插入自动挂载、拔出自动卸载、重插后再次挂载</strong>，需要 udev 规则来监听块设备事件。</p><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># /etc/udev/rules.d/99-usb-mount.rules</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 插入时挂载（替换 UUID 为你的实际值）</span></span><span class="line"><span style="color:#E06C75">SUBSYSTEM</span><span style="color:#56B6C2">=</span><span style="color:#98C379">="block",</span><span style="color:#E06C75"> ACTION</span><span style="color:#56B6C2">=</span><span style="color:#98C379">="add",</span><span style="color:#61AFEF"> ENV</span><span style="color:#98C379">&#123;ID_FS_UUID&#125;==</span><span style="color:#61AFEF">"YOUR-UUID-HERE"</span><span style="color:#61AFEF">,</span><span style="color:#98C379"> TAG+="systemd",</span><span style="color:#98C379"> ENV&#123;SYSTEMD_WANTS&#125;="mnt-usbdisk.mount"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 拔出时卸载（防止下次插入时出现 dirty 标记）</span></span><span class="line"><span style="color:#E06C75">SUBSYSTEM</span><span style="color:#56B6C2">=</span><span style="color:#98C379">="block",</span><span style="color:#E06C75"> ACTION</span><span style="color:#56B6C2">=</span><span style="color:#98C379">="remove",</span><span style="color:#61AFEF"> ENV</span><span style="color:#98C379">&#123;ID_FS_UUID&#125;==</span><span style="color:#61AFEF">"YOUR-UUID-HERE"</span><span style="color:#61AFEF">,</span><span style="color:#98C379"> RUN+="/usr/bin/systemctl stop mnt-usbdisk.mount"</span></span></code></pre></td></tr></tbody></table></figure><blockquote><p><strong>两处 UUID 都要替换</strong>，且必须与 mount unit 中的完全一致（大小写敏感）。</p><p>udev 通过 <code>ENV{ID_FS_UUID}</code> 匹配，即使同时插多块 USB 盘也不会混淆。</p></blockquote><h2 id="Step-5：启用配置">Step 5：启用配置</h2><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 重新加载 systemd 配置</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> daemon-reload</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 重新加载 udev 规则（无需重启）</span></span><span class="line"><span style="color:#61AFEF">udevadm</span><span style="color:#98C379"> control</span><span style="color:#D19A66"> --reload-rules</span></span><span class="line"><span style="color:#61AFEF">udevadm</span><span style="color:#98C379"> trigger</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 手动测试挂载</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> start</span><span style="color:#98C379"> mnt-usbdisk.mount</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> mnt-usbdisk.mount</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 确认挂载成功</span></span><span class="line"><span style="color:#61AFEF">df</span><span style="color:#D19A66"> -h</span><span style="color:#98C379"> /mnt/usbdisk</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 设置开机自动挂载（硬盘存在时生效）</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> mnt-usbdisk.mount</span></span></code></pre></td></tr></tbody></table></figure><h2 id="Step-6：三种场景验证">Step 6：三种场景验证</h2><h3 id="场景一：开机不插盘">场景一：开机不插盘</h3><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">reboot</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 开机后检查，状态应为 inactive (dead)，而非 failed</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> mnt-usbdisk.mount</span></span></code></pre></td></tr></tbody></table></figure><h3 id="场景二：开机后插盘">场景二：开机后插盘</h3><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 插入硬盘，等 2 秒后验证</span></span><span class="line"><span style="color:#61AFEF">ls</span><span style="color:#98C379"> /mnt/usbdisk</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 实时查看 udev 触发日志</span></span><span class="line"><span style="color:#61AFEF">journalctl</span><span style="color:#D19A66"> -f</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#D19A66"> -E</span><span style="color:#98C379"> "usb|usbdisk|ntfs|exfat"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="场景三：掉盘模拟（拔出后重插）">场景三：掉盘模拟（拔出后重插）</h3><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 开启实时日志监控</span></span><span class="line"><span style="color:#61AFEF">journalctl</span><span style="color:#D19A66"> -f</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 拔出硬盘 → 等 3 秒 → 重新插入</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 日志中应先出现 stop 再出现 start</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 之后 /mnt/usbdisk 应再次可访问</span></span><span class="line"><span style="color:#61AFEF">ls</span><span style="color:#98C379"> /mnt/usbdisk</span></span></code></pre></td></tr></tbody></table></figure><h2 id="常见问题排查">常见问题排查</h2><h3 id="问题一：挂载失败，日志显示-“dirty”">问题一：挂载失败，日志显示 “dirty”</h3><p>Windows 未安全弹出，或 Linux 上次拔盘前未卸载，NTFS 会被标记为 dirty，内核 ntfs3 默认拒绝挂载。exFAT 无此问题。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 用 ntfs-3g 工具包修复（这就是保留它的意义）</span></span><span class="line"><span style="color:#61AFEF">ntfsfix</span><span style="color:#98C379"> /dev/sdX1</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 修复后重新挂载</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> start</span><span style="color:#98C379"> mnt-usbdisk.mount</span></span></code></pre></td></tr></tbody></table></figure><h3 id="问题二：udev-规则不触发">问题二：udev 规则不触发</h3><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 检查 udev 是否识别到 UUID</span></span><span class="line"><span style="color:#61AFEF">udevadm</span><span style="color:#98C379"> info</span><span style="color:#D19A66"> --name=/dev/sda1</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> ID_FS_UUID</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 手动测试规则匹配</span></span><span class="line"><span style="color:#61AFEF">udevadm</span><span style="color:#98C379"> test</span><span style="color:#ABB2BF"> $(</span><span style="color:#61AFEF">udevadm</span><span style="color:#98C379"> info</span><span style="color:#D19A66"> -q</span><span style="color:#98C379"> path</span><span style="color:#D19A66"> -n</span><span style="color:#98C379"> /dev/sda1</span><span style="color:#ABB2BF">) 2>&#x26;1 | </span><span style="color:#61AFEF">grep</span><span style="color:#D19A66"> -E</span><span style="color:#98C379"> "uuid|RUN"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="问题三：确认当前实际使用的驱动">问题三：确认当前实际使用的驱动</h3><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 查看挂载点使用的文件系统类型</span></span><span class="line"><span style="color:#61AFEF">mount</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> usbdisk</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 或</span></span><span class="line"><span style="color:#61AFEF">findmnt</span><span style="color:#98C379"> /mnt/usbdisk</span></span></code></pre></td></tr></tbody></table></figure><h2 id="参考">参考</h2><p><a href="https://www.freedesktop.org/software/systemd/man/systemd.mount.html">https://www.freedesktop.org/software/systemd/man/systemd.mount.html</a></p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/armbian-automount</id>
    <link href="https://www.xrgzs.top/posts/armbian-automount"/>
    <published>2026-03-29T15:00:00.000Z</published>
    <summary>
      <![CDATA[<p>最近在折腾配置 N1 上的 Armbian，想实现插入 USB 移动硬盘自动挂载的功能。经过一番摸索，终于找到了一套稳定可靠的方案，特此记录下来，供大家参考。</p>
<p>AI 生成的 Armbian USB 移动硬盘自动挂载完全指南。</p>
<blockquote>]]>
    </summary>
    <title>Armbian 自动挂载 USB 移动硬盘</title>
    <updated>2026-04-04T10:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="CUPS" scheme="https://www.xrgzs.top/tags/cups/"/>
    <category term="HomeLab" scheme="https://www.xrgzs.top/tags/homelab/"/>
    <content>
      <![CDATA[<p>由于最近有打印文件的需求，打印机和电脑的布局不合理，导致出现位置争抢的情况，索性折腾起共享打印机的方案。</p><p>本文使用的打印机为 EPSON L383 喷墨打印机，虽然带扫描功能，但是不好用，不如手机当高拍仪扫描打印。其实一开始采购打印机的时候就考虑过要不要带网络，但认为使用共享的方式能解决问题，后面发现还是太天真了，这个远程打印折腾起来巨麻烦，尤其是这种冷门型号的打印机。之前由于小时候技术不佳，Linux玩不明白，只会使用 Windows 共享，导致专门弄了台 D425 装 XP 负责远程打印，效果也不好。现在又开始重操旧业，总算是给它折腾明白了。</p><h2 id="环境">环境</h2><p>本次部署由于打印机附近存在 7x24h 工作的PVE AIO服务器，因此基于 Linux VM实现。后期如果不使用服务器，会考虑 IPTV 盒子刷 Armbian——因为打印机驱动支持 ARM64 版 Linux 驱动。</p><ul><li>PVE 通过 USB 延长线连接到打印机。</li><li>USB 接口直通到 Linux VM。</li><li>客户端通过 Wi-Fi / VPN 接入到内网。</li></ul><h2 id="系统安装">系统安装</h2><p>这里系统直接使用 Ubuntu Cloud Images，我懒得安装 Ubuntu Server ISO。</p><h3 id="PVE-新建创建-Ubuntu-虚拟机">PVE 新建创建 Ubuntu 虚拟机</h3><p>登录到 PVE 的控制台，打开Shell，复制粘贴内容：</p><p>下载Daily构建的虚拟硬盘文件：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://cloud-images.ubuntu.com/minimal/daily/noble/current/noble-minimal-cloudimg-amd64.img</span></span><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/minimal/daily/noble/current/noble-minimal-cloudimg-amd64.img</span></span></code></pre></td></tr></tbody></table></figure><p>创建虚拟机（2H2G，实际使用分512M就够了）：</p><figure class="highlight bash"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 此处改为你的存储位置，可用 pvesm status 查看</span></span><span class="line"><span style="color:#E06C75">STORAGE_ID</span><span style="color:#56B6C2">=</span><span style="color:#98C379">local</span></span><span class="line"><span style="color:#E06C75">IMG_PATH</span><span style="color:#56B6C2">=</span><span style="color:#E06C75">$PWD</span><span style="color:#98C379">/noble-minimal-cloudimg-amd64.img</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 定义VMID虚拟机ID，不得重复</span></span><span class="line"><span style="color:#E06C75">VM_ID</span><span style="color:#56B6C2">=</span><span style="color:#98C379">500</span></span><span class="line"><span style="color:#E06C75">ROOT_PASS</span><span style="color:#56B6C2">=</span><span style="color:#98C379">Cups@123!</span></span><span class="line"><span style="color:#E06C75">IPCFG</span><span style="color:#56B6C2">=</span><span style="color:#E06C75">ip</span><span style="color:#56B6C2">=</span><span style="color:#98C379">10.0.5.5/24,</span><span style="color:#E06C75">gw</span><span style="color:#56B6C2">=</span><span style="color:#98C379">10.0.0.1</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 一行命令创建虚拟机</span></span><span class="line"><span style="color:#61AFEF">qm</span><span style="color:#98C379"> create</span><span style="color:#E06C75"> $VM_ID</span><span style="color:#D19A66"> --name</span><span style="color:#98C379"> CUPS</span><span style="color:#D19A66"> --cpu</span><span style="color:#98C379"> x86-64-v2-AES</span><span style="color:#D19A66"> --cores</span><span style="color:#D19A66"> 2</span><span style="color:#D19A66"> --memory</span><span style="color:#D19A66"> 2048</span><span style="color:#D19A66"> --net0</span><span style="color:#98C379"> virtio,bridge=vmbr0</span><span style="color:#D19A66"> --scsihw</span><span style="color:#98C379"> virtio-scsi-single</span><span style="color:#D19A66"> --serial0</span><span style="color:#98C379"> socket</span><span style="color:#D19A66"> --vga</span><span style="color:#98C379"> serial0</span><span style="color:#D19A66"> --ostype</span><span style="color:#98C379"> l26</span><span style="color:#D19A66"> --ide2</span><span style="color:#E06C75"> $STORAGE_ID</span><span style="color:#98C379">:cloudinit</span><span style="color:#D19A66"> --scsi0</span><span style="color:#E06C75"> $STORAGE_ID</span><span style="color:#98C379">:0,discard=on,format=qcow2,import-from=</span><span style="color:#E06C75">$IMG_PATH</span><span style="color:#98C379">,iothread=1,ssd=</span><span style="color:#D19A66">1</span><span style="color:#D19A66"> --boot</span><span style="color:#98C379"> order=scsi0</span><span style="color:#D19A66"> --ciupgrade</span><span style="color:#D19A66"> 0</span><span style="color:#D19A66">  --ciuser</span><span style="color:#98C379"> root</span><span style="color:#D19A66"> --cipassword</span><span style="color:#ABB2BF"> $(</span><span style="color:#61AFEF">openssl</span><span style="color:#98C379"> passwd</span><span style="color:#D19A66"> -6</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$ROOT_PASS</span><span style="color:#98C379">"</span><span style="color:#ABB2BF">) </span><span style="color:#D19A66">--ipconfig0</span><span style="color:#98C379"> ip=</span><span style="color:#E06C75">$IPCFG</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 扩容得单独来一下</span></span><span class="line"><span style="color:#61AFEF">qm</span><span style="color:#98C379"> disk</span><span style="color:#98C379"> resize</span><span style="color:#E06C75"> $VM_ID</span><span style="color:#98C379"> scsi0</span><span style="color:#98C379"> 16G</span></span></code></pre></td></tr></tbody></table></figure><p>系统就装好了。</p><h3 id="直通USB">直通USB</h3><p>在网页中设置，手册建议先安装驱动，再配置直通。这里我选择绑定USB插口。</p><p><img src="./cups/image-20260226073158858.png" alt=""></p><h3 id="登录虚拟机">登录虚拟机</h3><p>开启虚拟机，然后立马打开 xterm，通过刚才设置的密码登录虚拟机。</p><p><img src="./cups/image-20260226073346599.png" alt=""></p><h3 id="卸载自带软件">卸载自带软件</h3><p>由于是通过 PVE 控制台直接操作 Linux 容器，因此不需要内置的 SSH，留着反倒会造成安全隐患，因此删除。另外这个 Ubuntu Minimal 居然带 snap！！！</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> auto-remove</span><span style="color:#98C379"> ssh</span><span style="color:#98C379"> openssh</span><span style="color:#E5C07B">*</span><span style="color:#98C379"> snapd</span></span></code></pre></td></tr></tbody></table></figure><h3 id="更换软件源">更换软件源</h3><p>默认的 Ubuntu 软件源下载贼慢，这里更换为国内源。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> sed</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> 's/\(archive\|security\)\.ubuntu\.com/mirrors.aliyun.com/g'</span><span style="color:#98C379"> /etc/apt/sources.list</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> sed</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> 's/\(archive\|security\)\.ubuntu\.com/mirrors.aliyun.com/g'</span><span style="color:#98C379"> /etc/apt/sources.list.d/</span><span style="color:#E5C07B">*</span></span></code></pre></td></tr></tbody></table></figure><h3 id="执行系统更新">执行系统更新</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> dist-upgrade</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装组件">安装组件</h2><h3 id="安装-CUPS">安装 CUPS</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> cups</span><span style="color:#98C379"> cups-bsd</span><span style="color:#98C379"> libcupsimage2t64</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># sudo apt install cups cup* libcups* printer*</span></span></code></pre></td></tr></tbody></table></figure><ul><li>vim：由于<s>自带的 vi 和 nano</s> （Minimal一个都不带）个人用不习惯，这里安装了 vim，后续会使用 vim 编辑配置。</li><li>libcupsimage2t64：原本为 libcupsfilters1 依赖，但 Ubuntu 24.04 LTS+ 没这个包，不装这个会造成打印报错在 GhostScript 渲染的过滤阶段。Debian 倒是一直都有这个，所以尽可能用 Debian。</li></ul><h3 id="安装打印机驱动程序">安装打印机驱动程序</h3><p>如果是较老的打印机，应该有通用驱动，<code> sudo apt install printer*</code> 全部装完。</p><p>我这边是爱普生打印机，没有通用的，就到 <code>https://download-center.epson.com/</code> 下载打印机驱动程序，选择Linux版本。选择“Epson Inkjet Printer Driver (ESC/P) for Linux”，然后复制下载链接，我这里已经复制出来了，不知道能存活多久。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> https://download-center.epson.com.cn/f/module/81976474-cb4d-4b75-a0d8-ebc9ebde70d0/epson-inkjet-printer-201601w_1.0.1-1_amd64.deb</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> dpkg</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> epson-inkjet-printer-201601w_1.0.1-1_amd64.deb</span></span></code></pre></td></tr></tbody></table></figure><p>另外手册也下载阅读一下，那里面写着最佳环境是 Ubuntu 24.04 LTS 和 Fedora 42，所以我选择了前者。其实一开始是用 Linux Mint 测试了下的，没问题才选的，结果换成 Ubuntu 24.04 LTS 少装一个依赖。</p><h2 id="配置服务">配置服务</h2><h3 id="配置-CUPS">配置 CUPS</h3><p>这里不编辑配置文件了，直接通过 <code>cupsctl</code> 配置外部访问，和那个桌面环境上的 <a href="https://github.com/OpenPrinting/system-config-printer">system-config-printer</a> 一样。现在 CUPS 这么好用得感谢这个 OpenPrinting 项目。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">~# cupsctl --help</span></span><span class="line"><span style="color:#61AFEF">Usage:</span><span style="color:#98C379"> cupsctl</span><span style="color:#ABB2BF"> [options] [param</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">value ... paramN</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">valueN]</span></span><span class="line"><span style="color:#61AFEF">Options:</span></span><span class="line"><span style="color:#61AFEF">-E</span><span style="color:#98C379">                      Encrypt</span><span style="color:#98C379"> the</span><span style="color:#98C379"> connection</span><span style="color:#98C379"> to</span><span style="color:#98C379"> the</span><span style="color:#98C379"> server</span></span><span class="line"><span style="color:#61AFEF">-h</span><span style="color:#98C379"> server[:port]</span><span style="color:#98C379">        Connect</span><span style="color:#98C379"> to</span><span style="color:#98C379"> the</span><span style="color:#98C379"> named</span><span style="color:#98C379"> server</span><span style="color:#98C379"> and</span><span style="color:#98C379"> port</span></span><span class="line"><span style="color:#61AFEF">-U</span><span style="color:#98C379"> username</span><span style="color:#98C379">             Specify</span><span style="color:#98C379"> username</span><span style="color:#98C379"> to</span><span style="color:#98C379"> use</span><span style="color:#98C379"> for</span><span style="color:#98C379"> authentication</span></span><span class="line"><span style="color:#61AFEF">--[no-]debug-logging</span><span style="color:#98C379">    Turn</span><span style="color:#98C379"> debug</span><span style="color:#98C379"> logging</span><span style="color:#98C379"> on/off</span></span><span class="line"><span style="color:#61AFEF">--[no-]remote-admin</span><span style="color:#98C379">     Turn</span><span style="color:#98C379"> remote</span><span style="color:#98C379"> administration</span><span style="color:#98C379"> on/off</span></span><span class="line"><span style="color:#61AFEF">--[no-]remote-any</span><span style="color:#98C379">       Allow/prevent</span><span style="color:#98C379"> access</span><span style="color:#98C379"> from</span><span style="color:#98C379"> the</span><span style="color:#98C379"> Internet</span></span><span class="line"><span style="color:#61AFEF">--[no-]share-printers</span><span style="color:#98C379">   Turn</span><span style="color:#98C379"> printer</span><span style="color:#98C379"> sharing</span><span style="color:#98C379"> on/off</span></span><span class="line"><span style="color:#61AFEF">--[no-]user-cancel-any</span><span style="color:#98C379">  Allow/prevent</span><span style="color:#98C379"> users</span><span style="color:#98C379"> to</span><span style="color:#98C379"> cancel</span><span style="color:#98C379"> any</span><span style="color:#98C379"> job</span></span></code></pre></td></tr></tbody></table></figure><p>执行以下命令，如果：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cupsctl</span><span style="color:#D19A66"> --remote-admin</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> restart</span><span style="color:#98C379"> cups</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 此时访问会变成已禁止</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cupsctl</span><span style="color:#D19A66"> --remote-any</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> restart</span><span style="color:#98C379"> cups</span></span></code></pre></td></tr></tbody></table></figure><p>使用浏览器访问 <code>https://10.0.5.5:631/admin</code> （这里改成实际的IP）直接进入管理页面，用户名密码同 Linux。</p><p>点击 Printers -&gt; Add Printer，进入如下步骤：</p><p><img src="./cups/image-20260226074034599.png" alt=""></p><ol><li><p>选择 Local Printers 下面的你的型号打印机，点击 Continue。</p><p><img src="./cups/image-20260226161124023.png" alt="image-20260226161124023"></p></li><li><p>设置一下打印机的名字（Name），建议不要包含特殊符号（包括空格），不然后面通过 LPD 连需要你手动输入的时候人就麻了，然后勾选：Share This Printer，点击 Continue。</p><p><img src="./cups/image-20260226082328885.png" alt=""></p></li><li><p>选择对应打印机型号的型号（Model），点击 Add Printer。</p><p><img src="./cups/image-20260226082355114.png" alt=""></p></li><li><p>点击 Set Printer Options。</p><p><img src="./cups/image-20260226074451909.png" alt=""></p></li><li><p>一项一项点进去，调整默认配置，比如双面打印啥的（可惜没有）。</p><p><img src="./cups/image-20260226074513046.png" alt=""></p></li></ol><p>调整完后，我们将 A4 纸张放入打印机，点击页面导航栏的最后一个 Printers，点击进入刚才添加的打印机，找到第一个叫做 Maintenance 的下拉菜单，选择 Print Test Page。</p><p><img src="./cups/image-20260226074622076.png" alt=""></p><p><img src="./cups/image-20260226074652610.png" alt=""></p><p>测试页能够正常打印。</p><h3 id="配置-SAMBA">配置 SAMBA</h3><p><strong>由于SAMBA实测不好用，而且还一堆漏洞，实际上后面又给 SAMBA 删了。</strong></p><p>需要服务端安装 SAMBA，参考官方教程配置：</p><p><a href="https://wiki.samba.org/index.php/Setting_up_Samba_as_a_Print_Server">https://wiki.samba.org/index.php/Setting_up_Samba_as_a_Print_Server</a></p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> samba</span><span style="color:#98C379"> samba</span><span style="color:#98C379"> samba-common-bin</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> smbd</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> nmbd</span></span></code></pre></td></tr></tbody></table></figure><p>然后编辑：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> /etc/samba/smb.conf</span></span></code></pre></td></tr></tbody></table></figure><p>找到 <code>[global]</code>，在后面插入：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">rpcd_spoolss:idle_seconds</span><span style="color:#98C379">=300</span></span></code></pre></td></tr></tbody></table></figure><p>找到 <code>[printers]</code>，将：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>browseable = no</span></span><span class="line"><span>guest ok = no</span></span></code></pre></td></tr></tbody></table></figure><p>改成：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>browseable = yes</span></span><span class="line"><span>guest ok = yes</span></span></code></pre></td></tr></tbody></table></figure><p>保存，然后：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> restart</span><span style="color:#98C379"> smbd</span><span style="color:#98C379"> nmbd</span></span></code></pre></td></tr></tbody></table></figure><p>如果要让客户端（Windows）支持自动安装驱动，参考：<a href="http://cn.linux.vbird.org/linux_server/0370samba.php#server_printer">鸟哥的 Linux 私房菜 – SAMBA 服务器</a> 配置驱动共享，否则需要提前安装好驱动。</p><h3 id="配置-LPD">配置 LPD</h3><p>LPD是一个古董协议，没招了只能用这个。</p><p>正常情况下，直接执行下面的命令就行：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> cups-lpd.socket</span></span></code></pre></td></tr></tbody></table></figure><p>但实际上不好使，Ubuntu报错“Unit file cups-lpd.socket does not exist.”，估计是因为LPD不用鉴权就可以打印，存在安全风险就被Canonical 删了，只能手动创建。</p><ol><li><p>创建 Socket 文件。</p><p>我们需要告诉 systemd 监听哪个端口（LPD 默认为 515）。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> /etc/systemd/system/cups-lpd.socket</span></span></code></pre></td></tr></tbody></table></figure><p>输入：</p><figure class="highlight ini"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">[Unit]</span></span><span class="line"><span style="color:#C678DD">Description</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">CUPS LPD Server Socket</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Socket]</span></span><span class="line"><span style="color:#C678DD">ListenStream</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">515</span></span><span class="line"><span style="color:#C678DD">Accept</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">yes</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Install]</span></span><span class="line"><span style="color:#C678DD">WantedBy</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">sockets.target</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>创建 Service 文件</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> /etc/systemd/system/cups-lpd@.service</span></span></code></pre></td></tr></tbody></table></figure><p>输入：</p><figure class="highlight ini"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">[Unit]</span></span><span class="line"><span style="color:#C678DD">Description</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">CUPS LPD Server Service</span></span><span class="line"><span style="color:#C678DD">After</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">network.target</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Service]</span></span><span class="line"><span style="color:#C678DD">ExecStart</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">-/usr/lib/cups/daemon/cups-lpd -n -o </span><span style="color:#C678DD">job-sheets</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">none,none</span></span><span class="line"><span style="color:#C678DD">StandardInput</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">socket</span></span><span class="line"><span style="color:#C678DD">User</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">lp</span></span><span class="line"><span style="color:#C678DD">Group</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">lp</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">[Install]</span></span><span class="line"><span style="color:#C678DD">WantedBy</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">multi-user.target</span></span></code></pre></td></tr></tbody></table></figure><p><code>-n</code> ：表示不进行反向域名解析（加快速度）。</p><p><code>lp</code> ：打印机的标准用户组。</p></li><li><p>激活服务</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> daemon-reload</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> cups-lpd.socket</span></span></code></pre></td></tr></tbody></table></figure></li></ol><p>检查端口是否已开启：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">ss</span><span style="color:#D19A66"> -ant</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#98C379"> :515</span></span></code></pre></td></tr></tbody></table></figure><h2 id="Windows-客户端配置">Windows 客户端配置</h2><h3 id="IPP-协议">IPP 协议</h3><p>Windows 正常应该可以在设置里面搜索到打印机。<strong>不用安装驱动！！！不用安装驱动！！！不用安装驱动！！！</strong></p><p><img src="./cups/image-20260226075715440.png" alt=""></p><p>如果搜不到的话，请勾选Share printers connected to this system，然后重启一下虚拟机。</p><p><img src="./cups/image-20260226104839066.png" alt=""></p><p>但基本没啥功能，双面打印啥的基本都没。正常使用还得是带驱动。</p><p><img src="./cups/image-20260226080129846.png" alt=""></p><h3 id="SMB-协议">SMB 协议</h3><p>这里我懒得配置驱动共享了，就手动运行打印机驱动安装程序安装。</p><p>安装好驱动以后，在资源管理器的地址栏里面输入：<code>\\10.0.5.5\打印机</code>，对，中文的“打印机”，才可以看到共享的打印机，只输地址我这边只能看到共享文件夹。</p><p><img src="./cups/image-20260226110849545.png" alt=""></p><p>但经过测试，这种方式存在不稳定的现象，打印反应非常慢，估计是驱动的缺陷。总之就是没法用。</p><p><img src="./cups/image-20260226111123784.png" alt=""></p><p>而且 Windows 11 22H2 给 RPC 的默认设置改了，直接还连不上，得改注册表：</p><figure class="highlight cmd"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">reg</span><span style="color:#ABB2BF"> add </span><span style="color:#98C379">"HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Printers\RPC"</span><span style="color:#ABB2BF"> /f /v </span><span style="color:#98C379">"RpcUseNamedPipeProtocol"</span><span style="color:#ABB2BF"> /t REG_DWORD /d </span><span style="color:#98C379">"1"</span></span><span class="line"><span style="color:#C678DD">reg</span><span style="color:#ABB2BF"> add </span><span style="color:#98C379">"HKLM\SYSTEM\CurrentControlSet\Control\Print"</span><span style="color:#ABB2BF"> /f /v </span><span style="color:#98C379">"RpcAuthnLevelPrivacyEnabled"</span><span style="color:#ABB2BF"> /t REG_DWORD /d </span><span style="color:#98C379">"0"</span></span></code></pre></td></tr></tbody></table></figure><p>据说还不一定有用，最后回到古董协议LPD。</p><h3 id="LPD-协议">LPD 协议</h3><ol><li><p>这种情况也需要提前安装好驱动。</p></li><li><p>Windows 10 以上需要启用 LPR 支持，以<strong>管理员权限</strong>打开 CMD 输入：</p><figure class="highlight cmd"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">DISM.exe /Online /Enable-Feature /FeatureName:Printing-Foundation-LPRPortMonitor</span></span></code></pre></td></tr></tbody></table></figure><p>或者 PowerShell：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">Enable-WindowsOptionalFeature</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Online </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">FeatureName </span><span style="color:#98C379">"Printing-Foundation-LPRPortMonitor"</span></span></code></pre></td></tr></tbody></table></figure><p>或者打开 <code>控制面板\所有控制面板项\程序和功能</code>，勾选 打印和文件服务 下方的 LPR 端口监视器：</p><p><img src="./cups/image-20260226151930696.png" alt=""></p><blockquote><p>为什么先给命令？阿三Windows 11天天改，找半天都找不到这个控制面板的入口。“设备和打印机”都砍了，但又没砍全，自带的设置贼难用。</p></blockquote></li><li><p>进入刚刚说的那个“设备和打印机”，点击菜单栏的“添加打印机”，会弹出一个添加设备的窗口，这个里面即使出来你的打印机型号也不要用，肯定不是 LPD，这里应该点击“我所需的打印机未列出”。</p><p><img src="./cups/image-20260226152227553.png" alt=""></p></li><li><p>然后选择“通过手动设置添加本地打印机或网络打印机(O)”，不要选择 IP，那个里面没得 LPD，点击下一页。</p><p><img src="./cups/image-20260226152348467.png" alt=""></p></li><li><p>这里选择创建新端口，端口类型为 <code>LPR Port</code>。什么，没有？请检查上面的 LPR 功能有没有启用成功。</p><p><img src="./cups/image-20260226152812154.png" alt=""></p></li><li><p>点击下一页会弹出一个窗口（史山代码），填写服务器信息，注意打印队列名称填写CUPS里面设置的共享名，不是打印机描述名称，然后点击确定。</p><p><img src="./cups/image-20260226153624938.png" alt=""></p></li><li><p>这里务必要选择正确的驱动，不然无法打印，然后下一页。</p><p><img src="./cups/image-20260226155626092.png" alt=""></p></li><li><p>这里建议改下名，避免和本地打印机冲突/不好区分，然后点击下一页后，会自动注册打印机，需要一小会儿。</p><p><img src="./cups/image-20260226155721696.png" alt=""></p></li><li><p>这里可以打印测试页，然后完成。</p><p><img src="./cups/image-20260226155915795.png" alt=""></p><p>此时就基本可以了。如果你遇到了问题，建议关闭双向支持：</p><p><img src="./cups/1358e75c1778df8b5c8b347bdd1baffb.png" alt=""></p><p><img src="./cups/96b10ea0171a451e55e78a068b823158.png" alt=""></p></li></ol><h2 id="macOS客户端配置">macOS客户端配置</h2><p>首先打开「打印中心.app」。</p><p><img src="./cups/image-20260226224400511.png" alt=""></p><p><img src="./cups/image-20260226224801574.png" alt=""></p><h3 id="IPP协议">IPP协议</h3><p>IPP协议能自动搜索出来，也可以手动添加，不需要安装驱动。</p><p><img src="./cups/image-20260226225122625.png" alt=""></p><h3 id="LPD协议">LPD协议</h3><p><img src="./cups/image-20260226230136237.png" alt=""></p><h2 id="安卓客户端配置">安卓客户端配置</h2><p>需要安卓版本大于Android 8.0 (Oreo) ，不然只能安装第三方打印客户端。</p><h3 id="IPP-协议-2">IPP 协议</h3><p>安卓这边就简单得多，下面以 ColorOS 为例，实测支持 OriginOS、MagicOS，但不支持鸿蒙。因为鸿蒙的安卓版本较老，且系统APP打印服务为自行实现的华为打印，只支持华为打印机。</p><p><img src="./cups/image-20260226141412774.png" alt="image-20260226141412774"></p><h3 id="SMB-协议-LPD-协议">SMB 协议/LPD 协议</h3><p>需要下载安卓移动打印客户端，一般为付费，如PrintHand、PrinterShare、MobilePrint、ePrint - Mobile Printer &amp; Scan 、 PrintShare Pro 、NokoPrint 等。</p><blockquote><p>由于目前 IPP 能用，暂时没有折腾其他协议的需求，下面的地址来自于第三方，个人没试过，请自行判断是否带毒。</p><p>下载地址：<a href="https://apphot.cc/3738.html">https://apphot.cc/3738.html</a></p></blockquote><p>下载安装以后，打开APP，点击添加打印机，可能需要联网下载打印机驱动包，不然会缺少PPD文件无法识别型号。</p><h2 id="远程打印">远程打印</h2><p>直接用VPN搞定，免得给那些微信小程序交钱。</p><ol><li>EasyTier</li><li>Tailscale/Headscale（自建）</li><li>Zerotier</li></ol><h2 id="总结">总结</h2><p>本次部署遇到了很多个坑，其中大多都来自于 Ubuntu。反倒是当时在 Linux Mint 上测试，一点问题都没有。另外我是实在想不明白了，都 Minimal 镜像了，怎么还带一个 snap，真的服了 Canonical。。。</p><p>现在手机连上 Wi-Fi 自动搜索打印机，CUPS 真的太方便了。不得不说安卓更新了打印框架以后，越来越多厂商跟进，挺好的，这才是真的互联。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/cups</id>
    <link href="https://www.xrgzs.top/posts/cups"/>
    <published>2026-02-26T15:47:13.000Z</published>
    <summary>CUPS共享EPSON L383打印机</summary>
    <title>CUPS共享EPSON L383打印机</title>
    <updated>2026-02-26T15:47:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="前端" scheme="https://www.xrgzs.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="Vue" scheme="https://www.xrgzs.top/tags/vue/"/>
    <category term="CSS" scheme="https://www.xrgzs.top/tags/css/"/>
    <content>
      <![CDATA[<h1>Vue + UnoCSS 避免影响全局样式</h1><p>最近尝试使用 Vite 将一个 Vue 3 组件打包成 JS，以便插入一个不是 Vue 写的 HTML 中。本人属于前端小白，不会写CSS。由于 AI 写这种类名样式比较厉害，且听说 UnoCSS 比较厉害，故使用 UnoCSS + presetWind4 的方案。</p><p>不过实际部署后发现一个严重的问题，由于缺少隔离机制，UnoCSS / Tailwind CSS 会导致全局样式被覆盖，表现为外部的原生样式丢失。为了避免造成更大的影响，需要对其进行处理，避免全局样式被影响。</p><h2 id="尝试-vue-scoped-模式">尝试 <code>vue-scoped</code> 模式</h2><p>经过阅读 UnoCSS 文档，发现其支持<code>vue-scoped</code>功能，感觉可以完美解决这个问题，将样式限制到 Vue 的 <code>&lt;style scope&gt;</code> 内：</p><blockquote><p><a href="https://unocss.dev/integrations/vite#modes">https://unocss.dev/integrations/vite#modes</a></p><p><strong>Modes</strong></p><p>The Vite plugin comes with a set of modes that enable different behaviors.</p><ul><li><p><code>global</code> (default)</p><p>This is the default mode for the plugin: in this mode you need to add the import of <code>uno.css</code> on your entry point.</p><p>This mode enables a set of Vite plugins for <code>build</code> and for <code>dev</code> with <code>HMR</code> support.</p><p>The generated <code>css</code> will be a global stylesheet injected on the <code>index.html</code>.</p></li><li><p><code>vue-scoped</code></p><p>This mode will inject generated CSS to Vue SFCs <code>&lt;style scoped&gt;</code> for isolation.</p></li></ul></blockquote><p>具体使用方法：</p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">defineConfig</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "vite"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> vue</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "@vitejs/plugin-vue"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">resolve</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "path"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> UnoCSS</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "unocss/vite"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#61AFEF"> defineConfig</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">  plugins</span><span style="color:#ABB2BF">: [</span><span style="color:#61AFEF">vue</span><span style="color:#ABB2BF">(), </span><span style="color:#61AFEF">UnoCSS</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">      mode</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"vue-scoped"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;)],</span></span><span class="line"><span style="color:#ABB2BF">  ...</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><p>但经过实测发现样式只正常一半，和 Tailwind CSS 基本一样，经过查找发现这给问题还没修复。</p><ul><li>Issue：<a href="https://github.com/unocss/unocss/issues/4825">[presetWind4] Missing CSS variables in vue-scoped mode · Issue #4825 · unocss/unocss</a></li><li>PR：<a href="https://github.com/unocss/unocss/pull/4829">fix(vite): styles missing or duplicated when mode is <code>vue-scoped</code> by Jungzl · Pull Request #4829 · unocss/unocss</a></li></ul><p>这个 PR 已经无法跟上最新版本，所以这招暂时行不通，否则应该是最优解。</p><h2 id="Tailwind-CSS-完整用法">Tailwind CSS 完整用法</h2><p>这个时候感觉到 UnoCSS 存在问题，又换回 Tailwind CSS，发现其存在一个 <code>preflight</code> 机制（<a href="https://tailwindcss.com/docs/preflight%EF%BC%89%EF%BC%9A%E7%AE%80%E5%8D%95%E6%9D%A5%E8%AF%B4%E5%B0%B1%E6%98%AF%E7%BB%99%E6%B5%8F%E8%A7%88%E5%99%A8%E7%9A%84%E9%82%A3%E4%BA%9B%E8%87%AA%E5%B8%A6%E6%A0%B7%E5%BC%8F%E6%B8%85%E7%A9%BA%E3%80%82">https://tailwindcss.com/docs/preflight）：简单来说就是给浏览器的那些自带样式清空。</a></p><p>正常情况下，我们用 Tailwind CSS v4，只需要在 CSS 里面引入一句：</p><figure class="highlight css"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss"</span><span style="color:#ABB2BF">;</span></span></code></pre></td></tr></tbody></table></figure><p>实际上里面包含了：</p><figure class="highlight css"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">@layer</span><span style="color:#ABB2BF"> theme, base, components, utilities;</span></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss/theme.css"</span><span style="color:#ABB2BF"> layer(theme);</span></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss/preflight.css"</span><span style="color:#ABB2BF"> layer(base);</span></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss/utilities.css"</span><span style="color:#ABB2BF"> layer(utilities);</span></span></code></pre></td></tr></tbody></table></figure><p>这个 <code>@layer</code> 是什么意思呢？查询 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@layer">MDN</a> 发现，这是一个样式作用的过程，相当于不同的优先级，相当于定义CSS的作用域。</p><p>我可以给我的 Vue 组件加个 id，然后用这玩意限定一下清理的范围即可。</p><figure class="highlight css"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">@layer</span><span style="color:#ABB2BF"> theme, base, components, utilities;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss/theme.css"</span><span style="color:#ABB2BF"> layer(theme);</span></span><span class="line"><span style="color:#61AFEF">#extension-root</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#ABB2BF">  @import "tailwindcss/preflight.css" layer(base);</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "tailwindcss/utilities.css"</span><span style="color:#ABB2BF"> layer(utilities);</span></span></code></pre></td></tr></tbody></table></figure><p>（来自：<a href="https://j5cookie.medium.com/scoping-tailwindcss-preflight-for-injected-apps-c30152f6dd8d%EF%BC%89">https://j5cookie.medium.com/scoping-tailwindcss-preflight-for-injected-apps-c30152f6dd8d）</a></p><p>但是，经过实测，这样在 Vue Scope 内并不好使，样式还是存在小问题。</p><h2 id="回归-UnoCSS">回归 UnoCSS</h2><p>基于上面对 Tailwind CSS 的了解，继续看回 UnoCSS 的文档，发现 UnoCSS 的 presetWind4 支持<a href="https://unocss.dev/presets/wind4#reset">开关 Reset 样式</a>，貌似找到了希望，紧接着还发现其支持<a href="https://unocss.dev/transformers/compile-class">合并+随机类名</a>，这样可以解决冲突的问题，那就不得不用 UnoCSS 了。</p><p>比如：</p><figure class="highlight html"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">":uno: text-center sm:text-left"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">":uno: text-sm font-bold hover:text-red"</span><span style="color:#FFFFFF"> /</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>会合并成：</p><figure class="highlight html"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"uno-qlmcrp"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"uno-0qw2gr"</span><span style="color:#FFFFFF"> /</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>通过这种合并，可以避免污染外部环境，实现“隔离”的效果。但此时 Reset 样式还没解决。</p><h3 id="使用-Vue-style-scope-解决-Reset-样式污染">使用 Vue <code>&lt;style scope&gt;</code> 解决 Reset 样式污染</h3><p>首先去到 <code>uno.config.js/ts</code> 里面 <code>reset: false</code>，然后在对应的 Vue 组件里面手动导入 <code>@unocss/reset/tailwind-v4.css</code>，结果样式出现了部分泄露，通过对其 <code>@layer</code> 进行限制，即可解决这个问题。</p><p>经过测试，外部样式几乎未受影响，组件内部样式没有问题。</p><h2 id="完整配置">完整配置</h2><h3 id="vite-config-js"><code>vite.config.js</code></h3><figure class="highlight javascript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">defineConfig</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "vite"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> vue</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "@vitejs/plugin-vue"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">resolve</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "path"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> UnoCSS</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "unocss/vite"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#61AFEF"> defineConfig</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">  plugins</span><span style="color:#ABB2BF">: [</span><span style="color:#61AFEF">vue</span><span style="color:#ABB2BF">(), </span><span style="color:#61AFEF">UnoCSS</span><span style="color:#ABB2BF">()],</span></span><span class="line"><span style="color:#E06C75">  define</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#98C379">    "process.env"</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">JSON</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">stringify</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">      NODE_ENV</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">process</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">env</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">NODE_ENV</span><span style="color:#56B6C2"> ||</span><span style="color:#98C379"> "production"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">    &#125;),</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#E06C75">  build</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    lib</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">      entry</span><span style="color:#ABB2BF">: </span><span style="color:#61AFEF">resolve</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">__dirname</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"src/main.js"</span><span style="color:#ABB2BF">),</span></span><span class="line"><span style="color:#E06C75">      name</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"VueApp"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#61AFEF">      fileName</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">format</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#98C379"> `vue-app.</span><span style="color:#C678DD">$&#123;</span><span style="color:#E06C75">format</span><span style="color:#C678DD">&#125;</span><span style="color:#98C379">.js`</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      formats</span><span style="color:#ABB2BF">: [</span><span style="color:#98C379">"umd"</span><span style="color:#ABB2BF">],</span></span><span class="line"><span style="color:#ABB2BF">    &#125;,</span></span><span class="line"><span style="color:#E06C75">    cssCodeSplit</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><h3 id="uno-config-js"><code>uno.config.js</code></h3><figure class="highlight javascript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">defineConfig</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">presetWind4</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">transformerCompileClass</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "unocss"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#61AFEF"> defineConfig</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">  content</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    pipeline</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">      include</span><span style="color:#ABB2BF">: [</span><span style="color:#E06C75">/</span><span style="color:#56B6C2">\.</span><span style="color:#E06C75">(vue)(</span><span style="color:#C678DD">$</span><span style="color:#ABB2BF">|</span><span style="color:#56B6C2">\?</span><span style="color:#E06C75">)/</span><span style="color:#ABB2BF">],</span></span><span class="line"><span style="color:#ABB2BF">    &#125;,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#E06C75">  presets</span><span style="color:#ABB2BF">: [</span></span><span class="line"><span style="color:#61AFEF">    presetWind4</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">      preflights</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">        reset</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">false</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">      &#125;,</span></span><span class="line"><span style="color:#ABB2BF">    &#125;),</span></span><span class="line"><span style="color:#ABB2BF">  ],</span></span><span class="line"><span style="color:#E06C75">  transformers</span><span style="color:#ABB2BF">: [</span></span><span class="line"><span style="color:#61AFEF">    transformerCompileClass</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">      classPrefix</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"vapp-"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">    &#125;),</span></span><span class="line"><span style="color:#ABB2BF">  ],</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><h3 id="src-App-vue"><code>src/App.vue</code></h3><figure class="highlight vue"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">script</span><span style="color:#D19A66"> setup</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#98C379"> "virtual:uno.css"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">template</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"flex items-center justify-center min-h-screen bg-gray-100 "</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">    &#x3C;</span><span style="color:#E06C75">button</span></span><span class="line"><span style="color:#D19A66">      class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"p-2 bg-green-600 hover:bg-green-700 text-white rounded-lg shadow-md transition-all"</span></span><span class="line"><span style="color:#ABB2BF">    ></span></span><span class="line"><span style="color:#ABB2BF">      UnoCSS</span></span><span class="line"><span style="color:#ABB2BF">    &#x3C;/</span><span style="color:#E06C75">button</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">template</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">style</span><span style="color:#D19A66"> scoped</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#C678DD">@layer</span><span style="color:#ABB2BF"> base;</span></span><span class="line"><span style="color:#C678DD">@import</span><span style="color:#98C379"> "@unocss/reset/tailwind-v4.css"</span><span style="color:#ABB2BF"> layer(base);</span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">style</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><h2 id="最后">最后</h2><p>Vue 牛逼！！！UnoCSS 牛逼！！！antfu 神！！！</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/vue-unocss-scoped</id>
    <link href="https://www.xrgzs.top/posts/vue-unocss-scoped"/>
    <published>2026-02-25T02:55:08.000Z</published>
    <summary>
      <![CDATA[<h1>Vue + UnoCSS 避免影响全局样式</h1>
<p>最近尝试使用 Vite 将一个 Vue 3 组件打包成 JS，以便插入一个不是 Vue 写的 HTML 中。本人属于前端小白，不会写CSS。由于 AI 写这种类名样式比较厉害，且听说 UnoCSS 比较厉害，故使]]>
    </summary>
    <title>Vue + UnoCSS 避免影响全局样式</title>
    <updated>2026-02-25T02:55:08.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="Linux" scheme="https://www.xrgzs.top/tags/linux/"/>
    <category term="Docker" scheme="https://www.xrgzs.top/tags/docker/"/>
    <content>
      <![CDATA[<h1>Linux 下安装 nerdctl</h1><p>本文将介绍 Linux 下安装 nerdctl 的方法。演示环境为 Fedora Cloud 43。本文所有组件均从 GitHub Releases 安装（iptables 除外），不使用包管理器，方法通用于 glibc 的 Linux 发行版。</p><p>下载地址已包含加速源，并使用 SDLP 解析最新版本。</p><h2 id="安装-nerdctl-full">安装 nerdctl-full</h2><p>使用 nerdctl 提供的完整版安装包安装，无需手动安装后面的组件，包括 Buildkit 和 RootlessKit 等。</p><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/containerd/nerdctl/releases/download/v2.2.0/nerdctl-full-2.2.0-linux-amd64.tar.gz -O nerdctl-full-linux-amd64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://api.xrgzs.top/ghrelease/?repo=containerd/nerdctl&#x26;search=nerdctl-full-&#x26;filter=linux-amd64.tar.gz&#x26;mirror=auto"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> nerdctl-full-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /usr/local</span><span style="color:#98C379"> nerdctl-full-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>安装 iptables：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo apt install iptables</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> dnf</span><span style="color:#98C379"> install</span><span style="color:#98C379"> iptables</span></span></code></pre></td></tr></tbody></table></figure><p>启用 containerd systemd 服务：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> daemon-reload</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> containerd</span></span></code></pre></td></tr></tbody></table></figure><p>运行容器：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> nerdctl</span><span style="color:#98C379"> pull</span><span style="color:#98C379"> nginx:latest</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> nerdctl</span><span style="color:#98C379"> run</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> 80:80</span><span style="color:#98C379"> nginx:latest</span></span></code></pre></td></tr></tbody></table></figure><p>CLI 使用方法类似 Docker，只有少数功能如 Swarm 不支持。</p><p>如需配置 Rootless 容器，请跳转到 [配置 Rootless](#配置 Rootless) 一节。</p><h2 id="安装-Containerd">安装 Containerd</h2><p>Containerd 是一个来自 Docker 的容器运行时，并实现了 CRI 规范。nerdctl 运行容器需要 Containerd。</p><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/containerd/containerd/releases/download/v2.2.0/containerd-2.2.0-linux-amd64.tar.gz -O containerd-linux-amd64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://api.xrgzs.top/ghrelease/?repo=containerd/containerd&#x26;search=containerd-2&#x26;filter=linux-amd64.tar.gz&#x26;mirror=auto"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> containerd-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /usr/local</span><span style="color:#98C379"> containerd-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>创建 systemd 配置：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> https://gh.xrgzs.top/https://raw.githubusercontent.com/containerd/containerd/main/containerd.service</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> /usr/lib/systemd/system/containerd.service</span></span></code></pre></td></tr></tbody></table></figure><p>启用 systemd 服务：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> daemon-reload</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> enable</span><span style="color:#D19A66"> --now</span><span style="color:#98C379"> containerd</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-RunC">安装 RunC</h2><p>Containerd 用 RunC 运行容器。</p><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo wget https://github.com/opencontainers/runc/releases/download/v1.3.3/runc.amd64 -O /usr/local/sbin/runc</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://gh.xrgzs.top/https://github.com/opencontainers/runc/releases/latest/download/runc.amd64"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> /usr/local/sbin/runc</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#D19A66"> 755</span><span style="color:#98C379"> /usr/local/sbin/runc</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-CNI-Plugin">安装 CNI Plugin</h2><p>Containerd 用 CNI（容器网络接口）管理网络，如 <code>bridge</code>、<code>macvlan</code> 等。</p><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/containernetworking/plugins/releases/download/v1.8.0/cni-plugins-linux-amd64-v1.8.0.tgz -O cni-plugins-linux-amd64.tgz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://api.xrgzs.top/ghrelease/?repo=containernetworking/plugins&#x26;search=cni-plugins-linux-amd64&#x26;filter=tgz&#x26;mirror=auto"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> cni-plugins-linux-amd64.tgz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> /opt/cni/bin</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /opt/cni/bin</span><span style="color:#98C379"> cni-plugins-linux-amd64.tgz</span></span></code></pre></td></tr></tbody></table></figure><p>需要注意的是，CNI 依赖 <code>iptables</code> 命令，所以还需安装 iptables。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo apt install iptables</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> dnf</span><span style="color:#98C379"> install</span><span style="color:#98C379"> iptables</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-nerdctl">安装 nerdctl</h2><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/containerd/nerdctl/releases/download/v2.2.0/nerdctl-2.2.0-linux-amd64.tar.gz -O nerdctl-linux-amd64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://api.xrgzs.top/ghrelease/?repo=containerd/nerdctl&#x26;search=nerdctl-&#x26;filter=linux-amd64.tar.gz&#x26;mirror=auto"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> nerdctl-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /usr/local/bin</span><span style="color:#98C379"> nerdctl-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><h2 id="（可选）安装-Buildkit">（可选）安装 Buildkit</h2><p>如果需构建容器，则需要安装 Buildkit。</p><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/moby/buildkit/releases/download/v0.26.2/buildkit-v0.26.2.linux-amd64.tar.gz -O buildkit-linux-amd64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://api.xrgzs.top/ghrelease/?repo=moby/buildkit&#x26;search=buildkit-&#x26;filter=linux-amd64.tar.gz&#x26;mirror=auto"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> buildkit-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /usr/local</span><span style="color:#98C379"> buildkit-linux-amd64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><h2 id="（可选）安装-RootlessKit-slirp4netns">（可选）安装 RootlessKit + slirp4netns</h2><p>如需使用 nerdctl 的 rootless 容器，需要安装这两个组件。</p><ol><li>RootlessKit 是使用 Linux 的用户命名空间原生实现的 fake root。</li><li>slirp4netns 为非特权网络命名空间提供用户模式网络（slirp）。</li></ol><p>从 GitHub Releases 下载最新版本：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># wget https://github.com/rootless-containers/rootlesskit/releases/download/v2.3.5/rootlesskit-x86_64.tar.gz -O rootlesskit-x86_64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://gh.xrgzs.top/https://github.com/rootless-containers/rootlesskit/releases/latest/download/rootlesskit-x86_64.tar.gz"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> rootlesskit-x86_64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>解压安装：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> Cxzvf</span><span style="color:#98C379"> /usr/local/bin</span><span style="color:#98C379"> rootlesskit-x86_64.tar.gz</span></span></code></pre></td></tr></tbody></table></figure><p>slirp4netns 下载安装：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># sudo wget https://github.com/rootless-containers/slirp4netns/releases/download/v1.3.3/slirp4netns-x86_64 -O /usr/local/bin/slirp4netns</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> curl</span><span style="color:#D19A66"> -SL</span><span style="color:#98C379"> "https://github.com/rootless-containers/slirp4netns/releases/latest/download/slirp4netns-x86_64"</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> /usr/local/bin/slirp4netns</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#D19A66"> 755</span><span style="color:#98C379"> /usr/local/bin/slirp4netns</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Rootless">配置 Rootless</h2><p>使用 nerdctl 提供的 <code>containerd-rootless-setuptool.sh check</code> 检查一下依赖是否配置正确：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[fedora@fedora ~]$ containerd-rootless-setuptool.sh check</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking RootlessKit functionality</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking cgroup v2</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking overlayfs</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Requirements are satisfied</span></span></code></pre></td></tr></tbody></table></figure><p>没问题就可以执行 <code>containerd-rootless-setuptool.sh install</code> 安装 rootless 的 daemon 了。</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[fedora@fedora ~]$ containerd-rootless-setuptool.sh install</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking RootlessKit functionality</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking cgroup v2</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Checking overlayfs</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Requirements are satisfied</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Creating </span><span style="color:#98C379">"/home/fedora/.config/systemd/user/containerd.service"</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Starting systemd unit </span><span style="color:#98C379">"containerd.service"</span></span><span class="line"><span style="color:#61AFEF">+</span><span style="color:#98C379"> systemctl</span><span style="color:#D19A66"> --user</span><span style="color:#98C379"> start</span><span style="color:#98C379"> containerd.service</span></span><span class="line"><span style="color:#61AFEF">+</span><span style="color:#98C379"> sleep</span><span style="color:#D19A66"> 3</span></span><span class="line"><span style="color:#61AFEF">+</span><span style="color:#98C379"> systemctl</span><span style="color:#D19A66"> --user</span><span style="color:#D19A66"> --no-pager</span><span style="color:#D19A66"> --full</span><span style="color:#98C379"> status</span><span style="color:#98C379"> containerd.service</span></span><span class="line"><span style="color:#61AFEF">●</span><span style="color:#98C379"> containerd.service</span><span style="color:#98C379"> -</span><span style="color:#98C379"> containerd</span><span style="color:#ABB2BF"> (Rootless)</span></span><span class="line"><span style="color:#61AFEF">     Loaded:</span><span style="color:#98C379"> loaded</span><span style="color:#ABB2BF"> (/home/fedora/.config/systemd/user/containerd.service; </span><span style="color:#61AFEF">disabled</span><span style="color:#ABB2BF">; </span><span style="color:#61AFEF">preset:</span><span style="color:#98C379"> disabled</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">    Drop-In:</span><span style="color:#98C379"> /usr/lib/systemd/user/service.d</span></span><span class="line"><span style="color:#61AFEF">             └─10-timeout-abort.conf</span></span><span class="line"><span style="color:#61AFEF">     Active:</span><span style="color:#98C379"> active</span><span style="color:#ABB2BF"> (running) since Fri 2025-11-21 02:47:37 UTC; </span><span style="color:#61AFEF">3s</span><span style="color:#98C379"> ago</span></span><span class="line"><span style="color:#61AFEF"> Invocation:</span><span style="color:#98C379"> 560889ac8bec4de48bcb83b292092485</span></span><span class="line"><span style="color:#61AFEF">   Main</span><span style="color:#98C379"> PID:</span><span style="color:#D19A66"> 3871</span><span style="color:#ABB2BF"> (rootlesskit)</span></span><span class="line"><span style="color:#61AFEF">      Tasks:</span><span style="color:#D19A66"> 31</span></span><span class="line"><span style="color:#61AFEF">     Memory:</span><span style="color:#98C379"> 20.2M</span><span style="color:#ABB2BF"> (peak: </span><span style="color:#98C379">20.2M</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#61AFEF">        CPU:</span><span style="color:#98C379"> 168ms</span></span><span class="line"><span style="color:#61AFEF">     CGroup:</span><span style="color:#98C379"> /user.slice/user-1000.slice/user@1000.service/app.slice/containerd.service</span></span><span class="line"><span style="color:#61AFEF">             ├─3871</span><span style="color:#98C379"> rootlesskit</span><span style="color:#D19A66"> --state-dir=/run/user/1000/containerd-rootless</span><span style="color:#D19A66"> --net=slirp4netns</span><span style="color:#D19A66"> --mtu=65520</span><span style="color:#D19A66"> --slirp4netns-sandbox=auto</span><span style="color:#D19A66"> --slirp4netns-seccomp=auto</span><span style="color:#D19A66"> --disable-host-loopback</span><span style="color:#D19A66"> --port-driver=builtin</span><span style="color:#D19A66"> --copy-up=/etc</span><span style="color:#D19A66"> --copy-up=/run</span><span style="color:#D19A66"> --copy-up=/var/lib</span><span style="color:#D19A66"> --propagation=rslave</span><span style="color:#D19A66"> --detach-netns</span><span style="color:#98C379"> /usr/local/bin/containerd-rootless.sh</span></span><span class="line"><span style="color:#61AFEF">             ├─3890</span><span style="color:#98C379"> /proc/self/exe</span><span style="color:#D19A66"> --state-dir=/run/user/1000/containerd-rootless</span><span style="color:#D19A66"> --net=slirp4netns</span><span style="color:#D19A66"> --mtu=65520</span><span style="color:#D19A66"> --slirp4netns-sandbox=auto</span><span style="color:#D19A66"> --slirp4netns-seccomp=auto</span><span style="color:#D19A66"> --disable-host-loopback</span><span style="color:#D19A66"> --port-driver=builtin</span><span style="color:#D19A66"> --copy-up=/etc</span><span style="color:#D19A66"> --copy-up=/run</span><span style="color:#D19A66"> --copy-up=/var/lib</span><span style="color:#D19A66"> --propagation=rslave</span><span style="color:#D19A66"> --detach-netns</span><span style="color:#98C379"> /usr/local/bin/containerd-rootless.sh</span></span><span class="line"><span style="color:#61AFEF">             ├─3918</span><span style="color:#98C379"> slirp4netns</span><span style="color:#D19A66"> --mtu</span><span style="color:#D19A66"> 65520</span><span style="color:#D19A66"> -r</span><span style="color:#D19A66"> 3</span><span style="color:#D19A66"> --disable-host-loopback</span><span style="color:#D19A66"> --enable-seccomp</span><span style="color:#D19A66"> --userns-path=/proc/3890/ns/user</span><span style="color:#D19A66"> --netns-type=path</span><span style="color:#98C379"> /proc/3890/root/run/user/1000/containerd-rootless/netns</span><span style="color:#98C379"> tap0</span></span><span class="line"><span style="color:#61AFEF">             └─3926</span><span style="color:#98C379"> containerd</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434258032Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="loading plugin"</span><span style="color:#98C379"> id=io.containerd.ttrpc.v1.otelttrpc</span><span style="color:#98C379"> type=io.containerd.ttrpc.v1</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434266430Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="loading plugin"</span><span style="color:#98C379"> id=io.containerd.grpc.v1.healthcheck</span><span style="color:#98C379"> type=io.containerd.grpc.v1</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434275558Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="loading plugin"</span><span style="color:#98C379"> id=io.containerd.grpc.v1.cri</span><span style="color:#98C379"> type=io.containerd.grpc.v1</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434283355Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="Connect containerd service"</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434346716Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="using experimental NRI integration - disable nri plugin to prevent this"</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434404243Z"</span><span style="color:#98C379"> level=warning</span><span style="color:#98C379"> msg="Running CRI plugin in a user namespace typically requires disable_apparmor and restrict_oom_score_adj to be true"</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.434880822Z"</span><span style="color:#98C379"> level=warning</span><span style="color:#98C379"> msg="failed to load plugin"</span><span style="color:#98C379"> error="failed to create CRI service: failed to create cni conf monitor for default: failed to watch cni conf dir /etc/cni/net.d: permission denied"</span><span style="color:#98C379"> id=io.containerd.grpc.v1.cri</span><span style="color:#98C379"> type=io.containerd.grpc.v1</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.435180666Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg=serving...</span><span style="color:#98C379"> address=/run/containerd/containerd.sock.ttrpc</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.435245256Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg=serving...</span><span style="color:#98C379"> address=/run/containerd/containerd.sock</span></span><span class="line"><span style="color:#61AFEF">Nov</span><span style="color:#D19A66"> 21</span><span style="color:#98C379"> 02:47:37</span><span style="color:#98C379"> fedora</span><span style="color:#98C379"> containerd-rootless.sh[3926]:</span><span style="color:#98C379"> time="2025-11-21T02:47:37.435264820Z"</span><span style="color:#98C379"> level=info</span><span style="color:#98C379"> msg="containerd successfully booted in 0.093982s"</span></span><span class="line"><span style="color:#61AFEF">+</span><span style="color:#98C379"> systemctl</span><span style="color:#D19A66"> --user</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> containerd.service</span></span><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> symlink</span><span style="color:#98C379"> '/home/fedora/.config/systemd/user/default.target.wants/containerd.service'</span><span style="color:#98C379"> →</span><span style="color:#98C379"> '/home/fedora/.config/systemd/user/containerd.service'.</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Installed </span><span style="color:#98C379">"containerd.service"</span><span style="color:#ABB2BF"> successfully.</span></span><span class="line"><span style="color:#ABB2BF">[INFO] To control </span><span style="color:#98C379">"containerd.service"</span><span style="color:#ABB2BF">, run: </span><span style="color:#98C379">`</span><span style="color:#61AFEF">systemctl</span><span style="color:#D19A66"> --user</span><span style="color:#98C379"> (start</span><span style="color:#ABB2BF">|</span><span style="color:#61AFEF">stop</span><span style="color:#ABB2BF">|</span><span style="color:#61AFEF">restart</span><span style="color:#98C379">) containerd.service`</span></span><span class="line"><span style="color:#ABB2BF">[INFO] </span><span style="color:#61AFEF">To</span><span style="color:#98C379"> run</span><span style="color:#98C379"> "containerd.service"</span><span style="color:#98C379"> on</span><span style="color:#98C379"> system</span><span style="color:#98C379"> startup</span><span style="color:#98C379"> automatically,</span><span style="color:#98C379"> run:</span><span style="color:#98C379"> `</span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> loginctl enable-linger fedora`</span></span><span class="line"><span style="color:#ABB2BF">[INFO] </span><span style="color:#61AFEF">------------------------------------------------------------------------------------------</span></span><span class="line"><span style="color:#ABB2BF">[INFO] Use </span><span style="color:#98C379">`</span><span style="color:#61AFEF">nerdctl</span><span style="color:#98C379">`</span><span style="color:#61AFEF"> to</span><span style="color:#98C379"> connect</span><span style="color:#98C379"> to</span><span style="color:#98C379"> the</span><span style="color:#98C379"> rootless</span><span style="color:#98C379"> containerd.</span></span><span class="line"><span style="color:#ABB2BF">[INFO] You </span><span style="color:#C678DD">do</span><span style="color:#61AFEF"> NOT</span><span style="color:#98C379"> need</span><span style="color:#98C379"> to</span><span style="color:#98C379"> specify</span><span style="color:#E06C75"> $CONTAINERD_ADDRESS</span><span style="color:#98C379"> explicitly.</span></span></code></pre></td></tr></tbody></table></figure><p>根据提示，使用 <code>systemctl --user (start|stop|restart) containerd.service</code> 管理 containerd 服务，如需开机自启容器，需要执行 <code>sudo loginctl enable-linger $USER</code>。</p><h2 id="配置-注册表镜像">配置 注册表镜像</h2><p>nerdctl 使用 containerd 拉取镜像，所以需要修改 containerd 的配置：</p><blockquote><p>由于镜像具有时效性，具体看这篇文章：</p><p><a href="https://www.xrgzs.top/posts/docker-cmd#containerd">https://www.xrgzs.top/posts/docker-cmd#containerd</a></p></blockquote><p>然后重启 containerd：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> systemctl</span><span style="color:#98C379"> restart</span><span style="color:#98C379"> containerd</span></span></code></pre></td></tr></tbody></table></figure><p>验证配置：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> nerdctl</span><span style="color:#98C379"> pull</span><span style="color:#98C379"> nginx</span></span></code></pre></td></tr></tbody></table></figure><p>如果你使用的是其他镜像源（如 <a href="http://gcr.io">gcr.io</a>、<a href="http://quay.io">quay.io</a>），按同样方式在 <code>/etc/containerd/certs.d/</code> 下创建对应目录和 <code>hosts.toml</code> 文件即可。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/nerdctl-install</id>
    <link href="https://www.xrgzs.top/posts/nerdctl-install"/>
    <published>2025-11-21T05:22:50.000Z</published>
    <summary>
      <![CDATA[<h1>Linux 下安装 nerdctl</h1>
<p>本文将介绍 Linux 下安装 nerdctl 的方法。演示环境为 Fedora Cloud 43。本文所有组件均从 GitHub Releases 安装（iptables 除外），不使用包管理器，方法通用于 glibc]]>
    </summary>
    <title>Linux 下安装 nerdctl</title>
    <updated>2025-11-21T05:22:50.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="跨域" scheme="https://www.xrgzs.top/tags/%E8%B7%A8%E5%9F%9F/"/>
    <category term="CORS" scheme="https://www.xrgzs.top/tags/cors/"/>
    <category term="JSONP" scheme="https://www.xrgzs.top/tags/jsonp/"/>
    <category term="反向代理" scheme="https://www.xrgzs.top/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
    <content>
      <![CDATA[<p>跨域问题是 Web 开发中经常遇到的一个挑战，解决方法多样，例如使用反向代理、CORS、JSONP 方案。本文将为大家深入探讨一下跨域方案的具体实现。</p><h2 id="同源策略">同源策略</h2><p>浏览器通过实施<strong>同源策略</strong>（Same Origin Policy）保障用户安全：若两个页面来自同一源，则允许彼此交互。这里的“同一源”指的是页面的<strong>域名</strong>、<strong>端口</strong>和<strong>协议</strong>完全一致。</p><blockquote><p><strong>小练习</strong></p><p>以下 URL 分别与 <a href="https://www.example.com">https://www.example.com</a> 是否同源？</p><table><thead><tr><th style="text-align:left">URL</th><th style="text-align:left">同源？</th></tr></thead><tbody><tr><td style="text-align:left"><code>https://www.example.com/profile</code></td><td style="text-align:left"><strong>是</strong> - 协议、域名和端口匹配，即使路径不同</td></tr><tr><td style="text-align:left"><code>http://www.example.com</code></td><td style="text-align:left"><strong>否</strong> - 协议不同</td></tr><tr><td style="text-align:left"><code>https://www.anotherwebsite.com</code></td><td style="text-align:left"><strong>否</strong> - 域名不同</td></tr><tr><td style="text-align:left"><code>https://www.example.com:8080</code></td><td style="text-align:left"><strong>否</strong> - 端口不同</td></tr></tbody></table></blockquote><p>同源策略可防止恶意网站在用户被诱骗访问其他网站时从其他网站读取敏感数据。例如，黑客的网站不能包含来自 Facebook 的 HTML，不然的话访问他们的网站，则可以轻松抓取您的个人资料。</p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">// hack-attempt.js</span></span><span class="line"><span style="color:#7F848E;font-style:italic">/**</span></span><span class="line"><span style="color:#7F848E;font-style:italic"> * 尝试访问用户资料</span></span><span class="line"><span style="color:#7F848E;font-style:italic"> */</span></span><span class="line"><span style="color:#61AFEF">fetch</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://www.facebooke.com/profile"</span><span style="color:#ABB2BF">).</span><span style="color:#61AFEF">catch</span><span style="color:#ABB2BF">((</span><span style="color:#E06C75;font-style:italic">err</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">  // 浏览器会因为页面位于不同来源（origin）而导致此代码失败。</span></span><span class="line"><span style="color:#7F848E;font-style:italic">  // 这样可以防止黑客抓取数据。</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><h3 id="解决跨域问题">解决跨域问题</h3><p>当网页中的 JavaScript 发起跨域 Ajax 请求时，这个请求会被发送出去吗？</p><p>实际上，大多数情况下，即便浏览器检测到跨域，请求依然会被发送至服务器。不过，请求成功发出并不意味着一定能获取到结果。当然，像 CSP（Content-Security-Policy，内容安全策略）这类特殊场景，不在本次讨论范围内。</p><p><img src="cors/3c062f876360b15d836ccef591a76808.png" alt=""></p><p>当服务器接收到请求后，会返回一个响应，该响应随后到达浏览器。浏览器会执行一些校验，以确认请求是否允许通过。</p><p>如果<strong>校验成功</strong>，浏览器将把请求的结果，即服务器的响应结果交付给 JavaScript，使得 JavaScript 能够正常触发事件并接收服务器的响应。相反，如果浏览器的<strong>校验未通过</strong>，将引发一个<strong>跨域错误</strong>。我们平时遇到的各类跨域错误，根源都是<strong>浏览器的校验未通过</strong>。</p><p>此外对于静态资源，如图片、视频、音频，<strong>默认允许跨域加载</strong>，无需 CORS；但当你需要使用相关 API （如：Canvas）对其进行处理时，又会需要启用 CORS。</p><p>理解了跨域问题的产生原因后，解决思路其实很清晰：<strong>只要让请求通过浏览器的校验即可。</strong></p><p>要实现这一点，首先需要明确校验的依据，即 <strong>CORS 规则</strong>。采用 CORS 方案解决跨域问题，本质上就是让请求满足 CORS 校验规则，从而顺利通过校验。接下来，我们将深入剖析 CORS 规则的具体内容，明确其核心机制，并探讨如何让请求符合规则以通过校验。</p><h2 id="CORS">CORS</h2><p>跨源资源共享（CORS，Cross-Origin Resource Sharing）是一种基于 HTTP 头的机制，用于浏览器校验跨域请求，它的基本理念是：</p><ol><li>只要<strong>服务器</strong>明确表示<strong>允许</strong>，则校验<strong>通过</strong>；</li><li><strong>服务器</strong>明确<strong>拒绝</strong>或<strong>没有表示</strong>，则校验<strong>不通过</strong>。</li></ol><p>使用 CORS 的前提：<strong>必须保证服务器是“自己人”。</strong></p><p><img src="cors/6d642fa134587765c0eba6a572526055.svg" alt=""></p><h3 id="校验规则">校验规则</h3><p>CORS 将请求分为两类：<strong>简单请求</strong>和<strong>预检请求</strong>。</p><p>简单请求和预检请求的<strong>校验方式</strong>不同，<strong>简单请求</strong>的校验过程较为<strong>宽松</strong>，而<strong>预检请求</strong>的校验过程则更为<strong>严格</strong>。</p><p>那么，什么请求是简单请求，什么请求是预检请求？简单请求怎么校验，预检请求又是怎么校验的？</p><h3 id="简单请求和预检请求">简单请求和预检请求</h3><p>简单请求有<strong>三个条件</strong>，这些条件<strong>必须全部得到满足</strong>：</p><ol><li>请求方法为：<code>GET</code>、<code>HEAD</code>、<code>POST</code>（列入 CORS 白名单的请求标头方法，<a href="https://fetch.spec.whatwg.org/#cors-safelisted-method">CORS-safelisted method</a>）</li><li>头部字段满足 CORS 安全规范（列入 CORS 白名单的请求标头，<a href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header">CORS-safelisted request header</a>）</li><li>请求头的 <code>Content-Type</code> 为：<ul><li><code>text/plain</code></li><li><code>multipart/form-data</code></li><li><code>application/x-www-form-urlencoded</code></li></ul></li></ol><p>如果有一项条件未被满足，那么该请求将被视为<strong>预检请求</strong>（Preflight）。</p><p>接下来，我们将通过一些示例来区分简单请求与预检请求。</p><p>首先需要确保浏览器开发者工具的网络中已勾选显示“方法”一列。</p><p><img src="cors/6d918faff37ffe50702f59b397aaaf46.webp" alt=""></p><p><strong>例 1</strong></p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fetch</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://www.douyin.com"</span><span style="color:#ABB2BF">);</span></span></code></pre></td></tr></tbody></table></figure><p>请求方法默认为 GET，头部未动，且 Content-Type 未更改，则该请求为简单请求。</p><p><img src="cors/916a5169d3608d1c7a470965cb8d046d.webp" alt=""></p><p><strong>例 2</strong></p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fetch</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://douyin.com"</span><span style="color:#ABB2BF">, &#123;</span></span><span class="line"><span style="color:#E06C75">  headers</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    a</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><p>这是一个 GET 请求，但操作了头部，导致头部不再满足安全规范，那么这个请求就变成了一个预检请求。</p><p>所以，GET 请求不一定是预检请求。</p><p><img src="cors/b96919760e5f77695c6264a75a3e66be.png" alt=""></p><p><strong>例 3</strong></p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fetch</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://www.douyin.com"</span><span style="color:#ABB2BF">, &#123;</span></span><span class="line"><span style="color:#E06C75">  method</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"POST"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">  body</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">JSON</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">stringify</span><span style="color:#ABB2BF">(&#123; </span><span style="color:#E06C75">a</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">b</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">2</span><span style="color:#ABB2BF"> &#125;),</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><p>这是一个 POST 请求，那 POST 一定是预检吗？我们通过测试发现这是一个简单请求。上面说的 POST 方法是满足的，也没有动头部，所以这是一个简单请求。</p><p>因此，POST 请求也不一定是预检请求。</p><p><img src="cors/dcc24b94e60c15c7efa85848e2e5b32b.png" alt=""></p><blockquote><p>部分第三方库，如 <code>axios</code>，在发送 POST 请求时，通常内部会有一些处理，会自动修改 <code>Content-Type</code>，不满足上述第三条，因此会变成预检请求，等同于例 4。</p><figure class="highlight javascript"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#E5C07B">axios</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">post</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://www.douyin.com"</span><span style="color:#ABB2BF">, &#123; </span><span style="color:#E06C75">a</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">b</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">2</span><span style="color:#ABB2BF"> &#125;);</span></span><span class="line"><span style="color:#7F848E;font-style:italic">// Content-Type: application/json</span></span></code></pre></td></tr></tbody></table></figure></blockquote><p><strong>例 4</strong></p><figure class="highlight javascript"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fetch</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"https://www.douyin.com"</span><span style="color:#ABB2BF">, &#123;</span></span><span class="line"><span style="color:#E06C75">  method</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"POST"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">  headers</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#98C379">    "content-type"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"application/json"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#E06C75">  body</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">JSON</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">stringify</span><span style="color:#ABB2BF">(&#123; </span><span style="color:#E06C75">a</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">b</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">2</span><span style="color:#ABB2BF"> &#125;),</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><p>预检请求</p><p><img src="cors/d262e0c1aa3286d6de9d1b3bc05c1be9.png" alt=""></p><h3 id="简单请求的校验方式">简单请求的校验方式</h3><p>简单请求的校验过程非常简单。当用户发起请求时，浏览器会自动添加 <code>Origin</code> 头，其中包含请求来源页面的完整信息（协议、域名、端口）。服务器接收请求后，将依据该头信息判断是否允许跨域访问。</p><p>若服务器允许访问，会通过以下两种方式告知浏览器：</p><ol><li>将 <code>Access-Control-Allow-Origin</code> 响应头设置为请求时的 <code>Origin</code> 值；</li><li>直接将 <code>Access-Control-Allow-Origin</code> 响应头设为 <code>*</code>，表示允许所有域名访问。</li></ol><p><img src="cors/846e41111cd61ada4ded042f9055f65c.png" alt=""></p><blockquote><p>在实际应用中，<strong>切忌为图简便而直接使用星号</strong>，应明确指定具体的 <code>Origin</code>，否则可能引发安全隐患及细节问题。</p></blockquote><blockquote><p>此外需要注意一个细节：<code>Access-Control-Allow-Origin</code> 仅能定义单个 <code>Origin</code>。若需允许多个 <code>Origin</code> 跨域且不使用星号，需在服务器端配置动态规则，仅返回当前请求对应的 <code>Origin</code> 即可。</p></blockquote><h3 id="预检请求的校验方式">预检请求的校验方式</h3><p>预检请求的执行分为两个步骤，流程如下：</p><ol><li><p>发送预检请求</p><ul><li>浏览器首先向服务器发起询问（采用<code>OPTIONS</code>方法）。例如，来源为<code>http://my.com</code>的页面计划通过<code>POST</code>方法请求服务器，且需携带<code>a</code>、<code>b</code>、<code>content-type</code>等请求头时，会先触发预检。</li><li>此时不会发送真实的请求体。</li><li>若服务器拒绝该预检请求，真实请求将不会发送。</li><li>若服务器同意，则会返回以下响应头：<ul><li><code>Access-Control-Allow-Origin</code>、<code>Access-Control-Allow-Methods</code>、<code>Access-Control-Allow-Headers</code>：明确允许的来源、方法和请求头</li><li><code>Access-Control-Max-Age</code>：指定预检结果的缓存时间（TTL），在该时间内，相同情况的请求无需重复执行预检，可减少请求次数</li></ul></li></ul><p><img src="cors/58b1991ca473683d019ffd6924a124ab.png" alt=""></p></li><li><p>发送真实请求</p><ul><li>流程与简单请求一致</li></ul></li></ol><blockquote><p><strong>小练习</strong></p><ol><li>领导要求前端通过 CORS 自行解决页面跨域问题，前端能否独立完成？</li><li>前端在页面中用 Ajax 调用抖音 API 时遇到跨域问题，能否通过 CORS 自行解决？</li><li>跨域上传图片正常，但提交普通表单却出现跨域问题，可能的原因是什么？</li></ol></blockquote><h2 id="JSONP">JSONP</h2><p>JSONP（JSON with Padding）是解决跨域问题的古老方案。</p><p>JSONP 利用了<strong>同源策略中，对 <code>script</code> 标签的跨域请求限制较小</strong>这一点实现跨域。</p><h3 id="实现">实现</h3><p>JSONP 通过动态创建 <code>script</code>标签，绕过浏览器同源策略限制，服务器返回执行回调函数的 JavaScript 代码，客户端预先定义函数接收数据，实现跨域数据获取，适用于无 CORS 支持的旧项目。</p><p><img src="cors/ab33789c4177e9f1b2f2b4797f373908.png" alt=""></p><figure class="highlight javascript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> callback</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75;font-style:italic">resp</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E5C07B">  console</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">log</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">resp</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> request</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75;font-style:italic">url</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#C678DD">  const</span><span style="color:#E5C07B"> script</span><span style="color:#56B6C2"> =</span><span style="color:#E5C07B"> document</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">createElement</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"script"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#E5C07B">  script</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">src</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> url</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#E5C07B">  script</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">onload</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> function</span><span style="color:#ABB2BF"> () &#123;</span></span><span class="line"><span style="color:#E5C07B">    script</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">remove</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#ABB2BF">  &#125;;</span></span><span class="line"><span style="color:#E5C07B">  document</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">body</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">appendChild</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">document</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">querySelector</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"button"</span><span style="color:#ABB2BF">).</span><span style="color:#61AFEF">onclick</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> function</span><span style="color:#ABB2BF"> () &#123;</span></span><span class="line"><span style="color:#61AFEF">  request</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"http://localhost:8000/api/user"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">&#125;;</span></span></code></pre></td></tr></tbody></table></figure><p>服务器返回：</p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">callback</span><span style="color:#ABB2BF">([</span></span><span class="line"><span style="color:#ABB2BF">  &#123; </span><span style="color:#E06C75">name</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"monica"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">age</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">17</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">sex</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"female"</span><span style="color:#ABB2BF"> &#125;,</span></span><span class="line"><span style="color:#ABB2BF">  &#123; </span><span style="color:#E06C75">name</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"姬成"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">age</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">27</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">sex</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"male"</span><span style="color:#ABB2BF"> &#125;,</span></span><span class="line"><span style="color:#ABB2BF">  &#123; </span><span style="color:#E06C75">name</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"邓旭明"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">age</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">37</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">sex</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"male"</span><span style="color:#ABB2BF"> &#125;,</span></span><span class="line"><span style="color:#ABB2BF">]);</span></span></code></pre></td></tr></tbody></table></figure><h3 id="示例">示例</h3><p>这里以 Hitokoto 一言接口为例：</p><figure class="highlight html"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">p</span><span style="color:#D19A66"> id</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"hitokoto"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">a</span><span style="color:#D19A66"> href</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"#"</span><span style="color:#D19A66"> id</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"hitokoto_text"</span><span style="color:#ABB2BF">>:D 获取中...&#x3C;/</span><span style="color:#E06C75">a</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">p</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">script</span></span><span class="line"><span style="color:#D19A66">  src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"https://v1.hitokoto.cn/?encode=js&#x26;select=%23hitokoto"</span></span><span class="line"><span style="color:#D19A66">  defer</span></span><span class="line"><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>其 JavaScript 中的内容如下，其<code>var hitokoto=</code> 后面的内容每次都不一样，从而实现动态随机的效果：</p><figure class="highlight javascript"><table><tbody><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">(</span><span style="color:#C678DD">function</span><span style="color:#61AFEF"> hitokoto</span><span style="color:#ABB2BF">() &#123;</span></span><span class="line"><span style="color:#C678DD">  var</span><span style="color:#E06C75"> hitokoto</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "刀剑无眼，匠人有情。人情二字。不就是人类最引以为傲的事物吗？"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">  var</span><span style="color:#E06C75"> dom</span><span style="color:#56B6C2"> =</span><span style="color:#E5C07B"> document</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">querySelector</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"#hitokoto"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#E5C07B">  Array</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">isArray</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">dom</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">    ?</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">dom</span><span style="color:#ABB2BF">[</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">].</span><span style="color:#E06C75">innerText</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> hitokoto</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">    :</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">dom</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">innerText</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> hitokoto</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">&#125;)();</span></span></code></pre></td></tr></tbody></table></figure><p>可以看到这个东西权限很大，甚至可以改 DOM，所以存在一定安全风险，尤其是外站链接。网站开发者和管理员应该警惕这种方式。</p><h2 id="反向代理">反向代理</h2><p>对于一些来自外部的接口，我们通常无法直接为其添加 <code>Access-Control-Allow-*</code> 系列响应头。此时，可通过反向代理方案解决：将目标接口代理至我们的服务器，再由我们的服务器为其补充所需的响应头。下面会给出一些具体的配置示例。</p><p><img src="cors/0aea532ab7c66a68896f98f7260e3231.png" alt=""></p><h3 id="使用-Express">使用 Express</h3><p>对于 NodeJS 开发，可以使用 Express 的 <code>cors</code> 中间件实现跨域反向代理。</p><p><a href="https://expressjs.com/en/resources/middleware/cors.html">https://expressjs.com/en/resources/middleware/cors.html</a></p><figure class="highlight javascript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> express</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> require</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"express"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> cors</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> require</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"cors"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E5C07B">createProxyMiddleware</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#56B6C2">=</span><span style="color:#61AFEF"> require</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"http-proxy-middleware"</span><span style="color:#ABB2BF">);</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> app</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> express</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#E5C07B">app</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">use</span><span style="color:#ABB2BF">(</span><span style="color:#61AFEF">cors</span><span style="color:#ABB2BF">());</span></span><span class="line"><span style="color:#E5C07B">app</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">use</span><span style="color:#ABB2BF">(</span></span><span class="line"><span style="color:#61AFEF">  createProxyMiddleware</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#61AFEF">    router</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">req</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#C678DD"> new</span><span style="color:#61AFEF"> URL</span><span style="color:#ABB2BF">(</span><span style="color:#E5C07B">req</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">path</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">substring</span><span style="color:#ABB2BF">(</span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">)),</span></span><span class="line"><span style="color:#61AFEF">    pathRewrite</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">path</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">req</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#C678DD"> new</span><span style="color:#61AFEF"> URL</span><span style="color:#ABB2BF">(</span><span style="color:#E5C07B">req</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">path</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">substring</span><span style="color:#ABB2BF">(</span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">)).</span><span style="color:#E06C75">pathname</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    changeOrigin</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    logger</span><span style="color:#ABB2BF">: </span><span style="color:#E06C75">console</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;)</span></span><span class="line"><span style="color:#ABB2BF">);</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">app</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">listen</span><span style="color:#ABB2BF">(</span><span style="color:#D19A66">8088</span><span style="color:#ABB2BF">, () </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#E5C07B">  console</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">info</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"proxy server is running on port 8088"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><h3 id="使用-nginx">使用 nginx</h3><p>这里以 nginx 为例，给出下面的参考配置：</p><h4 id="前端和后端都在同一个域名">前端和后端都在同一个域名</h4><figure class="highlight nginx"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">server</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#C678DD">    listen </span><span style="color:#D19A66">80</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">    server_name </span><span style="color:#ABB2BF">a.com;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">    location</span><span style="color:#ABB2BF"> / &#123;</span></span><span class="line"><span style="color:#C678DD">        root </span><span style="color:#ABB2BF">html;</span></span><span class="line"><span style="color:#C678DD">        index </span><span style="color:#ABB2BF">index.html index.htm;</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">    location</span><span style="color:#ABB2BF"> /api &#123;</span></span><span class="line"><span style="color:#C678DD">        proxy_pass </span><span style="color:#ABB2BF">http://api:8000;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">        # proxy_pass http://api:8000/;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        # HTTP 协议版本和连接设置</span></span><span class="line"><span style="color:#C678DD">        proxy_http_version </span><span style="color:#D19A66">1.1</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">Connection </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        # 传递客户端信息</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">host</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Real-IP $</span><span style="color:#E06C75">remote_addr</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Forwarded-For $</span><span style="color:#E06C75">proxy_add_x_forwarded_for</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Forwarded-Proto $</span><span style="color:#E06C75">scheme</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><h4 id="前端和后端不在同一个域名">前端和后端不在同一个域名</h4><figure class="highlight nginx"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">server</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#C678DD">    listen </span><span style="color:#D19A66">80</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">    server_name </span><span style="color:#ABB2BF">a.com;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">    location</span><span style="color:#ABB2BF"> / &#123;</span></span><span class="line"><span style="color:#C678DD">        root </span><span style="color:#ABB2BF">html;</span></span><span class="line"><span style="color:#C678DD">        index </span><span style="color:#ABB2BF">index.html index.htm;</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">server</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#C678DD">    listen </span><span style="color:#D19A66">80</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">    server_name </span><span style="color:#ABB2BF">api.a.com;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">    location</span><span style="color:#ABB2BF"> / &#123;</span></span><span class="line"><span style="color:#C678DD">        proxy_pass </span><span style="color:#ABB2BF">http://api:8000;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        # HTTP 协议版本和连接设置</span></span><span class="line"><span style="color:#C678DD">        proxy_http_version </span><span style="color:#D19A66">1.1</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">Connection </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        # 传递客户端信息</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">host</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Real-IP $</span><span style="color:#E06C75">remote_addr</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Forwarded-For $</span><span style="color:#E06C75">proxy_add_x_forwarded_for</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        proxy_set_header </span><span style="color:#ABB2BF">X-Forwarded-Proto $</span><span style="color:#E06C75">scheme</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        # CORS 设置</span></span><span class="line"><span style="color:#C678DD">        add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Origin </span><span style="color:#98C379">"http://a.com"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">        add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Credentials </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">        # OPTIONS 预检请求需要完整的 CORS 头部</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> ($</span><span style="color:#E06C75">request_method</span><span style="color:#ABB2BF"> = </span><span style="color:#98C379">'OPTIONS'</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#C678DD">            add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Origin </span><span style="color:#98C379">"http://a.com"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">            add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Methods </span><span style="color:#98C379">"GET, POST, PUT, DELETE, OPTIONS"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">            add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Headers </span><span style="color:#98C379">"Content-Type, Authorization"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">            add_header </span><span style="color:#ABB2BF">Access-Control-Allow-Credentials </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">            return</span><span style="color:#D19A66"> 204</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">        &#125;</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>上述代码仅供参考，请根据实际情况调整。注意 NGINX 中的 <code>if is evil</code>，需要测试是否能够正常工作。</p><h3 id="使用-Vite">使用 Vite</h3><p>在项目的<strong>开发环境</strong>中，如果遇到跨域问题，可以使用 Vite 对接口进行反代。</p><p>详情请看官方文档：<a href="https://cn.vite.dev/config/server-options.html#server-proxy">https://cn.vite.dev/config/server-options.html#server-proxy</a></p><figure class="highlight javascript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#61AFEF"> defineConfig</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">  server</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    proxy</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 字符串简写写法：</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // http://localhost:5173/foo</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // -> http://localhost:4567/foo</span></span><span class="line"><span style="color:#98C379">      "/foo"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"http://localhost:4567"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 带选项写法：</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // http://localhost:5173/api/bar</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // -> http://jsonplaceholder.typicode.com/bar</span></span><span class="line"><span style="color:#98C379">      "/api"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">        target</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"http://jsonplaceholder.typicode.com"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">        changeOrigin</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#61AFEF">        rewrite</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">path</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#E5C07B"> path</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">replace</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">/</span><span style="color:#C678DD">^</span><span style="color:#56B6C2">\/</span><span style="color:#E06C75">api/</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">),</span></span><span class="line"><span style="color:#ABB2BF">      &#125;,</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 正则表达式写法：</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // http://localhost:5173/fallback/</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // -> http://jsonplaceholder.typicode.com/</span></span><span class="line"><span style="color:#98C379">      "^/fallback/.*"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">        target</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"http://jsonplaceholder.typicode.com"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">        changeOrigin</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#61AFEF">        rewrite</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">path</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#E5C07B"> path</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">replace</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">/</span><span style="color:#C678DD">^</span><span style="color:#56B6C2">\/</span><span style="color:#E06C75">fallback/</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">),</span></span><span class="line"><span style="color:#ABB2BF">      &#125;,</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 使用 proxy 实例</span></span><span class="line"><span style="color:#98C379">      "/api"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">        target</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"http://jsonplaceholder.typicode.com"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">        changeOrigin</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#61AFEF">        configure</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">proxy</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">options</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">          // proxy 是 'http-proxy' 的实例</span></span><span class="line"><span style="color:#ABB2BF">        &#125;,</span></span><span class="line"><span style="color:#ABB2BF">      &#125;,</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 代理 websockets 或 socket.io 写法：</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // ws://localhost:5173/socket.io</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // -> ws://localhost:5174/socket.io</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 在使用 `rewriteWsOrigin` 时要特别谨慎，因为这可能会让</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 代理服务器暴露在 CSRF 攻击之下</span></span><span class="line"><span style="color:#98C379">      "/socket.io"</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">        target</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"ws://localhost:5174"</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">        ws</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">        rewriteWsOrigin</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">      &#125;,</span></span><span class="line"><span style="color:#ABB2BF">    &#125;,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><h2 id="总结">总结</h2><pre class="mermaid">graph TD    A[前端遇到跨域问题] --> B{是否能更改后端？}    B --是--> C{浏览器是否支持跨域？}    B --否--> D[反向代理]    C --支持--> E[CORS]    C --不支持--> F[JSONP]</pre>]]>
    </content>
    <id>https://www.xrgzs.top/posts/cors</id>
    <link href="https://www.xrgzs.top/posts/cors"/>
    <published>2025-07-08T11:30:00.000Z</published>
    <summary>
      <![CDATA[<p>跨域问题是 Web 开发中经常遇到的一个挑战，解决方法多样，例如使用反向代理、CORS、JSONP 方案。本文将为大家深入探讨一下跨域方案的具体实现。</p>
<h2 id="同源策略">同源策略</h2>
<p>浏览器通过实施<strong>同源策略</strong>（S]]>
    </summary>
    <title>跨域问题：CORS、JSONP 与反向代理</title>
    <updated>2025-07-08T11:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="OpenList" scheme="https://www.xrgzs.top/tags/openlist/"/>
    <category term="Rclone" scheme="https://www.xrgzs.top/tags/rclone/"/>
    <content>
      <![CDATA[<p>用 Rclone 通过 WebDAV 挂载 OpenList 实现文件同步，是目前唯一好用的方案。相当于使用 Rclone 远控 OpenList，源目录和目标目录都是 OpenList，在 OpenList 服务端进行复制，你可以挂在流量多的 VPS 上，不会大量消耗你运行 Rclone 机器上的流量。</p><h2 id="安装-Rclone">安装 Rclone</h2><p><a href="https://rclone.org/install/">https://rclone.org/install/</a></p><p>一般包管理器都带 Rclone，可以直接 <code>apt</code>、<code>pacman</code>、<code>brew</code>、<code>scoop</code> 等安装。</p><h2 id="添加-WebDAV">添加 WebDAV</h2><p>需要在 OpenList 的用户设置中放开用户的 WebDAV 权限，建议为 WebDAV 专门创建一个用户。</p><p>然后参考：</p><p><a href="https://rclone.org/webdav/">https://rclone.org/webdav/</a></p><p>用 <code>rclone config</code> 一步一步添加即可，服务器地址填写你的 OpenList 地址 + <code>/dav</code>，如：<code>http://your-openlist/dav</code>。</p><p>配置完后注意保管好你的 <code>rclone.conf</code>。</p><h2 id="使用-Rclone">使用 Rclone</h2><p>在终端中输入以下命令，或者将其写成脚本然后执行：</p><h3 id="复制">复制</h3><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">rclone</span><span style="color:#D19A66"> --progress</span><span style="color:#D19A66"> --timeout</span><span style="color:#D19A66"> 0</span><span style="color:#D19A66"> --transfers</span><span style="color:#D19A66"> 1</span><span style="color:#D19A66"> --ignore-existing</span><span style="color:#D19A66"> --tpslimit</span><span style="color:#D19A66"> 0.5</span><span style="color:#98C379"> copy</span><span style="color:#98C379"> openlist:源路径</span><span style="color:#98C379"> openlist:目标路径</span></span></code></pre></td></tr></tbody></table></figure><h3 id="同步">同步</h3><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">rclone</span><span style="color:#D19A66"> --progress</span><span style="color:#D19A66"> --timeout</span><span style="color:#D19A66"> 0</span><span style="color:#D19A66"> --transfers</span><span style="color:#D19A66"> 1</span><span style="color:#D19A66"> --tpslimit</span><span style="color:#D19A66"> 0.5</span><span style="color:#98C379"> sync</span><span style="color:#98C379"> openlist:源路径</span><span style="color:#98C379"> openlist:目标路径</span></span></code></pre></td></tr></tbody></table></figure><h3 id="删除">删除</h3><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">rclone</span><span style="color:#D19A66"> --progress</span><span style="color:#D19A66"> --timeout</span><span style="color:#D19A66"> 0</span><span style="color:#D19A66"> --transfers</span><span style="color:#D19A66"> 1</span><span style="color:#D19A66"> --tpslimit</span><span style="color:#D19A66"> 0.5</span><span style="color:#98C379"> delete</span><span style="color:#98C379"> openlist:源路径</span><span style="color:#98C379"> openlist:目标路径</span></span></code></pre></td></tr></tbody></table></figure><h2 id="调优">调优</h2><p>部分网盘可能需要添加以下参数：</p><ul><li><p><code>--ignore-times</code>：忽略文件修改时间，强制同步，适合同步大量实时性的文件的情况</p></li><li><p><code>--ignore-existing</code>：跳过已经存在的文件，部分网盘如果不加此选项会出现多个同名文件</p></li><li><p><code>--delete-before</code>：同步前删除已经存在的文件，部分网盘如果不加此选项会出现文件冲突导致无法上传</p></li><li><p><code>--tpslimit 0.5</code>：限制文件请求速率，避免请求速度过快导致网盘账号、IP 被封</p></li><li><p><code> --max-size 8G</code>：限制上传的最大文件大小，避免超出网盘限制导致上传失败</p></li><li><p><code> --min-size 100B</code>：限制上传的最小文件大小，避免上传过小文件（大小为 0）导致网盘风控</p></li><li><p><code>-vv</code>：显示详细的调试信息，方便排查问题</p></li></ul><h2 id="自动同步">自动同步</h2><p>其实可以复制以上两点粘贴给 AI 让它给你生成脚本，肯定比我给你的好。</p><p>或者直接给你的同步命令写死也行，避免各种变量绕的头晕。</p><p>然后使用各种定时工具让它自动执行，最好能记录执行日志方便后期排错，比如 1Panel / BT 的计划任务、青龙等。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/openlist-sync</id>
    <link href="https://www.xrgzs.top/posts/openlist-sync"/>
    <published>2025-07-01T18:40:13.000Z</published>
    <summary>用 Rclone 通过 WebDAV 挂载 OpenList 实现文件同步，是目前唯一好用的方案</summary>
    <title>OpenList + Rclone 文件同步方案</title>
    <updated>2025-07-01T18:40:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="宽带" scheme="https://www.xrgzs.top/tags/%E5%AE%BD%E5%B8%A6/"/>
    <content>
      <![CDATA[<html><head><script src="https://s4.zstatic.net/ajax/libs/artplayer/5.2.2/artplayer.min.js"></script></head><body><h2 id="背景">背景</h2><p>自从国内这些运营商开始打击 PCDN 后，整个个网络质量就越来越差。尤其是那个什么流量结算，让跨运营商、跨地区的网络变得十分难用。</p><p>不少人认为运营商不能以打击 PCDN 为由限制 NAS 用户备份文件。对于这种情况，本人也是见怪不怪了。其实，这个时候对于运营商来说，不管你跑不跑 PCDN，只要是上传大的，对他流量结算的影响都是一样的。见过很多投诉解开了，回去跑两天 BT / PT、NAS 备份、直播，又给封了的。所以不用抱着侥幸心理，只要这个流量结算存在，不管你跑不跑 PCDN，你上传到省外的流量那么大，不封你封谁。</p><p>众所周知，狗子经常使用网盘备份大量文件，属于运营商口中的“上传大户”，换哪家来了都受不了。反正狗子不跑 PCDN / BT / PT，也没有跑的意义，100M 小水管连狗子自己都不够用，更不会去弄个 PCDN 增加连接数去恶心自己的用网体验，最后连电费都跑不回本。而且就算是当时被关 IPv6 + 改 NAT4 的时候，对狗子的使用也没有任何影响，该备份的照样上传，速度也能够跑满，组网也能直连（其他的节点是 NAT1），远程串流一点都不卡，后面倒是投诉解封恢复了。</p><p>反倒是后面出的这个限速 5M 的政策，属实给狗子恶心坏了。本人最近有串流的需求，其实不介意中转，但这个串流即使中转也只有 5M 的概念就是连串流 720P 低画质都会卡，直接没法用，就不得不折腾一下了。目前还没到投诉的地步，毕竟测速测不出来，也找不到理由。当然走另外的协议也不是不行，就是要折腾一堆七七八八的东西，搞不好要自己写串流客户端之类的，或者是电脑后台挂一个命令窗口，用起来贼膈应。不然为了串流只能再开一条宽带 / CPE 了。</p><p><a href="https://v2ex.com/t/1118730">https://v2ex.com/t/1118730</a></p><h2 id="折腾">折腾</h2><p>当时是用 <code>iperf3</code> 测速，发现速度不对，换了好多个组网软件，全都不对劲。用各种测速测上行都能跑满，上传知名的网盘也可以跑满，就是传私人的服务跑不满。最抽象的是那个运营商的网盘不在白名单里面就离谱。折腾半天发现是运营商的问题，去网上一搜发现这种是普遍现象。没过几天，狗子的 B 站首页就天天被各种限速的案例刷屏了，哈哈。</p><p>不过当时发现 HTTP 没有出现限速的问题，就将上传的地址改成 HTTP，虽然不安全，但是能跑满。试了只要套 SSL/TLS 就限速，HTTP + Websocket 也是不行的。据说部分地区又是反过来，不知道是不是听反指令了，哈哈。</p><p>后面无意间发现往 VPS 上传数据的时候发现 SSH 能跑满，然后发现 FRP 支持用 SSH，试了下，确实可以跑满，而且超级安全，就给一些异地备份的工作改用这套传输方案，缺点是 SSH 加解密传输超级吃 CPU。</p><p>然后平时上传大文件的话就分流到另一条没限速的宽带。反正根据木桶效应，虽然会比现在这条宽带慢一点，毕竟那边只给 30~50M 上行，但肯定是要比被限制的 5M 要快得多，至少快 3 倍以上。如果没有宽带的话，各大云厂商也有一些大带宽的服务器，可以了解一下，这些服务器通常都有三网 BGP 接入，而且都是运营商的大客户，一般不会轻易限速（遇到蝗虫邻居另当别论），便宜一般是签了“对赌”协议。为了避免推广嫌疑，这里就不说名称了。而且需要注意使用限制，建议到手 dd 系统。</p><p>说回到工具。其实狗子很早就尝试过给 OpenWrt 编译 <code>nf_deaf</code> 了，但是没用，看了下它那个默认 payload 是 FTP 协议，估计 FTP 没在白名单里面，狗子这边测试速度还是一样的垃。网络上发现有些人还是可以使用这个默认的 payload 突破上行的，不然狗子差点以为这个工具就是哄人的。</p><p><a href="https://v2ex.com/t/1120910">https://v2ex.com/t/1120910</a></p><p><a href="https://github.com/LGA1150/nf_deaf">https://github.com/LGA1150/nf_deaf</a></p><p>至于 <code>udp2raw</code>，由于这玩意是是 C/S 模式，双方都要使用，直接放弃。</p><p><img src="fakehttp/3aff3975cf4dc1e0cef2db6e6452bd40.png" alt=""></p><p>然后最近又出现了 FakeHTTP、FakeSIP，打算试用一下，最好能够解决问题。</p><p><a href="https://v2ex.com/t/1135702">https://v2ex.com/t/1135702</a></p><p><a href="https://github.com/MikeWang000000/FakeHTTP">https://github.com/MikeWang000000/FakeHTTP</a></p><p><a href="https://github.com/MikeWang000000/FakeSIP">https://github.com/MikeWang000000/FakeSIP</a></p><h2 id="前提">前提</h2><p>首先是你的宽带要已经被限速。没被限速但是要访问被限速的，也可以试试看。</p><ul><li>这里说的 5M 指的是 5 Mbps，单位是比特不是字节，如果你的上传速度是 5 MBytes/s，相当于是 50M 的上行，为正常现象。国内宽带 1000M 配 30M 上行都算正常。</li></ul><p>另外常见的限速案例大致分两种：</p><ul><li>一种是高科技 QoS，基于 DPI 深度包检测，白名单外限速，那你就需要知道你用的哪些网站没有被限速；</li><li>一种是胡子眉毛一把抓，所有流量统统限速，有的案例甚至用运营商的自己省内的测速工具都测不达标，这种情况下就要么投诉、要么换网吧。</li></ul><p>当然还有特殊情况，比如限制连接数、DNS 解析等，反正都是低劣的手段。</p><p>此外对于路由器的系统也是有一定的要求，最好是 OpenWrt 系统。建议使用软路由，且确保 CPU 性能足够。</p><p>如果你的路由器、服务器支持开启 BBR 加速，据说有效果，你也可以开启试试看，反正狗子早就开了，没效果。</p><p><a href="https://mp.weixin.qq.com/s/wsqbU0b1PVIlN5Oz-glafA">https://mp.weixin.qq.com/s/wsqbU0b1PVIlN5Oz-glafA</a></p><h2 id="原理">原理</h2><blockquote><p>在 TCP 三次握手之后，立即发送一个 HTTP GET 请求，Host 为指定域名。使用 NFQUEUE 实现。</p></blockquote><p>在 TCP 连接建立时，使用较小的 TTL 发送 HTTP 特征。HTTP 特征由于 TTL 值很低，被半路丢弃，目标服务器收不到，所以不需要任何处理。这样既通过了运营商的检测，又不会到达服务器。</p><p>其实这个相当骗运营商说是你在用 HTTP 协议请求一个白名单内的网站。</p><p>另外要说明一下这个 FakeHTTP 不是内核模块，这点和 <code>nf_deaf</code> 不太一样。它的原理跟那些透明代理插件有点像，走的是 <code>iptables</code> 之类的防火墙。</p><p>FakeHTTP 的一个好处是不要求用户编译，不然 OpenWrt 用的 C 库是 <code>musl</code> 不是 <code>glibc</code>，直接在 Ubuntu 这种下面用 <code>gcc</code> 编译的用不了，哪怕都是 <code>x86_64</code>，你还要去折腾 OpenWrt 的那套编译工具链，编译完还要配置防火墙，各种掩码，头都给你搞大。FakeHTTP 就没有这种麻烦，防火墙那些都会自动给你调好，等稳定了说不定哪天直接合入 OpenWrt 软件库，到时候连依赖安装都没这么麻烦。</p><p>FakeSIP 和 FakeHTTP 原理类似，但作用于 UDP，模拟视频会议类软件用的 SIP 协议。</p><h2 id="使用">使用</h2><p><a href="https://github.com/MikeWang000000/FakeHTTP/wiki">https://github.com/MikeWang000000/FakeHTTP/wiki</a></p><p>首先，打开路由器的 SSH，然后用 SSH 工具连接到你的路由器。</p><p>执行命令，安装以下内核模块与 <code>iptables</code> 扩展。现在新一点的 OpenWrt 用的是 <code>nftables</code>，因此这里以 <code>nftables</code> 为例。如果你是 <code>iptables</code>，那么以下命令不适合你。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">opkg</span><span style="color:#98C379"> update</span></span><span class="line"><span style="color:#61AFEF">opkg</span><span style="color:#98C379"> install</span><span style="color:#98C379"> nftables</span><span style="color:#98C379"> kmod-nft-queue</span></span></pre></td></tr></tbody></table></figure><p>进入 <a href="https://github.com/MikeWang000000/FakeHTTP/releases">Releases</a> 页面，下载对应 CPU 架构的压缩包。解压后即可直接执行 <code>fakehttp</code> 文件。</p><p>这里直接用命令下载和安装。</p><p>FakeHTTP:</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -L</span><span style="color:#98C379"> https://gh.xrgzs.top/https://github.com/MikeWang000000/FakeHTTP/releases/latest/download/fakehttp-linux-x86_64.tar.gz</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> fakehttp.tar.gz</span></span><span class="line"><span style="color:#61AFEF">tar</span><span style="color:#98C379"> xzvf</span><span style="color:#98C379"> fakehttp.tar.gz</span></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> ./fakehttp-linux-x86_64/fakehttp</span></span><span class="line"><span style="color:#61AFEF">./fakehttp-linux-x86_64/fakehttp</span></span></pre></td></tr></tbody></table></figure><p>FakeSIP:</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -L</span><span style="color:#98C379"> https://gh.xrgzs.top/https://github.com/MikeWang000000/FakeSIP/releases/latest/download/fakesip-linux-x86_64.tar.gz</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> fakesip.tar.gz</span></span><span class="line"><span style="color:#61AFEF">tar</span><span style="color:#98C379"> xzvf</span><span style="color:#98C379"> fakesip.tar.gz</span></span><span class="line"><span style="color:#61AFEF">chmod</span><span style="color:#98C379"> +x</span><span style="color:#98C379"> ./fakesip-linux-x86_64/fakesip</span></span><span class="line"><span style="color:#61AFEF">./fakesip-linux-x86_64/fakesip</span></span></pre></td></tr></tbody></table></figure><p>应该就能运行了，以下是执行参数，以实际的为准：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">Usage:</span><span style="color:#98C379"> fakehttp</span><span style="color:#ABB2BF"> [options]</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">接口选项（Interface</span><span style="color:#98C379"> Options）:</span></span><span class="line"><span style="color:#61AFEF">  -a</span><span style="color:#98C379">                 作用于所有网络接口（忽略</span><span style="color:#D19A66"> -i）</span></span><span class="line"><span style="color:#61AFEF">  -i</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">interfac</span><span style="color:#ABB2BF">e&gt;     </span><span style="color:#98C379">作用于指定的网络接口</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">负载选项（Payload</span><span style="color:#98C379"> Options）:</span></span><span class="line"><span style="color:#61AFEF">  -b</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">fil</span><span style="color:#ABB2BF">e&gt;          </span><span style="color:#98C379">使用二进制文件作为</span><span style="color:#98C379"> TCP</span><span style="color:#98C379"> 负载</span></span><span class="line"><span style="color:#61AFEF">  -e</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">hostnam</span><span style="color:#ABB2BF">e&gt;      </span><span style="color:#98C379">用于</span><span style="color:#98C379"> HTTPS</span><span style="color:#98C379"> 混淆的主机名</span></span><span class="line"><span style="color:#61AFEF">  -h</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">hostnam</span><span style="color:#ABB2BF">e&gt;      </span><span style="color:#98C379">用于</span><span style="color:#98C379"> HTTP</span><span style="color:#98C379"> 混淆的主机名</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">通用选项（General</span><span style="color:#98C379"> Options）:</span></span><span class="line"><span style="color:#61AFEF">  -0</span><span style="color:#98C379">                 处理入站连接</span></span><span class="line"><span style="color:#61AFEF">  -1</span><span style="color:#98C379">                 处理出站连接</span></span><span class="line"><span style="color:#61AFEF">  -4</span><span style="color:#98C379">                 处理</span><span style="color:#98C379"> IPv4</span><span style="color:#98C379"> 连接</span></span><span class="line"><span style="color:#61AFEF">  -6</span><span style="color:#98C379">                 处理</span><span style="color:#98C379"> IPv6</span><span style="color:#98C379"> 连接</span></span><span class="line"><span style="color:#61AFEF">  -d</span><span style="color:#98C379">                 以守护进程方式运行</span></span><span class="line"><span style="color:#61AFEF">  -k</span><span style="color:#98C379">                 杀掉正在运行的进程</span></span><span class="line"><span style="color:#61AFEF">  -s</span><span style="color:#98C379">                 启用静默模式</span></span><span class="line"><span style="color:#61AFEF">  -w</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">fil</span><span style="color:#ABB2BF">e&gt;          </span><span style="color:#98C379">将日志写入</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">fil</span><span style="color:#ABB2BF">e&gt;</span><span style="color:#98C379">，而不是标准错误输出</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">高级选项（Advanced</span><span style="color:#98C379"> Options）:</span></span><span class="line"><span style="color:#61AFEF">  -f</span><span style="color:#98C379">                 跳过防火墙规则</span></span><span class="line"><span style="color:#61AFEF">  -g</span><span style="color:#98C379">                 禁用跃点数估算</span></span><span class="line"><span style="color:#61AFEF">  -m</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">mar</span><span style="color:#ABB2BF">k&gt;          </span><span style="color:#98C379">用于绕过队列的</span><span style="color:#98C379"> fwmark</span></span><span class="line"><span style="color:#61AFEF">  -n</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">numbe</span><span style="color:#ABB2BF">r&gt;        </span><span style="color:#98C379">Netfilter</span><span style="color:#98C379"> 队列编号</span></span><span class="line"><span style="color:#61AFEF">  -r</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">repea</span><span style="color:#ABB2BF">t&gt;        </span><span style="color:#98C379">重复生成的数据包</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">repea</span><span style="color:#ABB2BF">t&gt; </span><span style="color:#98C379">次</span></span><span class="line"><span style="color:#61AFEF">  -t</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">tt</span><span style="color:#ABB2BF">l&gt;           </span><span style="color:#98C379">生成数据包的</span><span style="color:#98C379"> TTL</span></span><span class="line"><span style="color:#61AFEF">  -x</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">mas</span><span style="color:#ABB2BF">k&gt;          </span><span style="color:#98C379">设置</span><span style="color:#98C379"> fwmark</span><span style="color:#98C379"> 的掩码</span></span><span class="line"><span style="color:#61AFEF">  -y</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">pc</span><span style="color:#ABB2BF">t&gt;           </span><span style="color:#98C379">将</span><span style="color:#98C379"> TTL</span><span style="color:#98C379"> 动态提升至估算跃点数的</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">pc</span><span style="color:#ABB2BF">t&gt;</span><span style="color:#98C379">%</span></span><span class="line"><span style="color:#61AFEF">  -z</span><span style="color:#98C379">                 使用</span><span style="color:#98C379"> iptables</span><span style="color:#98C379"> 命令而不是</span><span style="color:#98C379"> nft</span></span></pre></td></tr></tbody></table></figure><p>接下来找到你上网的网络接口（WAN 口）。狗子这边是<strong>光猫拨号</strong>，因此没有 <code>pppoe-wan</code>，而是 <code>eth1</code>。请以实际的为准。</p><p>然后这里的主机名对应没有限速的域名。生成命令如下：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">./fakehttp-linux-x86_64/fakehttp</span><span style="color:#D19A66"> -h</span><span style="color:#98C379"> www.speedtest.cn</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> eth1</span></span></pre></td></tr></tbody></table></figure><p>执行命令，此时屏幕会输出一堆包含 IP 连接信息的日志，说明成功，否则就是环境不对，需要重新安装依赖。</p><p>如果需要设置成开机启动，可以进入 luci <code>http://192.168.1.1/cgi-bin/luci/admin/system/startup</code>，在 本地启动脚本 中加入以下命令，在开机时使用 <code>-d</code> 参数运行即可，程序会在后台运行。</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Put your custom commands here that should be executed once</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># the system init finished. By default this file does nothing.</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">/root/fakehttp-linux-x86_64/fakehttp</span><span style="color:#D19A66"> -h</span><span style="color:#98C379"> www.speedtest.cn</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> eth1</span><span style="color:#D19A66"> -d</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># /root/fakesip-linux-x86_64/fakesip -i eth1 -t 4 -d</span></span><span class="line"></span><span class="line"><span style="color:#56B6C2">exit</span><span style="color:#D19A66"> 0</span></span><span class="line"></span></pre></td></tr></tbody></table></figure><p>如果要关闭后台运行，可以使用 <code>-k</code> 参数运行，会自动删除添加的防火墙规则。</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">/root/fakehttp-linux-x86_64/fakehttp</span><span style="color:#D19A66"> -k</span></span></pre></td></tr></tbody></table></figure><h2 id="测速">测速</h2><p>终于到了喜闻乐见的测速环节。</p><p>首先要在测速服务器上启用 <code>iperf3</code>：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">iperf3</span><span style="color:#D19A66"> -s</span></span></pre></td></tr></tbody></table></figure><p>然后在电脑上连接服务器进行测速。</p><p>这里服务器是国内跨省阿里云的，那边入站应该是共享 100M，这里单线程能跑到 50M 左右，已经不错了，用 <code>-P 8</code> 测了下多线程能跑满。</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&gt; iperf3 -c &lt;服务器IP&gt;</span></span><span class="line"><span style="color:#61AFEF">Connecting</span><span style="color:#98C379"> to</span><span style="color:#98C379"> host</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">服务器I</span><span style="color:#ABB2BF">P&gt;</span><span style="color:#98C379">,</span><span style="color:#98C379"> port</span><span style="color:#D19A66"> 5201</span></span><span class="line"><span style="color:#ABB2BF">[  5] </span><span style="color:#C678DD">local</span><span style="color:#E06C75"> 10</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">0</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">1</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">123</span><span style="color:#E06C75"> port</span><span style="color:#E06C75"> 12782</span><span style="color:#E06C75"> connected</span><span style="color:#E06C75"> to</span><span style="color:#ABB2BF"> &lt;服务器IP&gt; </span><span style="color:#E06C75">port</span><span style="color:#E06C75"> 5201</span></span><span class="line"><span style="color:#ABB2BF">[ ID] Interval           Transfer     Bitrate</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-1.01   sec  11.9 MBytes  99.1 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   1.01-2.01   sec  15.1 MBytes   126 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   2.01-3.01   sec  9.00 MBytes  75.9 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   3.01-4.01   sec  7.88 MBytes  66.3 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   4.01-5.01   sec  6.25 MBytes  52.0 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   5.01-6.01   sec  4.12 MBytes  34.8 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   6.01-7.01   sec  4.50 MBytes  37.9 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   7.01-8.01   sec  4.88 MBytes  40.9 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   8.01-9.01   sec  3.62 MBytes  30.1 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   9.01-10.01  sec  3.75 MBytes  31.5 Mbits/sec</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span></span><span class="line"><span style="color:#ABB2BF">[ ID] Interval           Transfer     Bitrate</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-10.01  sec  71.0 MBytes  59.5 Mbits/sec                  sender</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-10.05  sec  69.3 MBytes  57.8 Mbits/sec                  receiver</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">iperf</span><span style="color:#98C379"> Done.</span></span></pre></td></tr></tbody></table></figure><p>在路由器上使用 Ctrl+C 结束运行 FakeHTTP ，然后再在电脑上用 <code>iperf3</code> 测速：</p><figure class="highlight bash"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&gt; iperf3 -c &lt;服务器IP&gt;</span></span><span class="line"><span style="color:#61AFEF">Connecting</span><span style="color:#98C379"> to</span><span style="color:#98C379"> host</span><span style="color:#ABB2BF"> &lt;</span><span style="color:#98C379">服务器I</span><span style="color:#ABB2BF">P&gt;</span><span style="color:#98C379">,</span><span style="color:#98C379"> port</span><span style="color:#D19A66"> 5201</span></span><span class="line"><span style="color:#ABB2BF">[  5] </span><span style="color:#C678DD">local</span><span style="color:#E06C75"> 10</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">0</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">1</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">123</span><span style="color:#E06C75"> port</span><span style="color:#E06C75"> 12782</span><span style="color:#E06C75"> connected</span><span style="color:#E06C75"> to</span><span style="color:#ABB2BF"> &lt;服务器IP&gt; </span><span style="color:#E06C75">port</span><span style="color:#E06C75"> 5201</span></span><span class="line"><span style="color:#ABB2BF">[ ID] Interval           Transfer     Bitrate</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-1.01   sec  3.38 MBytes  28.1 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   1.01-2.00   sec   512 KBytes  4.22 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   2.00-3.01   sec   512 KBytes  4.16 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   3.01-4.00   sec   640 KBytes  5.27 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   4.00-5.01   sec   640 KBytes  5.21 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   5.01-6.01   sec   640 KBytes  5.27 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   6.01-7.00   sec   640 KBytes  5.25 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   7.00-8.00   sec   512 KBytes  4.20 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   8.00-9.01   sec   640 KBytes  5.19 Mbits/sec</span></span><span class="line"><span style="color:#ABB2BF">[  5]   9.01-10.01  sec   640 KBytes  5.27 Mbits/sec</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span><span style="color:#98C379"> -</span></span><span class="line"><span style="color:#ABB2BF">[ ID] Interval           Transfer     Bitrate</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-10.01  sec  8.62 MBytes  7.23 Mbits/sec                  sender</span></span><span class="line"><span style="color:#ABB2BF">[  5]   0.00-10.06  sec  6.71 MBytes  5.59 Mbits/sec                  receiver</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">iperf</span><span style="color:#98C379"> Done.</span></span></pre></td></tr></tbody></table></figure><p>这个速度肯定有鬼，而且狗子是半夜三更测速，还不是用网高峰期。设置多少线程都没有用，总共加起来还是 5M。</p><p>对比说明，这个工具对于狗子的宽带在一定程度上是有用的。<s>但经过实际测试，在狗子这对于上传 HTTPS 没有太大帮助，仅对其它的 TCP 协议有效。对于 ZeroTier 这种 UDP 协议是无用的，还需寻找 TCP 的组网工具。</s> FakeSIP 支持 UDP，实测 100M 上行 ZeroTier 能跑 30~50M（非跨网）。</p><h2 id="调优">调优</h2><p>根据原帖，作者的 TTL 默认设置为 3。这里解释一下 TTL（Time to Live，生存时间）可以理解为数据包在网络中经过路由器（网关）的最大台数，每经过一台路由器就会增加一跳，就是这么简单。</p><p>由于网间结算涉及到 “3Q”——三网互相 QOS ，跨网 Q ，跨省 Q ，UDP Q ，有些时候你的运营商也是被其它运营商限速，所以根据木桶效应，需要适当增加 TTL 跳数以便让这个 HTTP 数据包达到最后一个 QOS 路由节点。</p><p>比如你在 OpenWrt 路由器上运行设置为 3 跳，假设你是光猫拨号，也就是从你的 OpenWrt 路由器开始，光猫是第 1 跳，运营商的路由器上有 2 跳，然后就停止了，那么运营商路由的那 2 跳就可以知道你的 host 是测速网，从而实现解除限速的目的，但数据包最后到不了服务器，半路就丢了。</p><p>你可以使用 <code>nexttrace</code>、<code>mtr</code>、<code>traceroute</code> 等路由追踪工具查看具体的路由信息。根据狗子的测试，一般“干坏事”的路由很难一次就追踪出 IP 地址，只显示 <code>*</code>，需要用 MTR 多次追踪才能查到。</p><p>比如狗子这里的测试结果：</p><ul><li>对于国内服务器，设置 TTL 为 1、2 都是限速，直到设置 3 才解除，路由追踪刚好就那一跳显示不出来，MTR 查出来那一跳是一个地州的 IP，说明限狗子国内服务器速的路由就是地州的运营商；</li><li>对于某家日本 VPS，设置 TTL 为 3 都是限速，直到设置 4 才解除，路由追踪刚好就那一跳显示不出来，MTR 查出来那一跳是一个省上的 IP，说明限狗子国外服务器速的路由就是省级的运营商；</li><li>对于某家韩国 VPS，设置 TTL 为 4 都是限速，路由追踪有好几跳都显示不出来，那就重点试那几跳，直到设置 6 才解除，MTR 查出来那一跳是骨干网的 IP，说明限速的路由就是骨干网。<ul><li>像这台鸡子，说是三网优化，测试下来去程回程都一样，延迟确实不高，下行也正常，但回程的优先级肯定是排得低的</li></ul></li></ul><p>这些知识还可以在你购买的 VPS 出现 QOS 速度问题时提供参考和解决方案，当然也不是每个路由都吃测速白名单这套的。对于 iperf3 测速服务器的获取，不一定非得购买对应的服务器，某些 VPS 的买家会提供 Looking Glass Server，你可以去找找看。这里避免广告嫌疑就不放链接了，反正经常玩鸡的人应该都能找得到。</p><p>另外还有一个 <code>-r</code> 参数，可以设置重发次数。狗子遇到不是每一次测速都能跑满。这些参数需要你根据你的实际使用环境测试。目前程序属于测试阶段，需要等待作者进一步调优。目前<s>还不支持 IPv6（PR：<a href="https://github.com/MikeWang000000/FakeHTTP/issues/5">#5</a>）。</s> 已支持 IPv6。</p><p>另外发现如果这个 <code>iperf3</code> 加个 <code>-t 0</code> 参数一直跑着，即使给 FakeHTTP 停了，也不影响测速。反之，如果先测速，然后再开 FakeHTTP，那么测速就是 5M。说明只需要在连接建立的时候发那个 HTTP 欺骗即可，有点像 STUN + NAT1 的思路。然后发现这个作者就是打洞软件 Natter 的开发者，<s>果然用二次元头像的都是大佬</s>。</p><h2 id="结尾">结尾</h2><p>如果程序被优化得十分简单就可以使用，那么就会有更多的人使用这个方法来绕过限速。因为限速会影响到正常用户，快进有人到卖突破限速的路由器，搞半天还催生黑产，造成网络危险。到最后运营商不高兴，肯定会研发出新的方法来检测，最终沦为<strong>新时代多拨</strong>。</p><p>所以且用且珍惜！</p><p>更新：高位端口已寄</p><p><img src="fakehttp/743b9cec5e44f36aada117cc934047de.png" alt=""></p><p>更新：部分地区仅能提速 10 分钟。</p><p>更新：抓取 payload 的方法（转）</p><blockquote><h1>2 使用 Wireshark 抓取 payload</h1><hr><p>目前，FakeHTTP / FakeSIP 的一些特征已经被某些地区的运营商识别（例如固定的 URL、固定的 User-Agent、无 Cookie、固定的 TLS 指纹等），使用<code>-h</code> <code>-e</code>等基础参数来绕过限速的方法大多已经失效。</p><p>作为应对，用户可以自行抓取对应网站的 payload.bin 文件，并使用<code>-b</code>参数来绕过运营商更严格的限速规则。</p><p>payload 文件可能包含个人隐私，请勿分享到互联网。</p><h2 id="2-1-安装-Wireshark">2.1 安装 Wireshark</h2><p>**使用 Homebrew 安装：**打开 macOS 终端，执行<code>brew install --cask wireshark-app</code>命令，安装 Wireshark。</p><h2 id="2-2-使用-Wireshark-进行抓包">2.2 使用 Wireshark 进行抓包</h2><ol><li><p>打开 Wireshark，界面中会显示本机的网卡，双击需要抓包的网卡，即可开始抓包。如果无法判断应该抓取哪张网卡，可将光标悬停在网卡名称上，会显示对应的 IP 地址。</p></li><li><p>开始抓包后，<a href="http://xn--www-u68d898a56ax35arog9u1au88d.speedtest.cn">在浏览器中打开www.speedtest.cn</a> ，进行一次完整的测速。</p></li></ol><p>3.测速结束后，点击 Wireshark 界面左上角的红色停止按钮，结束抓包。</p><h2 id="2-3-导出-payload-bin-文件">2.3 导出 payload.bin 文件</h2><ol><li>在 Wireshark 抓包界面中，展开“Transmission Control Protocol…”，右键单击“TCP payload…”，选择“Export Packet Bytes…”，将导出的文件命名为“payload”，并与 fakehttp 程序保存在同一文件夹下。</li></ol><h2 id="2-4-使用-payload-文件运行-FakeHTTP">2.4 使用 payload 文件运行 FakeHTTP</h2><ol><li>登录到 NAS 主机的 SSH，提权，并将工作目录切换到 FakeHTTP 文件的所在路径。</li><li>在终端中执行<code>./fakehttp -b payload.bin -a</code>命令，使用<code>-b</code>参数调用 payload.bin 文件进行混淆。</li></ol></blockquote><p>更新：移动放大招，上“AI WAN”，此方法已无效：</p><p><div style="border-radius:8px;margin:1rem 0;overflow:hidden;"><div id="artplayer-container-0" style="width:100%;height:400px;"></div></div></p><p>原视频：<a href="https://www.bilibili.com/video/BV1YV1xBzEMX">https://www.bilibili.com/video/BV1YV1xBzEMX</a></p><script>document.addEventListener("DOMContentLoaded", function() {        var art0 =  new Artplayer({            container: '#artplayer-container-0',            url: 'https://uploads.xrgzs.top/media/20251105001.mp4',            poster: '',            pip: true,            setting: true,            playbackRate: true,            aspectRatio: true,            fullscreen: true,            fullscreenWeb: true,            mutex: true,            theme: getComputedStyle(document.body).getPropertyValue("--anzhiyu-main")        });        art0.on("ready", () => {        art0.autoHeight()        })        art0.on("resize", () => {        art0.autoHeight()        })    });</script></body></html>]]>
    </content>
    <id>https://www.xrgzs.top/posts/fakehttp</id>
    <link href="https://www.xrgzs.top/posts/fakehttp"/>
    <published>2025-06-06T00:55:21.000Z</published>
    <summary>
      <![CDATA[<html><head><script src="https://s4.zstatic.net/ajax/libs/artplayer/5.2.2/artplayer.min.js"></script></head><body><h2 id="背景">背景</h2>
<p>自从国]]>
    </summary>
    <title>使用 FakeHTTP 绕过运营商白名单限速 5M 上行</title>
    <updated>2025-11-05T14:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="前端" scheme="https://www.xrgzs.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="NodeJS" scheme="https://www.xrgzs.top/tags/nodejs/"/>
    <category term="JavaScript" scheme="https://www.xrgzs.top/tags/javascript/"/>
    <content>
      <![CDATA[<blockquote><p>2026 更新：不推荐使用 nvm，强烈推荐使用 <a href="https://github.com/Schniz/fnm">fnm</a>。<br>文章已修改。</p></blockquote><p>在前端开发中，Node.js 是一个重要的工具。不同项目可能需要不同版本的 Node.js，因此使用 Node.js 版本管理工具（如 nvm 或 fnm）可以帮助我们轻松切换和管理多个 Node.js 版本。本文将介绍如何安装和使用 fnm 来管理 Node.js 版本。</p><h2 id="安装">安装</h2><h3 id="Linux-macOS">Linux / macOS</h3><p>安装前请确保已经安装了 <code>curl</code> 和 <code>unzip</code>。</p><p>输入以下命令安装 fnm：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -fsSL</span><span style="color:#98C379"> https://fnm.vercel.app/install</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">bash</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 如果上面那个打不开，使用：</span></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#D19A66"> -fsSL</span><span style="color:#98C379"> https://gh.xrgzs.top/https://raw.githubusercontent.com/Schniz/fnm/refs/heads/master/.ci/install.sh</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">bash</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后，脚本会自动添加配置到你的 shell 配置文件中（如 <code>~/.bashrc</code> 或 <code>~/.zshrc</code>）。需要重新加载配置文件或者重启终端来使配置生效：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">source</span><span style="color:#98C379"> ~/.bashrc</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># source ~/.zshrc</span></span></code></pre></td></tr></tbody></table></figure><p>也可以用 Homebrew 安装：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">brew</span><span style="color:#98C379"> install</span><span style="color:#98C379"> fnm</span></span></code></pre></td></tr></tbody></table></figure><h3 id="Windows">Windows</h3><blockquote><p>注意：在Windows上，fnm可能不支持给其他无终端的应用提供Node.js安装功能，一些工具会提示找不到Node.js。一般建议在Windows上全局安装Node.js LTS，然后在VS Code的终端（PowerShell）中使用fnm来管理不同版本的Node.js。</p></blockquote><p>Windows 这边可以用 WinGet 安装，也可以用 Scoop 安装：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">winget install Schniz.fnm</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># scoop install fnm</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置终端">配置终端</h2><h3 id="Bash-Zsh">Bash / Zsh</h3><p>如果使用脚本安装，通常会自动配置好。如果没有，可以手动添加配置。</p><p>在 <code>~/.bashrc</code> 或 <code>~/.zshrc</code> 中添加：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">eval</span><span style="color:#98C379"> "$(</span><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> env </span><span style="color:#D19A66">--use-on-cd</span><span style="color:#D19A66"> --shell</span><span style="color:#98C379"> bash)"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># eval "$(fnm env --use-on-cd --shell zsh)"</span></span></code></pre></td></tr></tbody></table></figure><h4 id="安装自动补全（可选）">安装自动补全（可选）</h4><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> completions</span><span style="color:#D19A66"> --shell</span><span style="color:#98C379"> bash</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#98C379"> /usr/share/bash-completion/completions/fnm</span></span></code></pre></td></tr></tbody></table></figure><h3 id="PowerShell">PowerShell</h3><p>PowerShell 分为 Windows PowerShell 和 PowerShell Core，两个的配置文件路径不一样，需要分别配置（如果你两个都要用的话）。</p><p>查看配置地址：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">Write-Host</span><span style="color:#ABB2BF"> $PROFILE</span></span><span class="line"><span style="color:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#56B6C2">-not</span><span style="color:#ABB2BF"> (</span><span style="color:#56B6C2">Test-Path</span><span style="color:#ABB2BF"> $PROFILE)) &#123; </span><span style="color:#56B6C2">New-Item</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force &#125;</span></span></code></pre></td></tr></tbody></table></figure><p>在 <code>$PROFILE</code> 中添加：</p><figure class="highlight powershell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">fnm env </span><span style="color:#56B6C2">--use-on-</span><span style="color:#ABB2BF">cd </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">shell powershell | </span><span style="color:#56B6C2">Out-String</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Invoke-Expression</span></span></code></pre></td></tr></tbody></table></figure><h3 id="CMD">CMD</h3><p>（独家）</p><p>如果你使用 CMD，那么大概率不支持 FNM 的自动切换功能。但如果你需要在脚本中使用，可以这么写：</p><figure class="highlight cmd"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">@</span><span style="color:#C678DD">echo</span><span style="color:#61AFEF"> off</span></span><span class="line"><span style="color:#ABB2BF">fnm env --shell </span><span style="color:#C678DD">cmd</span><span style="color:#ABB2BF"> >> </span><span style="color:#E06C75">%TEMP%</span><span style="color:#ABB2BF">\fnm-env.cmd</span></span><span class="line"><span style="color:#C678DD">call</span><span style="color:#E06C75"> %TEMP%</span><span style="color:#ABB2BF">\fnm-env.cmd</span></span><span class="line"><span style="color:#C678DD">del</span><span style="color:#ABB2BF"> /q </span><span style="color:#E06C75">%TEMP%</span><span style="color:#ABB2BF">\fnm-env.cmd</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">node -v</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Node">安装 Node</h2><h3 id="换源">换源</h3><p>fnm 默认从官方服务器下载 Node.js。在中国大陆环境下，建议配置镜像以加速下载。</p><p>设置环境变量 <code>FNM_NODE_DIST_MIRROR</code>：</p><figure class="highlight bash"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">export</span><span style="color:#E06C75"> FNM_NODE_DIST_MIRROR</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"https://npmmirror.com/mirrors/node/"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># PowerShell: $env:FNM_NODE_DIST_MIRROR="https://npmmirror.com/mirrors/node/"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Windows: setx FNM_NODE_DIST_MIRROR "https://npmmirror.com/mirrors/node/"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="获取可用版本列表：">获取可用版本列表：</h3><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> list-remote</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装指定版本">安装指定版本</h3><p>一般建议装安装最新的 LTS 版本，或者你项目需要的版本：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> 24</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-PNPM">安装 PNPM</h2><p>这里我们使用 Corepack 来安装 pnpm。Corepack 允许你在 <code>package.json</code> 中指定包管理器的版本，并且会自动安装和使用指定版本的包管理器。强烈推荐使用 Corepack 来管理 pnpm，这样可以确保在不同项目中使用不同版本的 pnpm，而不需要全局安装。</p><p>在安装 pnpm 前，需要换一下 NPM 源：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">npm</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> set</span><span style="color:#98C379"> registry</span><span style="color:#98C379"> https://registry.npmmirror.com</span></span></code></pre></td></tr></tbody></table></figure><p>如果你使用的是 v26 版本的 Node.js，Corepack 需要单独安装：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">npm</span><span style="color:#98C379"> install</span><span style="color:#D19A66"> -g</span><span style="color:#98C379"> corepack</span></span></code></pre></td></tr></tbody></table></figure><p>在 Corepack 安装前需要换一下源：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">export</span><span style="color:#E06C75"> COREPACK_NPM_REGISTRY</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"https://registry.npmmirror.com"</span></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#E06C75"> NPM_CONFIG_REGISTRY</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"https://registry.npmmirror.com"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># PowerShell: $env:COREPACK_NPM_REGISTRY=$env:NPM_CONFIG_REGISTRY="https://registry.npmmirror.com"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Windows: setx COREPACK_NPM_REGISTRY "https://registry.npmmirror.com"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Windows: setx NPM_CONFIG_REGISTRY "https://registry.npmmirror.com"</span></span></code></pre></td></tr></tbody></table></figure><p>然后：</p><figure class="highlight shell"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">corepack</span><span style="color:#98C379"> enable</span><span style="color:#98C379"> pnpm</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">pnpm</span><span style="color:#D19A66"> -v</span></span></code></pre></td></tr></tbody></table></figure><h2 id="切换版本">切换版本</h2><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> use</span><span style="color:#D19A66"> 20</span></span></code></pre></td></tr></tbody></table></figure><h2 id="设置默认版本">设置默认版本</h2><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">fnm</span><span style="color:#98C379"> default</span><span style="color:#D19A66"> 20</span></span></code></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://www.xrgzs.top/posts/nvm</id>
    <link href="https://www.xrgzs.top/posts/nvm"/>
    <published>2025-05-24T02:30:50.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>2026 更新：不推荐使用 nvm，强烈推荐使用 <a href="https://github.com/Schniz/fnm">fnm</a>。<br>
文章已修改。</p>
</blockquote>
<p>在前端开发中，Node.js 是一个]]>
    </summary>
    <title>NVM / FNM 安装使用</title>
    <updated>2026-05-22T08:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>狂犬主子</name>
    </author>
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    <category term="前端" scheme="https://www.xrgzs.top/tags/%E5%89%8D%E7%AB%AF/"/>
    <category term="Markdown" scheme="https://www.xrgzs.top/tags/markdown/"/>
    <category term="Vue" scheme="https://www.xrgzs.top/tags/vue/"/>
    <category term="VitePress" scheme="https://www.xrgzs.top/tags/vitepress/"/>
    <category term="Artplayer" scheme="https://www.xrgzs.top/tags/artplayer/"/>
    <content>
      <![CDATA[<p>浏览器自带的原生播放器相信大家都用过，总体体验不怎么样。为了提高用户播放体验，我们会引入第三方播放器，比如 <a href="https://github.com/DIYgod/DPlayer">DPlayer</a>、<a href="https://github.com/zhw2590582/ArtPlayer">ArtPlayer</a> 等。由于前者一直没有维护，此处我们选型为 ArtPlayer，当然也可以换成其他播放器。</p><p>通过本文的方法你可以将 Markdown 中直接使用 <code>&lt;video&gt;</code> 元素插入的视频就可以自动替换成 ArtPlayer，这样可以在提高用户体验的同时保留原始 Markdown 文件对不同软件的兼容。</p><h2 id="Markdown-插入视频">Markdown 插入视频</h2><p>对于 VitePress 这种以 Markdown 文件驱动的站点，这里我们可以采用 HTML 的 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/video"><code>&lt;video&gt;</code> 元素</a>。</p><figure class="highlight html"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">video</span><span style="color:#D19A66"> src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"movie.mp4"</span><span style="color:#D19A66"> controls</span><span style="color:#D19A66"> width</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"100%"</span><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">video</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>或者</p><figure class="highlight html"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">video</span><span style="color:#D19A66"> controls</span><span style="color:#D19A66"> width</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"100%"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">source</span><span style="color:#D19A66"> src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"movie.webm"</span><span style="color:#D19A66"> type</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"video/webm"</span><span style="color:#ABB2BF"> /></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">source</span><span style="color:#D19A66"> src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"movie.mp4"</span><span style="color:#D19A66"> type</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"video/mp4"</span><span style="color:#ABB2BF"> /></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">video</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>这种通过 HTML 插入视频的方式最为方便，只要查看器支持渲染 Markdown 中的 HTML5 即可。尤其是我们一般写文章用的以浏览器内核（Chromium、Webkit）驱动的 Typora、VS Code、Obsidian 等，直接就可以预览。</p><p>还有一种方式是先写一个 Vue 组件，然后在 Markdown 中引用。但这种方式就有点麻烦了，因为通常你的编辑器中是不跑 Vue 的，自己搓的组件一般也无法被编辑器识别，兼容性也不好。</p><p>本文要解决的问题是如何给这个插入的视频获得更好的播放体验，因此需要将这个 <code>&lt;video&gt;</code> 元素替换成 ArtPlayer。</p><h2 id="ArtPlayer-的加载（编写-Vue-组件）">ArtPlayer 的加载（编写 Vue 组件）</h2><p>先 NPM 安装一下：</p><figure class="highlight shell"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">pnpm</span><span style="color:#98C379"> add</span><span style="color:#98C379"> artplayer</span></span></code></pre></td></tr></tbody></table></figure><p>由于 ArtPlayer 非 Web Component 组件，它的渲染原理是通过 JavaScript 代码在指定 DOM（<code>&lt;div&gt;</code>）中插入播放器的 HTML 元素。</p><p>也就是说，使用 ArtPlayer 必须从 JavaScript 创建。没有 <code>&lt;Artplayer&gt;</code> 这个元素。无法实现简单的替换。</p><p>我们肯定不想在每个 Markdown 中都写一遍 JavaScript。</p><p>好在，这里使用的是 VitePress，支持在 Markdown 中插入 Vue 组件。众所周知，Vue 组件就可以实现自定义元素的效果。这就好办了，我们可以“手搓”一个基本兼容 <code>&lt;video&gt;</code> 元素的 Vue 组件，然后全局引用不就行了？</p><p>然后给 ArtPlayer 的 Vue 组件写出来。</p><p>因为我们只需要读取视频播放地址和封面，其他信息不重要，因此可以少传入一些属性。</p><blockquote><p>这里使用组合式 API（<code>&lt;script setup&gt;</code>），<s>因为非专业前端选项式 API 看球不懂。</s> 组合式 API 更符合函数式编程的直觉，但获取一些属性就需要调用一些 hooks。</p></blockquote><p>由于我们上面提到的视频文件地址有两种方式进行定义，我们需要都进行兼容：</p><ol><li>直接 <code>&lt;video src=&quot;...&quot; /&gt;</code>，这样和 <a href="https://cn.vuejs.org/guide/essentials/component-basics.html#passing-props">Vue 组件 Props 传值</a> 的方式类似，可以直接通过属性读取。</li><li><code>&lt;video&gt;&lt;source src=&quot;aaa&quot;&gt;&lt;source src=&quot;bbb&quot;&gt;...&lt;/video&gt;</code>，这种情况就比较难受，因为里面的那个 <code>&lt;source&gt;</code> 元素涉及到 <a href="https://cn.vuejs.org/guide/components/slots.html">插槽 Slots</a> ，然后这种方式可以支持多线路。</li></ol><p>Vue 使用的是 Virtual DOM 技术，最终生成的 div 是随机的，我们可以使用 <a href="https://cn.vuejs.org/guide/essentials/template-refs.html">Vue 的模板引用 refs</a> 来获取到准确的 DOM，交给 ArtPlayer 进行渲染。</p><p>最终代码如下：</p><figure class="highlight vue"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">&#x3C;!-- src/.vitepress/theme/components/ArtVideo.vue --></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">script</span><span style="color:#D19A66"> setup</span><span style="color:#D19A66"> lang</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"ts"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> Artplayer</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "artplayer"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">onMounted</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">onUnmounted</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">ref</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">useSlots</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "vue"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> props</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> defineProps</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">  src</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    type</span><span style="color:#ABB2BF">: </span><span style="color:#E06C75">String</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    required</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">false</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    default</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#E06C75">  poster</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#E06C75">    type</span><span style="color:#ABB2BF">: </span><span style="color:#E06C75">String</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    required</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">false</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">    default</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">""</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> container</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> ref</span><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E5C07B">HTMLDivElement</span><span style="color:#ABB2BF">>();</span></span><span class="line"><span style="color:#C678DD">let</span><span style="color:#E06C75"> art</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">Artplayer</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> slots</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> useSlots</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#C678DD">const</span><span style="color:#E5C07B"> sources</span><span style="color:#56B6C2"> =</span><span style="color:#61AFEF"> ref</span><span style="color:#ABB2BF">&#x3C;&#123; </span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">string</span><span style="color:#ABB2BF">; </span><span style="color:#E06C75">type</span><span style="color:#C678DD">?</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">string</span><span style="color:#ABB2BF"> &#125;[]>([]);</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">props</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E5C07B">  sources</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">value</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">push</span><span style="color:#ABB2BF">(&#123; </span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">props</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF"> &#125;);</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">slots</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">default</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#C678DD">  const</span><span style="color:#E5C07B"> slotContent</span><span style="color:#56B6C2"> =</span><span style="color:#E5C07B"> slots</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">default</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#C678DD">  const</span><span style="color:#E5C07B"> newSources</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> slotContent</span></span><span class="line"><span style="color:#ABB2BF">    .</span><span style="color:#61AFEF">filter</span><span style="color:#ABB2BF">((</span><span style="color:#E06C75;font-style:italic">node</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#E5C07B"> node</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">type</span><span style="color:#56B6C2"> ===</span><span style="color:#98C379"> "source"</span><span style="color:#56B6C2"> &#x26;&#x26;</span><span style="color:#E5C07B"> node</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">props</span><span style="color:#ABB2BF">?.</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    .</span><span style="color:#61AFEF">map</span><span style="color:#ABB2BF">((</span><span style="color:#E06C75;font-style:italic">node</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> (&#123;</span></span><span class="line"><span style="color:#E06C75">      src</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">node</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">props</span><span style="color:#56B6C2">!</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      type</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">node</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">props</span><span style="color:#ABB2BF">?.</span><span style="color:#E06C75">type</span><span style="color:#56B6C2"> ||</span><span style="color:#D19A66"> undefined</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">    &#125;));</span></span><span class="line"><span style="color:#E5C07B">  sources</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [...</span><span style="color:#E5C07B">sources</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#ABB2BF">, ...</span><span style="color:#E06C75">newSources</span><span style="color:#ABB2BF">];</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">onMounted</span><span style="color:#ABB2BF">(() </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#C678DD">  if</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">container</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">    art</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> new</span><span style="color:#61AFEF"> Artplayer</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#E06C75">      container</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">container</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      url</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">sources</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#ABB2BF">[</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">].</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      poster</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">props</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">poster</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      pip</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      setting</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      playbackRate</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      aspectRatio</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      fullscreen</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      fullscreenWeb</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      mutex</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">      theme</span><span style="color:#ABB2BF">: </span><span style="color:#61AFEF">getComputedStyle</span><span style="color:#ABB2BF">(</span><span style="color:#E5C07B">container</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">value</span><span style="color:#ABB2BF">).</span><span style="color:#61AFEF">getPropertyValue</span><span style="color:#ABB2BF">(</span></span><span class="line"><span style="color:#98C379">        "--vp-c-brand-3"</span></span><span class="line"><span style="color:#ABB2BF">      ),</span></span><span class="line"><span style="color:#E06C75">      quality</span><span style="color:#ABB2BF">:</span></span><span class="line"><span style="color:#E5C07B">        sources</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">value</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">length</span><span style="color:#56B6C2"> ></span><span style="color:#D19A66"> 1</span></span><span class="line"><span style="color:#C678DD">          ?</span><span style="color:#E5C07B"> sources</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">value</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">map</span><span style="color:#ABB2BF">((</span><span style="color:#E06C75;font-style:italic">source</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">index</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> (&#123;</span></span><span class="line"><span style="color:#E06C75">              html</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">source</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">type</span><span style="color:#56B6C2"> ||</span><span style="color:#98C379"> `线路 </span><span style="color:#C678DD">$&#123;</span><span style="color:#E06C75">index</span><span style="color:#56B6C2"> +</span><span style="color:#D19A66"> 1</span><span style="color:#C678DD">&#125;</span><span style="color:#98C379">`</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">              url</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">source</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">src</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75">              default</span><span style="color:#ABB2BF">: </span><span style="color:#E06C75">index</span><span style="color:#56B6C2"> ===</span><span style="color:#D19A66"> 0</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">            &#125;))</span></span><span class="line"><span style="color:#C678DD">          :</span><span style="color:#ABB2BF"> [],</span></span><span class="line"><span style="color:#ABB2BF">    &#125;);</span></span><span class="line"><span style="color:#E5C07B">    art</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">on</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"ready"</span><span style="color:#ABB2BF">, () </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#E5C07B">      art</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">autoHeight</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#ABB2BF">    &#125;);</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">    art</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">on</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"resize"</span><span style="color:#ABB2BF">, () </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#E5C07B">      art</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">autoHeight</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#ABB2BF">    &#125;);</span></span><span class="line"><span style="color:#E5C07B">    console</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">log</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">art</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">  &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">onUnmounted</span><span style="color:#ABB2BF">(() </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#E5C07B">  art</span><span style="color:#ABB2BF">?.</span><span style="color:#61AFEF">destroy</span><span style="color:#ABB2BF">();</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">></span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">template</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"video-container"</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">    &#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> ref</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"container"</span><span style="color:#D19A66"> class</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"artplayer-app"</span><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">  &#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">template</span><span style="color:#ABB2BF">></span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">style</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#D19A66">.video-container</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#ABB2BF">  margin: </span><span style="color:#D19A66">1</span><span style="color:#E06C75">rem</span><span style="color:#D19A66"> 0</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">  border-radius: </span><span style="color:#D19A66">8</span><span style="color:#E06C75">px</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">  overflow: </span><span style="color:#D19A66">hidden</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D19A66">.artplayer-app</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#ABB2BF">  width: </span><span style="color:#D19A66">100</span><span style="color:#E06C75">%</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">  height: </span><span style="color:#D19A66">400</span><span style="color:#E06C75">px</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">  background: </span><span style="color:#D19A66">#000</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">style</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>上面这个组件已经满足本人的使用需求。如果你想要传更多参数对播放器进行控制，也是可以的，反正多传的参数原生组件识别不了就是了。</p><p>然后在 Markdown 中试用一下：</p><figure class="highlight markdown"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">script</span><span style="color:#D19A66"> setup</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> ArtVideo</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> ".vitepress/theme/components/ArtVideo"</span></span><span class="line"><span style="color:#ABB2BF">&#x3C;/</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">></span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">ArtVideo</span><span style="color:#D19A66"> src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"movie.mp4"</span><span style="color:#D19A66"> controls</span><span style="color:#D19A66"> width</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"100%"</span><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">ArtVideo</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>应该是没有问题的。</p><h2 id="引用-Vue-组件">引用 Vue 组件</h2><p>我们不想在每个 Markdown 中都写 JavaScript 引入前面写的 <code>ArtVideo</code> 组件，对于 VitePress 可以全局引用。</p><p><a href="https://vitepress.dev/zh/guide/custom-theme#theme-interface">https://vitepress.dev/zh/guide/custom-theme#theme-interface</a></p><p>错误示范：</p><figure class="highlight typescript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">// src/.vitepress/theme/index.ts</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> Video</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "./components/Video.vue"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">  // ...</span></span><span class="line"><span style="color:#61AFEF">  enhanceApp</span><span style="color:#ABB2BF">(&#123; </span><span style="color:#E06C75;font-style:italic">app</span><span style="color:#ABB2BF"> &#125;) &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">    // ...</span></span><span class="line"><span style="color:#E5C07B">    app</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">component</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"video"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">Video</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;;</span></span></code></pre></td></tr></tbody></table></figure><p>然而没这么简单，当我们尝试全局引入，发现浏览器控制台报错：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span>[Vue warn]: Do not use built-in or reserved HTML elements as component id: video</span></span></code></pre></td></tr></tbody></table></figure><p>看来我们不能全局引入名为 <code>video</code> 这种已经存在的元素的组件。因此，这里我们将其重命名为 <code>ArtVideo</code>。</p><figure class="highlight typescript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">// src/.vitepress/theme/index.ts</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#E06C75"> ArtVideo</span><span style="color:#C678DD"> from</span><span style="color:#98C379"> "./components/ArtVideo.vue"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">  // ...</span></span><span class="line"><span style="color:#61AFEF">  enhanceApp</span><span style="color:#ABB2BF">(&#123; </span><span style="color:#E06C75;font-style:italic">app</span><span style="color:#ABB2BF"> &#125;) &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">    // ...</span></span><span class="line"><span style="color:#E5C07B">    app</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">component</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"ArtVideo"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">ArtVideo</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;;</span></span></code></pre></td></tr></tbody></table></figure><p>现在我们在任意 Markdown 中插入 <code>&lt;ArtVideo&gt;</code>，可以发现视频能够正常使用 ArtPlayer 播放了。</p><p>由于我们直接通过引入名为 <code>&lt;video&gt;</code> 组件的梦想已经破灭，接下来我们需要让 VitePress 在渲染 Markdown 的时候将 <code>&lt;video&gt;</code> 替换成 <code>&lt;ArtVideo&gt;</code>。</p><p>我们需要查找相应的 API 完成对 Markdown 内容的替换。假如实在找不到，最后的一招还有正则表达式替换。</p><h2 id="VitePress-的-Markdown-渲染">VitePress 的 Markdown 渲染</h2><p>VitePress 在站点配置中给了 Markdown 的渲染配置：</p><p><a href="https://vitepress.dev/zh/reference/site-config#markdown">https://vitepress.dev/zh/reference/site-config#markdown</a></p><figure class="highlight javascript"><table><tbody><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 class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#E06C75">  markdown</span><span style="color:#ABB2BF">: &#123;...&#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>VitePress 使用的是 <a href="https://github.com/markdown-it/markdown-it">Markdown-it</a> 这个库作为解析器，<a href="https://vitepress.dev/zh/guide/using-vue">将 Markdown 渲染成 HTML</a> 作为 Vue 的 <code>&lt;template&gt;</code>。因此我们需要知道 Markdown-it 的渲染原理，无非就是将 Markdown <strong>解析</strong>成抽象语法树（AST），然后再<strong>生成</strong> HTML。</p><p>可以通过这个 DEMO 的 Debug 选项看到它生成的语法树结构：</p><p><a href="https://markdown-it.github.io/">https://markdown-it.github.io/</a></p><p><img src="vitepress-artplayer/3a4b500c9634558c1d85c6c106c80482.png" alt=""></p><p>上图是错误示范，这里的 HTML 没有启用。我们要知道 VitePress 的 HTML 解析默认是启用的。</p><p><img src="vitepress-artplayer/9ed1ac655e46d2cb9d4e19d8d9932d60.webp" alt=""></p><p>这里我们可以看出来了，它会分别解析开放标签和闭合标签，类型都是 <code>html_inline</code>。</p><p>那么我们就去翻它的文档。</p><p><a href="https://markdown-it.github.io/markdown-it/">https://markdown-it.github.io/markdown-it/</a></p><p>结果发现一脸懵，一堆 API，怎么用都不知道。后面在它 GitHub 仓库主页发现还有文档：</p><p><a href="https://github.com/markdown-it/markdown-it/blob/master/docs/examples/renderer_rules.md">https://github.com/markdown-it/markdown-it/blob/master/docs/examples/renderer_rules.md</a></p><p>根据这篇开发者文档，我们就可以轻松编写一个插件，控制这个解析的流程，完成对 HTML 元素的替换。</p><p>最后的代码如下：</p><figure class="highlight typescript"><table><tbody><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><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic">// src/.vitepress/config.mts</span></span><span class="line"><span style="color:#C678DD">import</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">defineConfig</span><span style="color:#ABB2BF"> &#125; </span><span style="color:#C678DD">from</span><span style="color:#98C379"> "vitepress"</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">// https://vitepress.dev/reference/site-config</span></span><span class="line"><span style="color:#C678DD">export</span><span style="color:#C678DD"> default</span><span style="color:#61AFEF"> defineConfig</span><span style="color:#ABB2BF">(&#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">  // ...</span></span><span class="line"><span style="color:#E06C75">  markdown</span><span style="color:#ABB2BF">: &#123;</span></span><span class="line"><span style="color:#61AFEF">    config</span><span style="color:#ABB2BF">: (</span><span style="color:#E06C75;font-style:italic">md</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // https://github.com/markdown-it/markdown-it/blob/master/docs/examples/renderer_rules.md</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      // 自定义插件：替换 video 标签</span></span><span class="line"><span style="color:#E5C07B">      md</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">use</span><span style="color:#ABB2BF">((</span><span style="color:#E06C75;font-style:italic">md</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">=></span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">        // 1. 处理块中的 video 标签</span></span><span class="line"><span style="color:#C678DD">        const</span><span style="color:#E5C07B"> defaultHtmlBlockRender</span><span style="color:#56B6C2"> =</span></span><span class="line"><span style="color:#E5C07B">          md</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">renderer</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">rules</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">html_block</span><span style="color:#56B6C2"> ||</span></span><span class="line"><span style="color:#C678DD">          function</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75;font-style:italic">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">options</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">env</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">self</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#C678DD">            return</span><span style="color:#E5C07B"> self</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">renderToken</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">options</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">          &#125;;</span></span><span class="line"></span><span class="line"><span style="color:#E5C07B">        md</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">renderer</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">rules</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">html_block</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> function</span><span style="color:#ABB2BF"> (</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          tokens</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          idx</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          options</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          env</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          self</span></span><span class="line"><span style="color:#ABB2BF">        ) &#123;</span></span><span class="line"><span style="color:#C678DD">          const</span><span style="color:#E5C07B"> content</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> tokens</span><span style="color:#ABB2BF">[</span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">].</span><span style="color:#E06C75">content</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">          // console.debug(`处理 html_block #$&#123;idx&#125;:`, content);</span></span><span class="line"><span style="color:#C678DD">          if</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">includes</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"&#x3C;video"</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">||</span><span style="color:#E5C07B"> content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">includes</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"&#x3C;/video>"</span><span style="color:#ABB2BF">)) &#123;</span></span><span class="line"><span style="color:#E5C07B">            console</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">debug</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">`发现 video 标签在 html_block #</span><span style="color:#C678DD">$&#123;</span><span style="color:#E06C75">idx</span><span style="color:#C678DD">&#125;</span><span style="color:#98C379">`</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">content</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#C678DD">            return</span><span style="color:#E5C07B"> content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">replaceAll</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"video"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"ArtVideo"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">          &#125;</span></span><span class="line"><span style="color:#C678DD">          return</span><span style="color:#61AFEF"> defaultHtmlBlockRender</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">options</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">env</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">self</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">        &#125;;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">        // 2. 处理内联的 video 标签</span></span><span class="line"><span style="color:#C678DD">        const</span><span style="color:#E5C07B"> defaultHtmlInlineRender</span><span style="color:#56B6C2"> =</span></span><span class="line"><span style="color:#E5C07B">          md</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">renderer</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">rules</span><span style="color:#ABB2BF">.</span><span style="color:#E06C75">html_inline</span><span style="color:#56B6C2"> ||</span></span><span class="line"><span style="color:#C678DD">          function</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75;font-style:italic">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">options</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">env</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75;font-style:italic">self</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#C678DD">            return</span><span style="color:#E5C07B"> self</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">renderToken</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">options</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">          &#125;;</span></span><span class="line"><span style="color:#E5C07B">        md</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">renderer</span><span style="color:#ABB2BF">.</span><span style="color:#E5C07B">rules</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">html_inline</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> function</span><span style="color:#ABB2BF"> (</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          tokens</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          idx</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          options</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          env</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#E06C75;font-style:italic">          self</span></span><span class="line"><span style="color:#ABB2BF">        ) &#123;</span></span><span class="line"><span style="color:#C678DD">          const</span><span style="color:#E5C07B"> content</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> tokens</span><span style="color:#ABB2BF">[</span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">].</span><span style="color:#E06C75">content</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">          // console.debug(`处理 html_inline #$&#123;idx&#125;:`, content);</span></span><span class="line"><span style="color:#C678DD">          if</span><span style="color:#ABB2BF"> (</span><span style="color:#E5C07B">content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">includes</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"&#x3C;video"</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">||</span><span style="color:#E5C07B"> content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">includes</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"&#x3C;/video>"</span><span style="color:#ABB2BF">)) &#123;</span></span><span class="line"><span style="color:#E5C07B">            console</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">debug</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">`发现 video 标签在 html_inline #</span><span style="color:#C678DD">$&#123;</span><span style="color:#E06C75">idx</span><span style="color:#C678DD">&#125;</span><span style="color:#98C379">`</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">content</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#C678DD">            return</span><span style="color:#E5C07B"> content</span><span style="color:#ABB2BF">.</span><span style="color:#61AFEF">replaceAll</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"video"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"ArtVideo"</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">          &#125;</span></span><span class="line"><span style="color:#C678DD">          return</span><span style="color:#61AFEF"> defaultHtmlInlineRender</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">tokens</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">idx</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">options</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">env</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">self</span><span style="color:#ABB2BF">);</span></span><span class="line"><span style="color:#ABB2BF">        &#125;;</span></span><span class="line"><span style="color:#ABB2BF">      &#125;);</span></span><span class="line"><span style="color:#ABB2BF">    &#125;,</span></span><span class="line"><span style="color:#ABB2BF">  &#125;,</span></span><span class="line"><span style="color:#ABB2BF">&#125;);</span></span></code></pre></td></tr></tbody></table></figure><p>现在 Markdown 中直接使用 <code>&lt;video&gt;</code> 元素插入的视频就可以自动替换成 ArtPlayer 了！</p><p>通过这个方法，也可以将其他 Markdown 中原生的 HTML 元素自动替换成 Vue 组件，比如 <code>&lt;audio&gt;</code> 替换成 Aplayer 等等。这样可以在提高用户体验的同时保留原始 Markdown 文件对不同软件的兼容。</p>]]>
    </content>
    <id>https://www.xrgzs.top/posts/vitepress-artplayer</id>
    <link href="https://www.xrgzs.top/posts/vitepress-artplayer"/>
    <published>2025-05-15T10:55:08.000Z</published>
    <summary>
      <![CDATA[<p>浏览器自带的原生播放器相信大家都用过，总体体验不怎么样。为了提高用户播放体验，我们会引入第三方播放器，比如 <a href="https://github.com/DIYgod/DPlayer">DPlayer</a>、<a href="https://github.com]]>
    </summary>
    <title>VitePress 自动替换视频为 ArtPlayer</title>
    <updated>2025-05-15T10:55:08.000Z</updated>
  </entry>
</feed>
