<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>潇然工作室</title>
  <icon>https://www.xrgzs.top/icon.png</icon>
  <subtitle>潇洒自然</subtitle>
  <link href="https://www.xrgzs.top/atom.xml" rel="self"/>
  
  <link href="https://www.xrgzs.top/"/>
  <updated>2026-04-10T00:51:46.000Z</updated>
  <id>https://www.xrgzs.top/</id>
  
  <author>
    <name>狂犬主子</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>macOS 手动 PF 共享网络</title>
    <link href="https://www.xrgzs.top/posts/macos-pf-nat"/>
    <id>https://www.xrgzs.top/posts/macos-pf-nat</id>
    <published>2026-04-10T00:51:46.000Z</published>
    <updated>2026-04-10T00:51:46.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;p&gt;由于要将 MacBook 的 Wi-Fi 网络共享给一台没有网络的机器，使用网络共享功能发现无法灵活配置IP地址。由于机器不方便使用 DHCP、更改 IP，因此无法使用。&lt;/p&gt;
&lt;p&gt;通过搜索得知，在 macOS 上配置网卡 NAT（网络地址转换）主要有两种方式：&lt;/p&gt;</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>超微 X9SCL 无法进入 BIOS 解决方法</title>
    <link href="https://www.xrgzs.top/posts/supermicro-x9scl-bios"/>
    <id>https://www.xrgzs.top/posts/supermicro-x9scl-bios</id>
    <published>2026-04-08T15:30:50.000Z</published>
    <updated>2026-04-08T15:30:50.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;p&gt;最近狗子在折腾一台使用了超微（Supermicro）服务器主板 X9SCL 的设备。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./supermicro-x9scl-bios/server.jpeg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;由于自带的系统过老，无法跟上时代的发展，为了提高设</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>Armbian 自动挂载 USB 移动硬盘</title>
    <link href="https://www.xrgzs.top/posts/armbian-automount"/>
    <id>https://www.xrgzs.top/posts/armbian-automount</id>
    <published>2026-03-29T15:00:00.000Z</published>
    <updated>2026-04-04T10:00:00.000Z</updated>
    
    <content type="html"><![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&#123;ID_FS_UUID&#125;</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>
    
    
      
      
    <summary type="html">&lt;p&gt;最近在折腾配置 N1 上的 Armbian，想实现插入 USB 移动硬盘自动挂载的功能。经过一番摸索，终于找到了一套稳定可靠的方案，特此记录下来，供大家参考。&lt;/p&gt;
&lt;p&gt;AI 生成的 Armbian USB 移动硬盘自动挂载完全指南。&lt;/p&gt;
&lt;blockquote&gt;
</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>CUPS共享EPSON L383打印机</title>
    <link href="https://www.xrgzs.top/posts/cups"/>
    <id>https://www.xrgzs.top/posts/cups</id>
    <published>2026-02-26T15:47:13.000Z</published>
    <updated>2026-02-26T15:47:13.000Z</updated>
    
    <content type="html"><![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:#ABB2BF">reg </span><span style="color:#56B6C2">add</span><span style="color:#98C379"> "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Printers\RPC"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">f</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">v</span><span style="color:#98C379"> "RpcUseNamedPipeProtocol"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">t</span><span style="color:#E06C75"> REG_DWORD</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">d</span><span style="color:#98C379"> "1"</span></span><span class="line"><span style="color:#ABB2BF">reg </span><span style="color:#56B6C2">add</span><span style="color:#98C379"> "HKLM\SYSTEM\CurrentControlSet\Control\Print"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">f</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">v</span><span style="color:#98C379"> "RpcAuthnLevelPrivacyEnabled"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">t</span><span style="color:#E06C75"> REG_DWORD</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">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.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">Online</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">Enable</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">Feature</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">FeatureName</span><span style="color:#ABB2BF">:</span><span style="color:#E06C75">Printing</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">Foundation</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">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>
    
    
    <summary type="html">CUPS共享EPSON L383打印机</summary>
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>Vue + UnoCSS 避免影响全局样式</title>
    <link href="https://www.xrgzs.top/posts/vue-unocss-scoped"/>
    <id>https://www.xrgzs.top/posts/vue-unocss-scoped</id>
    <published>2026-02-25T02:55:08.000Z</published>
    <updated>2026-02-25T02:55:08.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;h1&gt;Vue + UnoCSS 避免影响全局样式&lt;/h1&gt;
&lt;p&gt;最近尝试使用 Vite 将一个 Vue 3 组件打包成 JS，以便插入一个不是 Vue 写的 HTML 中。本人属于前端小白，不会写CSS。由于 AI 写这种类名样式比较厉害，且听说 UnoCSS 比较厉害，故使</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>Linux 下安装 nerdctl</title>
    <link href="https://www.xrgzs.top/posts/nerdctl-install"/>
    <id>https://www.xrgzs.top/posts/nerdctl-install</id>
    <published>2025-11-21T05:22:50.000Z</published>
    <updated>2025-11-21T05:22:50.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;h1&gt;Linux 下安装 nerdctl&lt;/h1&gt;
&lt;p&gt;本文将介绍 Linux 下安装 nerdctl 的方法。演示环境为 Fedora Cloud 43。本文所有组件均从 GitHub Releases 安装（iptables 除外），不使用包管理器，方法通用于 glibc</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>跨域问题：CORS、JSONP 与反向代理</title>
    <link href="https://www.xrgzs.top/posts/cors"/>
    <id>https://www.xrgzs.top/posts/cors</id>
    <published>2025-07-08T11:30:00.000Z</published>
    <updated>2025-07-08T11:30:00.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;p&gt;跨域问题是 Web 开发中经常遇到的一个挑战，解决方法多样，例如使用反向代理、CORS、JSONP 方案。本文将为大家深入探讨一下跨域方案的具体实现。&lt;/p&gt;
&lt;h2 id=&quot;同源策略&quot;&gt;同源策略&lt;/h2&gt;
&lt;p&gt;浏览器通过实施&lt;strong&gt;同源策略&lt;/strong&gt;（S</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>OpenList + Rclone 文件同步方案</title>
    <link href="https://www.xrgzs.top/posts/openlist-sync"/>
    <id>https://www.xrgzs.top/posts/openlist-sync</id>
    <published>2025-07-01T18:40:13.000Z</published>
    <updated>2025-07-01T18:40:13.000Z</updated>
    
    <content type="html"><![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>
    
    
    <summary type="html">用 Rclone 通过 WebDAV 挂载 OpenList 实现文件同步，是目前唯一好用的方案</summary>
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>使用 FakeHTTP 绕过运营商白名单限速 5M 上行</title>
    <link href="https://www.xrgzs.top/posts/fakehttp"/>
    <id>https://www.xrgzs.top/posts/fakehttp</id>
    <published>2025-06-06T00:55:21.000Z</published>
    <updated>2025-11-05T14:00:00.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;html&gt;&lt;head&gt;&lt;script src=&quot;https://s4.zstatic.net/ajax/libs/artplayer/5.2.2/artplayer.min.js&quot;&gt;&lt;/script&gt;&lt;/head&gt;&lt;body&gt;&lt;h2 id=&quot;背景&quot;&gt;背景&lt;/h2&gt;
&lt;p&gt;自从国</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>NVM / FNM 安装使用</title>
    <link href="https://www.xrgzs.top/posts/nvm"/>
    <id>https://www.xrgzs.top/posts/nvm</id>
    <published>2025-05-24T02:30:50.000Z</published>
    <updated>2026-04-04T10:30:00.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2026 更新：不推荐使用 nvm，推荐使用 <a href="https://fnm.vercel.app/">fnm</a>。<br>文章已修改。</p></blockquote><h2 id="安装">安装</h2><h3 id="Linux-macOS">Linux / macOS</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">curl</span><span style="color:#D19A66"> -o-</span><span style="color:#98C379"> https://fnm.vercel.app/install</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">bash</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><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>在 <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><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">@echo off</span></span><span class="line"><span style="color:#ABB2BF">fnm </span><span style="color:#E06C75">env</span><span style="color:#ABB2BF"> --</span><span style="color:#E06C75">shell</span><span style="color:#E06C75"> cmd</span><span style="color:#ABB2BF"> >> %TEMP%\</span><span style="color:#E06C75">fnm</span><span style="color:#ABB2BF">-env.cmd</span></span><span class="line"><span style="color:#C678DD">call </span><span style="color:#ABB2BF">%TEMP%\</span><span style="color:#E06C75">fnm</span><span style="color:#ABB2BF">-env.cmd</span></span><span class="line"><span style="color:#E06C75">del</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">q</span><span style="color:#ABB2BF"> %TEMP%\</span><span style="color:#E06C75">fnm</span><span style="color:#ABB2BF">-env.cmd</span></span><span class="line"></span><span class="line"><span style="color:#E06C75">node</span><span style="color:#ABB2BF"> -</span><span style="color:#E06C75">v</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Node">安装 Node</h2><p>fnm 默认从官方服务器下载 Node.js。在中国大陆环境下，建议配置镜像以加速下载。</p><ul><li>永久生效：设置环境变量 <code>FNM_NODE_DIST_MIRROR</code>：</li></ul><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:#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></code></pre></td></tr></tbody></table></figure><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><ul><li>临时生效：</li></ul><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 style="color:#D19A66"> --node-dist-mirror=</span><span style="color:#98C379">"https://npmmirror.com/mirrors/node/"</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-PNPM">安装 PNPM</h2><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>或者：</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:#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="https://registry.npmmirror.com"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># PowerShell: $env: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>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2026 更新：不推荐使用 nvm，推荐使用 &lt;a href=&quot;https://fnm.vercel.app/&quot;&gt;fnm&lt;/a&gt;。&lt;br&gt;
文章已修改。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;安装&quot;&gt;安装&lt;/h2&gt;
&lt;h3 id=&quot;L</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>VitePress 自动替换视频为 ArtPlayer</title>
    <link href="https://www.xrgzs.top/posts/vitepress-artplayer"/>
    <id>https://www.xrgzs.top/posts/vitepress-artplayer</id>
    <published>2025-05-15T10:55:08.000Z</published>
    <updated>2025-05-15T10:55:08.000Z</updated>
    
    <content type="html"><![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>
    
    
      
      
    <summary type="html">&lt;p&gt;浏览器自带的原生播放器相信大家都用过，总体体验不怎么样。为了提高用户播放体验，我们会引入第三方播放器，比如 &lt;a href=&quot;https://github.com/DIYgod/DPlayer&quot;&gt;DPlayer&lt;/a&gt;、&lt;a href=&quot;https://github.com</summary>
      
    
    
    
    <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/"/>
    
  </entry>
  
  <entry>
    <title>Debian 安装桌面环境</title>
    <link href="https://www.xrgzs.top/posts/debian-de"/>
    <id>https://www.xrgzs.top/posts/debian-de</id>
    <published>2025-03-13T07:27:56.000Z</published>
    <updated>2025-03-13T07:27:56.000Z</updated>
    
    <content type="html"><![CDATA[<h1>Debian 安装桌面环境</h1><p>通常情况下，我们可以使用 Debian 的安装盘，在安装时就勾选上桌面环境。然而，如果我们的系统已经安装好，然后突然想要将其改为桌面环境，那么此时就需要手动为其安装配置桌面环境。</p><p>此外，Debian 默认安装的桌面环境包含了一些用不到的组件，比如说 GNOME，一装就是一堆小游戏，一个一个卸载很头疼。通过手动安装桌面环境可以解决这个问题。</p><h3 id="启用非自由软件源和Backports">启用非自由软件源和Backports</h3><p>在一些设备上安装驱动需要启用非自由软件源，并安装 Backports 中的新版 Linux 内核。否则 Debian 默认内核较老，您甚至无法驱动网卡上网。</p><p>Debian 软件源配置 <code>/etc/apt/source.list</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:#61AFEF">deb</span><span style="color:#98C379"> http://deb.debian.org/debian/</span><span style="color:#98C379"> bookworm</span><span style="color:#98C379"> main</span><span style="color:#98C379"> contrib</span><span style="color:#98C379"> non-free</span><span style="color:#98C379"> non-free-firmware</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># deb-src http://deb.debian.org/debian/ bookworm main contrib non-free non-free-firmware</span></span><span class="line"><span style="color:#61AFEF">deb</span><span style="color:#98C379"> http://deb.debian.org/debian/</span><span style="color:#98C379"> bookworm-updates</span><span style="color:#98C379"> main</span><span style="color:#98C379"> contrib</span><span style="color:#98C379"> non-free</span><span style="color:#98C379"> non-free-firmware</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># deb-src http://deb.debian.org/debian/ bookworm-updates main contrib non-free non-free-firmware</span></span><span class="line"><span style="color:#61AFEF">deb</span><span style="color:#98C379"> http://deb.debian.org/debian-security/</span><span style="color:#98C379"> bookworm-security</span><span style="color:#98C379"> main</span><span style="color:#98C379"> contrib</span><span style="color:#98C379"> non-free</span><span style="color:#98C379"> non-free-firmware</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># deb-src http://deb.debian.org/debian-security/ bookworm-security main contrib non-free non-free-firmware</span></span></code></pre></td></tr></tbody></table></figure><p>我们需要加上：</p><ul><li><code>contrib</code> 带有非自由依赖关系的<a href="https://wiki.debian.org/DebianFreeSoftwareGuidelines">DFSG</a>兼容软件</li><li><code>non-free-firmware</code> 非DFSG兼容硬件驱动与固件</li><li><code>non-free</code> 非DSFG兼容软件</li></ul><p><a href="https://wiki.debian.org/zh_CN/Backports">https://wiki.debian.org/zh_CN/Backports</a></p><p>配置 Backports 可以获取一些新版本软件包。</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">deb</span><span style="color:#98C379"> http://deb.debian.org/debian/</span><span style="color:#98C379"> bookworm-backports</span><span style="color:#98C379"> main</span><span style="color:#98C379"> contrib</span><span style="color:#98C379"> non-free</span><span style="color:#98C379"> non-free-firmware</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># deb-src http://deb.debian.org/debian/ bookworm-backports main contrib non-free non-free-firmware</span></span></code></pre></td></tr></tbody></table></figure><p>这些可以和换源一起操作，这里不再介绍，可以通过下面的网页生成：</p><p><a href="https://help.mirrors.cernet.edu.cn/debian/">https://help.mirrors.cernet.edu.cn/debian/</a></p><h2 id="创建用户">创建用户</h2><p>如果您使用的用户为 root（UID: 0），那么强烈建议您为桌面环境创建一个用户（UID &gt;= 1000）：</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:#7F848E;font-style:italic"># 安装 sudo 命令</span></span><span class="line"><span style="color:#61AFEF">apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> sudo</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 创建新用户（将 user 替换为实际用户名）</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> adduser</span><span style="color:#98C379"> user</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 根据提示设置密码</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 将用户加入 sudo 组（将 user 替换为实际用户名）</span></span><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"> sudo</span><span style="color:#98C379"> user</span></span></code></pre></td></tr></tbody></table></figure><p>如果您已经是普通用户，可以跳过此步。</p><h2 id="安装Shell补全">安装Shell补全</h2><p>如果您使用的是 Bash（默认），可能没有默认安装补全，当您按 TAB 键时不会自动补全命令，可以先安装一下来提高体验：</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:#98C379"> bash-completion</span></span></code></pre></td></tr></tbody></table></figure><p>如果您想要更换其它Shell，也可以配置。</p><h2 id="安装驱动">安装驱动</h2><p>Intel、AMD 安装桌面环境的时候通常会正确配置好驱动，NVIDIA个人不太清楚会不会自动配置正确。</p><p>可能需要<a href="#%E5%90%AF%E7%94%A8%E9%9D%9E%E8%87%AA%E7%94%B1%E8%BD%AF%E4%BB%B6%E6%BA%90%E5%92%8Cbackports">启用非自由软件源和Backports</a>。</p><p>如果有问题请看文档：<a href="https://wiki.debian.org/GraphicsCard">https://wiki.debian.org/GraphicsCard</a></p><h3 id="安装VMware-Tools">安装VMware Tools</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">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> open-vm-tools</span><span style="color:#98C379"> open-vm-tools-desktop</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> symlink</span><span style="color:#98C379"> /etc/systemd/system/vmtoolsd.service</span><span style="color:#98C379"> →</span><span style="color:#98C379"> /lib/systemd/system/open-vm-tools.service.</span></span><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> symlink</span><span style="color:#98C379"> /etc/systemd/system/multi-user.target.wants/open-vm-tools.service</span><span style="color:#98C379"> →</span><span style="color:#98C379"> /lib/systemd/system/open-vm-tools.service.</span></span><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> symlink</span><span style="color:#98C379"> /etc/systemd/system/open-vm-tools.service.requires/vgauth.service</span><span style="color:#98C379"> →</span><span style="color:#98C379"> /lib/systemd/system/vgauth.service.</span></span><span class="line"><span style="color:#61AFEF">Setting</span><span style="color:#98C379"> up</span><span style="color:#98C379"> open-vm-tools-desktop</span><span style="color:#98C379"> ...</span></span><span class="line"><span style="color:#61AFEF">Created</span><span style="color:#98C379"> symlink</span><span style="color:#98C379"> /etc/systemd/system/multi-user.target.wants/run-vmblock</span><span style="color:#56B6C2">\x</span><span style="color:#98C379">2dfuse.mount</span><span style="color:#98C379"> →</span><span style="color:#98C379"> /lib/systemd/system/run-vmblock</span><span style="color:#56B6C2">\x</span><span style="color:#98C379">2dfuse.mount.</span></span></code></pre></td></tr></tbody></table></figure><p>桌面环境下第一次安装完成后需要先注销然后重新登录或重启，一般就可以正常使用剪贴板共享、HGFS（文件夹共享）等功能了。</p><p>文件夹共享目录位于：<code>/mnt/hgfs</code>。</p><p>如果后面不正常的话，可以排查这些服务：</p><ul><li><code>vmtoolsd.service</code></li><li><code>open-vm-tools.service</code></li><li><code>vgauth.service</code></li><li><code>run-vmblock\x2dfuse.mount</code> （注意加上引号）</li></ul><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">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> vmtoolsd.service</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> open-vm-tools.service</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> vgauth.service</span></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> status</span><span style="color:#98C379"> "run-vmblock\x2dfuse.mount"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Enable</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"> vmtoolsd.service</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"> open-vm-tools.service</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"> vgauth.service</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"> "run-vmblock\x2dfuse.mount"</span></span></code></pre></td></tr></tbody></table></figure><p>如果您使用的桌面环境使用了 Wayland，剪贴板共享一般不可用，可尝试：</p><ul><li>更换为 Xorg（X11）<br><img src="debian-de/622a9d67e48c36d120c43993fe7f8620.png" alt=""></li><li>拖拽文件进虚拟机时<strong>抖动鼠标</strong>（对于 GNOME 测试有用）</li><li>通过共享文件夹传输数据</li><li>通过网络传输数据</li><li>更新虚拟机软件</li><li>更换 VirtualBox 7.1 以上版本</li></ul><h2 id="安装桌面">安装桌面</h2><h3 id="方法1：tasksel">方法1：tasksel</h3><p>Debian 中有 <a href="https://wiki.debian.org/tasksel">tasksel</a> 命令用于设置桌面环境。这个也是使用安装盘安装时的同款。</p><p>但是这种方式的缺点已经说过，会带上一些用不到的组件，如果比较在意的话，可以<a href="#%E6%96%B9%E6%B3%952%E6%89%8B%E5%8A%A8%E6%9C%80%E5%B0%8F%E5%AE%89%E8%A3%85">手动最小安装</a>。</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"> tasksel</span></span></code></pre></td></tr></tbody></table></figure><p><img src="debian-de/f66c6119a6ba3650bc703c300bc68e36.png" alt=""></p><ol><li><p>使用方向键选择桌面环境（GNOME/KDE/Xfce等）</p></li><li><p>按空格键标记安装</p></li><li><p>Tab 键切换至 <code>&lt;OK&gt;</code> 确认</p></li><li><p>等待自动完成安装</p></li></ol><h3 id="方法2：手动最小安装">方法2：手动最小安装</h3><p>我们可以得知一些 <a href="https://packages.debian.org/stable/metapackages/">metapackages</a> 的名称、介绍、依赖等信息，然后通过元包一键安装。</p><p>如 GNOME：</p><p><img src="debian-de/4d6c7baa56d4b21cbfc6d32274292bc6.png" alt=""></p><h4 id="GNOME">GNOME</h4><p>GNOME 是一个类似 macOS 的桌面环境，采用 GTK 设计。默认十分简陋，通常需要一些调整（安装扩展）才能使其体验改善。Ubuntu 使用的就是魔改版 GNOME（ubuntu-desktop）。</p><p><a href="https://packages.debian.org/stable/gnome-core">https://packages.debian.org/stable/gnome-core</a></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"> gnome-core</span></span></code></pre></td></tr></tbody></table></figure><h4 id="KDE-Plasma">KDE Plasma</h4><p>KDE Plasma 是一个类似 Windows 的桌面环境，采用 QT 设计。默认提供了丰富的选项和设置，基本开箱即用，配套软件齐全能够满足日常需求，基本可以替代 Windows。Steam OS 使用的就是 KDE。</p><p><a href="https://packages.debian.org/stable/kde-plasma-desktop">https://packages.debian.org/stable/kde-plasma-desktop</a></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"> kde-plasma-desktop</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">sudo</span><span style="color:#98C379"> apt</span><span style="color:#98C379"> install</span><span style="color:#98C379"> fonts-noto-cjk</span></span></code></pre></td></tr></tbody></table></figure><h2 id="更改语言">更改语言</h2><p>如果您的系统比较精简，还需安装一下 locales 包：</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"> locales</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"> 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>）</li><li>将默认系统语言设置为中文（<code>zh_CN.UTF-8</code>）</li></ol><p>当然，locales 设置也可以自行生成。</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"> systemctl</span><span style="color:#98C379"> reboot</span></span></code></pre></td></tr></tbody></table></figure><p>如果您配置正确的话，重启应该会进入 GDM / SDDM，语言应该是中文的，且不会显示豆腐块，然后用你在<a href="#%E5%88%9B%E5%BB%BA%E7%94%A8%E6%88%B7">创建用户</a>中创建的用户登录。</p><h2 id="安装输入法">安装输入法</h2><p>首先安装输入法配置工具，方便后面<strong>配置输入环境</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:#98C379"> im-config</span><span style="color:#ABB2BF"> </span></span></code></pre></td></tr></tbody></table></figure><h3 id="ibus">ibus</h3><p>安装便捷，兼容性好，但性能一般，个人使用时打字甚至 CPU 会飙到 100%。推荐配合 GNOME 使用。</p><p><a href="https://wiki.debian.org/I18n/ibus">https://wiki.debian.org/I18n/ibus</a></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"> ibus</span><span style="color:#98C379"> ibus-pinyin</span></span></code></pre></td></tr></tbody></table></figure><p>GNOME:</p><p>原生支持 ibus。</p><p>KDE:</p><p>外挂支持 ibus。</p><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"> plasma-widgets-addons</span></span></code></pre></td></tr></tbody></table></figure><h3 id="Fcitx5">Fcitx5</h3><p>安装复杂，兼容性差，但性能好，功能强大。推荐配合 KDE 使用。</p><p><a href="https://wiki.debian.org/I18n/Fcitx5">https://wiki.debian.org/I18n/Fcitx5</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></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-gtk2</span><span style="color:#98C379"> fcitx5-frontend-gtk3</span><span style="color:#98C379"> fcitx5-frontend-gtk4</span><span style="color:#98C379"> fcitx5-frontend-qt5</span><span style="color:#98C379"> fcitx5-frontend-qt6</span></span></code></pre></td></tr></tbody></table></figure><p>GNOME:</p><p>默认不支持 Fcitx5，需要使用外挂方式实现。</p><p>KDE:</p><p>原生支持 Fcitx5。</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"> kde-config-fcitx5</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置输入法">配置输入法</h2><p>如果您还没有装输入法，可以使用上面<a href="#%E5%AE%89%E8%A3%85%E8%BE%93%E5%85%A5%E6%B3%95">安装输入法</a>的命令安装输入法。</p><p>为了使应用程序能够识别到输入法引擎，我们需要对<strong>输入环境</strong>进行配置。这里利用我们之前装的 <code>im-config</code> 对输入法进行配置。</p><p>GNOME下直接打开“输入法”然后过完配置即可。如果找不到这个应用的话，可以直接在终端中输入执行 <code>im-config</code> 即可。</p><p><img src="debian-de/961d4167be2df4815711d0d7a1e982ea.png" alt=""></p><p>通常为 default 无需配置，系统会自动选择，如果选择错误，或某些软件无法使用输入法时才需配置。</p><h3 id="ibus-2">ibus</h3><p>GNOME:</p><p>通常没有配置好拼音输入法，我们直接在 设置 -&gt; 键盘 里面添加拼音输入法即可。</p><p><img src="debian-de/d85bba4ed6f3729f65be01691733b574.png" alt=""></p><p>切换为中文 Meta+Space</p><p>KDE:</p><p>安装完后注销后重新登录或重启即可。</p><h3 id="Fcitx5-2">Fcitx5</h3><p>GNOME:</p><p>需要安装 kimpanel 扩展以方便在任务栏/状态栏切换输入法。</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"> gnome-shell-extension-kimpanel</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后在扩展用启用 Input Method Panel。</p><p><img src="debian-de/b7148e5d0b47983dfb039387c41ad7e2.png" alt=""></p><p>打开 Fcitx 5，此时就可以输入文字了。</p><p><img src="debian-de/a9a979bb1c4193971068087963163bf3.png" alt=""></p><p>切换为中文 Ctrl+Shift</p><p><img src="debian-de/8d6e6d1009ba74a3316310964d89c885.png" alt=""></p><p>当前没有配置好开机启动，我们需要使用 gnome-tweaks 将其设置为开机项。</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"> gnome-tweaks</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后可以在“工具”文件夹中找到</p><p><img src="debian-de/50a17840dde5cc8318625314250b3f8e.png" alt=""></p><p>添加 Fcitx 5 为开机启动项。</p><p><img src="debian-de/0a4824beb92b20a6cb76b1e974c8f310.png" alt=""></p><p>KDE:</p><p>设置 -&gt; 输入设备 -&gt; 虚拟键盘 中选择 Fcitx 5，然后点应用即可。</p><p><img src="debian-de/22f9855634b5767b78d14bcccc917f1a.png" alt=""></p><h2 id="安装软件">安装软件</h2><h3 id="新立得软件包管理器">新立得软件包管理器</h3><p>apt 命令的 GUI 界面，相比命令行更加直观。</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"> synaptic</span></span></code></pre></td></tr></tbody></table></figure><h3 id="商店">商店</h3><p>如 GNOME 商店、KDE Discover，能够通过系统包管理器，或者Flatpak、Snap容器的方式安装软件。</p><h3 id="配置-Flatpak">配置 Flatpak</h3><p>Flatpak 是一种容器化打包分发应用的技术，类似 Docker 但是打包的是 GUI 应用。由于 Snap 评价不佳，本文仅提供 Flatpak 的资料。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 安装 flathub</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"> flatpak</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 添加 flathub</span></span><span class="line"><span style="color:#7F848E;font-style:italic">#sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo</span></span><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://mirror.sjtu.edu.cn/flathub/flathub.flatpakrepo</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 更换 flathub 国内镜像源</span></span><span class="line"><span style="color:#61AFEF">curl</span><span style="color:#98C379"> https://mirror.sjtu.edu.cn/flathub/flathub.gpg</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> flatpak</span><span style="color:#98C379"> remote-modify</span><span style="color:#D19A66"> --gpg-import=-</span><span style="color:#98C379"> flathub</span></span><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://mirror.sjtu.edu.cn/flathub</span></span><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://flathub.xrgzs.us.kg/repo</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后重启即可使用。</p><blockquote><p>如果安装卡在下载 <code>org.freedesktop.Platform.openh264</code>，可以给那个域名手动解析一个优选的 Akamai CDN IP地址。这个域名 FireFox 也会用到。</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">ping</span><span style="color:#98C379"> whatismyip.akamai.com</span></span></code></pre></td></tr></tbody></table></figure><p>给 ping 到的 IP 加到 <code>/etc/hosts</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>23.202.35.72 ciscobinary.openh264.org</span></span></code></pre></td></tr></tbody></table></figure></blockquote><p>如需在桌面环境自带商店中使用，需要安装对应的组件：</p><p>GNOME:</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"> gnome-software-plugin-flatpak</span><span style="color:#ABB2BF"> </span></span></code></pre></td></tr></tbody></table></figure><p>KDE:</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"> kde-config-flatpak</span><span style="color:#98C379"> plasma-discover-backend-flatpak</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后即可通过自带商店搜索出 Google Chrome 等软件。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1&gt;Debian 安装桌面环境&lt;/h1&gt;
&lt;p&gt;通常情况下，我们可以使用 Debian 的安装盘，在安装时就勾选上桌面环境。然而，如果我们的系统已经安装好，然后突然想要将其改为桌面环境，那么此时就需要手动为其安装配置桌面环境。&lt;/p&gt;
&lt;p&gt;此外，Debian 默认安装的桌面</summary>
      
    
    
    
    <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/%E6%A1%8C%E9%9D%A2%E7%8E%AF%E5%A2%83/"/>
    
  </entry>
  
  <entry>
    <title>终端粘贴^[[200~问题及解决方法</title>
    <link href="https://www.xrgzs.top/posts/shell-paste-200"/>
    <id>https://www.xrgzs.top/posts/shell-paste-200</id>
    <published>2025-03-11T07:18:21.000Z</published>
    <updated>2025-07-01T18:40:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>有时候在终端粘贴内容时，会在前面加上 <code>^[[200~</code> 这些字符，尤其是在粘贴一大串链接的时候，比较让人难受。那么这是什么原因呢？该怎么处理？今天我们就来好好盘一盘。</p><h2 id="原因">原因</h2><p>在终端中进行粘贴操作时，有时候会遇到一些额外的转义字符，这可能和终端的某种模式有关。</p><p>例如，某些终端模拟器支持“<strong>括号粘贴模式</strong>”（Bracketed Paste Mode），这种模式允许终端<strong>区分用户直接输入的文本和从剪贴板粘贴的文本</strong>。</p><p>当启用这个模式时，粘贴的内容会被特定的转义字符包围，比如开始标记和结束标记，以便应用程序（如 Shell 或编辑器）<strong>区分用户直接输入和粘贴内容</strong>，从而避免意外执行恶意代码或格式混乱。</p><p><strong>默认情况下，在 <code>bash</code> 中会启用括号粘贴（bracketed paste）。</strong> bash <code>readline</code> 库版本 8.1 现已正式发布，它会默认启用括号粘贴模式。当您将文本粘贴到终端时（即使以换行符结尾），<code>bash</code> 会突出显示文本，您必须按 <code>Enter</code> 键来执行粘贴命令。括号粘贴模式是默认设置，它可以避免意外执行恶意命令。</p><p>括号遵循“特殊键”转义字符的通常格式（<code>ESC [ &lt;num&gt; ~</code>，<code>^[</code> 代表 <code>ESC</code> 字符），与 <kbd>PgUp</kbd>/<kbd>PgDn</kbd> 键或 <kbd>F4</kbd>–<kbd>F12</kbd> 功能键相同。因此从 Shell 或编辑器的角度来看，它们也被视为<strong>键绑定</strong>。如 Bash 中可以使用 <code>bind</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">$</span><span style="color:#98C379"> bind</span><span style="color:#D19A66"> -p</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">grep</span><span style="color:#D19A66"> 200</span></span><span class="line"><span style="color:#61AFEF">"\M-[200~"</span><span style="color:#56B6C2">:</span><span style="color:#98C379"> bracketed-paste-begin</span></span><span class="line"><span style="color:#61AFEF">"\200"</span><span style="color:#56B6C2">:</span><span style="color:#98C379"> self-insert</span></span></code></pre></td></tr></tbody></table></figure><p>与所有其他转转义字符一样，<strong>如果您在粘贴之前（或在按下特殊键之前）意外按下了 <kbd>Ctrl+V</kbd></strong>，则会导致 Shell 将后面的内容视为文字输入。例如，<kbd>Ctrl+V</kbd> <kbd>↑</kbd> 不会滚动浏览历史记录，而是直接插入<code>^[[A</code>。</p><blockquote><p>正常 <code>Ctrl+V</code> 不是粘贴。粘贴是 <code>Shift+Insert</code>，复制是 <code>Ctrl+Insert</code>。终端模拟器 <code>Ctrl+V</code> 能粘贴其实是被覆盖了。</p></blockquote><p>此外，括号粘贴模式是通过程序<strong>向终端打印转义序列来启用的</strong>。因此，如果使用该模式的程序意外退出，则可能启用该模式。</p><h2 id="解决">解决</h2><p>以下内容由 DeepSeek-R1 生成：</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:#56B6C2">printf</span><span style="color:#98C379"> '\e[?2004l'</span><span style="color:#7F848E;font-style:italic">  # 关闭括号粘贴模式</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:#56B6C2">printf</span><span style="color:#98C379"> '\e[?2004h'</span><span style="color:#7F848E;font-style:italic">  # 重新启用</span></span></code></pre></td></tr></tbody></table></figure><h3 id="永久禁用">永久禁用</h3><p>在 Shell 配置文件（如 <code>~/.bashrc</code>、<code>~/.zshrc</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">bind</span><span style="color:#98C379"> 'set enable-bracketed-paste off'</span><span style="color:#7F848E;font-style:italic">  # Bash</span></span></code></pre></td></tr></tbody></table></figure><p>或</p><figure class="highlight zsh"><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">unset</span><span style="color:#98C379"> zle_bracketed_paste</span><span style="color:#7F848E;font-style:italic">  # Zsh</span></span></code></pre></td></tr></tbody></table></figure><h3 id="检查终端模拟器设置">检查终端模拟器设置</h3><p>某些终端（如 GNOME Terminal、iTerm2）支持直接关闭括号粘贴模式。进入设置，搜索类似 <strong>“Bracketed Paste”</strong> 的选项并禁用。</p><h3 id="配置应用程序支持括号粘贴">配置应用程序支持括号粘贴</h3><p>如果问题出现在编辑器（如 Vim），确保其版本较新（≥ 8.0），或添加配置：</p><figure class="highlight vim"><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">if</span><span style="color:#ABB2BF"> !</span><span style="color:#61AFEF">has</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'patch-8.0.0238'</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">  let</span><span style="color:#ABB2BF"> &#x26;t_SI .= </span><span style="color:#98C379">"\&#x3C;Esc>[?2004h"</span></span><span class="line"><span style="color:#C678DD">  let</span><span style="color:#ABB2BF"> &#x26;t_EI .= </span><span style="color:#98C379">"\&#x3C;Esc>[?2004l"</span></span><span class="line"><span style="color:#C678DD">endif</span></span></code></pre></td></tr></tbody></table></figure><h3 id="使用快捷键粘贴（替代右键）">使用快捷键粘贴（替代右键）</h3><p>部分终端支持 <kbd>Ctrl+Shift+V</kbd> 或 <kbd>Cmd+V</kbd> 直接粘贴原始内容，绕过模式干扰。</p><h2 id="参考">参考</h2><p><a href="https://superuser.com/questions/1532688/pasting-required-text-into-terminal-emulator-results-in-200required-text">https://superuser.com/questions/1532688/pasting-required-text-into-terminal-emulator-results-in-200required-text</a></p><p><a href="https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux/9/html/9.0_release_notes/enhancement_shells-and-command-line-tools">https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux/9/html/9.0_release_notes/enhancement_shells-and-command-line-tools</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;有时候在终端粘贴内容时，会在前面加上 &lt;code&gt;^[[200~&lt;/code&gt; 这些字符，尤其是在粘贴一大串链接的时候，比较让人难受。那么这是什么原因呢？该怎么处理？今天我们就来好好盘一盘。&lt;/p&gt;
&lt;h2 id=&quot;原因&quot;&gt;原因&lt;/h2&gt;
&lt;p&gt;在终端中进行粘贴操作时，有时</summary>
      
    
    
    
    <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/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
  </entry>
  
  <entry>
    <title>TSforge 使用方法</title>
    <link href="https://www.xrgzs.top/posts/tsforge"/>
    <id>https://www.xrgzs.top/posts/tsforge</id>
    <published>2025-02-25T08:00:00.000Z</published>
    <updated>2025-02-25T08:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>TSforge 开启 Windows / Office 激活的新纪元。</p><p>仓库：<a href="https://github.com/massgravel/TSforge">https://github.com/massgravel/TSforge</a></p><p>博客：<a href="https://massgrave.dev/blog/tsforge">https://massgrave.dev/blog/tsforge</a></p><h2 id="原理">原理</h2><p>TD；DR</p><ul><li><p>SPP：这里指 Windows 的软件保护平台（Software Protection Platform）</p></li><li><p>TS：SPP 的 Trusted Store，用于存储激活信息，如CID（电话激活确认ID）、HWID，采用加密方式存储</p></li><li><p>forge：伪造</p></li></ul><p>MASSGRAVE 团队给微软用于加密 SPP Trusted Store 的密钥给弄了出来，所以可以伪造激活状态存储到 SPP 的 Trusted Store 中</p><blockquote><p>正常来说，微软为了让那些无法联网的用户，通过拨打客服电话进行激活Windows或Office，那么需要给微软提供一组安装ID，这组ID是和电脑硬件以及密钥相关的，微软收到这组ID后，返回一组确认ID（即CID），用户在电脑上手动输入CID即可完美离线激活。那么从安装ID转化为CID的过程，是需要一套加密算法的，微软自己当然知道，如果想要得到CID，除非将这套算法破解。</p><p>破解这套算法的难度极大，迄今为止，仅Windows XP的电话激活的加密算法被完全攻克(可以直接计算出CID)，而此次TSForge并非是破解了电话激活的这套算法。那么它和电话激活有什么关系呢？</p><p>经常备份激活信息的小伙伴都知道，微软会把激活相关的数据（比如硬件HWID、CID等等）存储到Store文件夹里面（data.dat、tokens.dat）。这些操作都是通过微软软件保护平台（SPP）实现的，SPP负责保护和管理软件许可证，验证微软产品的合法性，防止未经授权的复制、篡改许可和启动功能。</p><p>MASSGRAVE团队在研究时发现，SPP将激活数据写入上述文件，需要一系列的加密、解密算法，一旦写入激活数据后即认为已经激活，且后续不再进行检查，被称作受信任的存储（Trusted Store）。加密解密只能通过SPP实现，所以作者不但需要搞清楚存储数据时加密、解密、签名检查和哈希检查这些过程，还需要搞清楚RSA私钥，经过探索addition-chain exponentiation运算，掌握了整个过程并得到了从Windows Vista到Windows 11的所有SPP的完整RSA密钥。</p><p>因此，作者拿到了所有的RSA私钥，则可以让SPP API往data.dat、tokens.dat等文件里存储激活数据，即便是伪造的数据依然可以存储进去，作者直接伪造了一组CID，这组数值由多个0组成。</p><p>当然了，除了存储CID之外，作者还存储了激活数据相关的HWID（这个用了一组适用于所有硬件的ID）等数据，所以使用这个方法激活后，即便是更换硬件，也不会影响激活状态。而对于真正使用微软提供的电话激活后，更改主要硬件后，激活状态也会被取消。</p><p>通过上面的简介，其实原作者就是伪造（Forge）了一些数据（Trusted Store），所以这种方法命名为TSForge。并且，大家可以看到，该激活方法并未修改任何Windows组件，和真正的电话激活一样，往存储数据的文件中写入了一些假数据而已。因此，该方案非常安全，没有任何副作用，激活后也不需要卸载。</p></blockquote><h3 id="激活方式">激活方式</h3><p>支持三种方式：</p><ul><li><p>KMS4k：适用于 KMS 密钥激活的版本，能够激活 4000 多年</p></li><li><p>AVMA4k：适用于 Windows Server 2012 R2+，能够激活 4000 多年</p></li><li><p>ZeroCID：适用于支持电话激活的版本，如OEM、零售版、MAK，能永久激活</p></li></ul><p>用 ZeroCID 能够获得最好的效果（永久激活高贵、罕见的零售版）</p><h3 id="支持版本">支持版本</h3><p>支持使用 SPP 的任何 MS 产品。</p><ul><li><p>Windows 7 / 8 / 8.1 / 10 /11</p></li><li><p>Windows Server 2008 R2 / 2012 / 2012 R2 / 2016 / 2019 / 2024</p></li><li><p>SPP接管的 Office 2013 / 2016 / 2019 / 2021 / 2024 on Windows 8+</p></li></ul><h3 id="限制">限制</h3><ul><li><p>首先 Windows XP 这种古董肯定不支持，因为连 SPP 都没有</p></li><li><p>Windows Vista / 2008 R1 因为加了驱动保护 SPP，所以激活要到 Windows PE 中进行</p></li><li><p>此工具使用 C# 编写，需要正确安装 .NET Framework 运行库</p></li><li><p>AVMA4k 不支持无法提供 Hyper-V Enlightenments 的虚拟机</p></li><li><p>不支持 OSPP（Office Software Protection Platform）</p></li><li><p>Windows 7 / 2008 R2 上安装的 Office 2010+ 用的是 OSPP，非系统 SPP 接管，因此不支持</p></li><li><p>Office 2010 用的是 OSPP，非系统 SPP 接管，因此不支持</p></li><li><p>另外这种激活方式无需联网，不太可靠，也不会像数字激活那样重装后联网还可以激活</p></li></ul><p>只有当您使用旧的 Windows 版本或想要激活 ESU 等插件以延长支持结束日期时，您才应该使用 TSforge</p><h2 id="获取-TSforge">获取 TSforge</h2><p>此处使用 Scoop 安装 TSforge</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:#ABB2BF">scoop bucket </span><span style="color:#56B6C2">add</span><span style="color:#ABB2BF"> sdoog https://github.</span><span style="color:#E06C75">com</span><span style="color:#ABB2BF">/</span><span style="color:#E06C75">xrgzs</span><span style="color:#ABB2BF">/</span><span style="color:#E06C75">sdoog</span></span><span class="line"><span style="color:#ABB2BF">scoop install </span><span style="color:#E06C75">sdoog</span><span style="color:#ABB2BF">/</span><span style="color:#E06C75">tsforge</span></span></code></pre></td></tr></tbody></table></figure><h2 id="激活-Windows">激活 Windows</h2><ol><li><p>使用管理员权限打开 CMD</p></li><li><p>查看当前系统许可证的激活ID和激活状态</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">cscript.exe C:\</span><span style="color:#E06C75">Windows</span><span style="color:#ABB2BF">\</span><span style="color:#E06C75">System32</span><span style="color:#ABB2BF">\slmgr.</span><span style="color:#E06C75">vbs</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">dlv</span></span></code></pre></td></tr></tbody></table></figure><p>如果当前的许可证版本不喜欢，可以执行下面两步更改激活ID</p></li><li><p>查看当前系统支持的所有许可证的激活ID</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">cscript.exe C:\</span><span style="color:#E06C75">Windows</span><span style="color:#ABB2BF">\</span><span style="color:#E06C75">System32</span><span style="color:#ABB2BF">\slmgr.</span><span style="color:#E06C75">vbs</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">dlv</span><span style="color:#ABB2BF"> all</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>安装其他激活ID</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">igpk</span><span style="color:#ABB2BF"> &#x3C;激活ID></span></span></code></pre></td></tr></tbody></table></figure><p>此时您的系统就会变成您设置激活ID的那个许可证，TSforge会自动算号</p><p>如果您有特定许可证的密钥，也可以直接通过 <code>slmgr.vbs /ipk &lt;密钥&gt;</code> 来切换许可证</p></li><li><p>（可选）删除删除产品密钥的唯一ID以防止在线验证（Windows 7 WAT）</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">duid</span></span></code></pre></td></tr></tbody></table></figure></li></ol><h3 id="零售版本">零售版本</h3><p>通过 ZeroCID 方式激活</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">zcid</span><span style="color:#ABB2BF"> &#x3C;激活ID></span></span></code></pre></td></tr></tbody></table></figure><h3 id="KMS版本">KMS版本</h3><p>通过 KMS4k 方式激活</p><p>（可选）为了避免之后反弹，可以先通过普通KMS激活180天</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:#ABB2BF">cscript.exe C:\</span><span style="color:#E06C75">Windows</span><span style="color:#ABB2BF">\</span><span style="color:#E06C75">System32</span><span style="color:#ABB2BF">\slmgr.</span><span style="color:#E06C75">vbs</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">skms</span><span style="color:#ABB2BF"> kms.03k.org</span></span><span class="line"><span style="color:#ABB2BF">cscript.exe C:\</span><span style="color:#E06C75">Windows</span><span style="color:#ABB2BF">\</span><span style="color:#E06C75">System32</span><span style="color:#ABB2BF">\slmgr.</span><span style="color:#E06C75">vbs</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">ato</span></span></code></pre></td></tr></tbody></table></figure><p>然后用 TSforge 激活 4000 年</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">kms4k</span><span style="color:#ABB2BF"> &#x3C;激活ID></span></span></code></pre></td></tr></tbody></table></figure><h3 id="KMS-HOST版本">KMS HOST版本</h3><p>虽然是KMS，但 <code>VOLUME_KMS_W10</code> 可以做到永久激活，不过无法使用普通KMS方式激活</p><p>查看当前系统支持的所有许可证的激活ID</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">cscript.exe C:\</span><span style="color:#E06C75">Windows</span><span style="color:#ABB2BF">\</span><span style="color:#E06C75">System32</span><span style="color:#ABB2BF">\slmgr.</span><span style="color:#E06C75">vbs</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">dlv</span><span style="color:#ABB2BF"> all</span></span></code></pre></td></tr></tbody></table></figure><p>找到 <code>VOLUME_KMS_W10</code> 字样的激活ID</p><p>然后使用 TSforge 安装激活ID，并进行 ZeroCID 永久激活</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:#ABB2BF">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">igpk</span><span style="color:#ABB2BF"> &#x3C;激活ID></span></span><span class="line"><span style="color:#ABB2BF">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">zcid</span><span style="color:#ABB2BF"> &#x3C;激活ID></span></span></code></pre></td></tr></tbody></table></figure><h2 id="激活-Office">激活 Office</h2><p>Office支持多种方式同时激活，您可以混合激活</p><blockquote><p>由于 Office 使用系统 SPP 接管，因此 Office 的SKU ID（=激活 ID）可以亦可通过 <code>slmgr.vbs /dlv all</code> 找到（如果没被接管是找不到的），但为了方便查看，此处使用 <code>OSPP.VBS</code></p></blockquote><p>首先需要弄清楚 OSPP.VBS 的路径，这里给的是 Office 2016+ 的路径，MSI和C2R的都一样</p><ul><li>Office 2010：<code>C:\Program Files\Microsoft Office\Office14\OSPP.VBS</code></li><li>Office 2013：<code>C:\Program Files\Microsoft Office\Office15\OSPP.VBS</code></li><li>Office 2016：<code>C:\Program Files\Microsoft Office\Office16\OSPP.VBS</code></li></ul><ol><li><p>使用管理员权限打开 CMD</p></li><li><p>查看当前Office许可证的SKU ID和激活状态</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">cscript.exe </span><span style="color:#98C379">"C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">dstatus</span></span></code></pre></td></tr></tbody></table></figure><p>如果不喜欢当前的许可证版本，可以执行下面两步更改SKU ID</p></li><li><p>查看当前系统支持的所有许可证的SKU ID</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">cscript.exe </span><span style="color:#98C379">"C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">dstatusall</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>安装其他SKU ID</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">igpk</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>此时您的系统就会变成您设置激活ID的那个许可证，TSforge会自动算号</p><p>如果您有特定的密钥，也可以直接通过 <code>OSPP.VBS /inpkey:&lt;密钥&gt;</code> 来切换许可证</p><p>如果是ClickToRun版本的Office，需要安装其他许可证，可以通过以下命令安装（修改<code>MondoVL</code>为您想要的SKU）：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">for</span><span style="color:#ABB2BF"> %a </span><span style="color:#E06C75">in</span><span style="color:#ABB2BF"> (</span><span style="color:#98C379">"C:\Program Files\Microsoft Office\root\Licenses16\MondoVL*.xrm-ms"</span><span style="color:#ABB2BF">) </span><span style="color:#C678DD">do</span><span style="color:#ABB2BF"> (</span></span><span class="line"><span style="color:#ABB2BF">   cscript.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> //</span><span style="color:#E06C75">nologo</span><span style="color:#98C379"> "C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">inslic</span><span style="color:#ABB2BF">:</span><span style="color:#98C379">"%a"</span></span><span class="line"><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure></li></ol><h3 id="零售版本-2">零售版本</h3><p>通过 ZeroCID 方式激活</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">zcid</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><h3 id="KMS版本-2">KMS版本</h3><p>通过 KMS4k 方式激活</p><p>（可选）为了避免之后反弹，可以先通过普通KMS激活180天</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">cscript.exe </span><span style="color:#98C379">"C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">sethst</span><span style="color:#ABB2BF">:kms.03k.org</span></span><span class="line"><span style="color:#ABB2BF">cscript.exe </span><span style="color:#98C379">"C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">setprt</span><span style="color:#ABB2BF">:</span><span style="color:#D19A66">1688</span></span><span class="line"><span style="color:#ABB2BF">cscript.exe </span><span style="color:#98C379">"C:\Program Files\Microsoft Office\Office16\OSPP.VBS"</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">act</span></span></code></pre></td></tr></tbody></table></figure><p>然后用 TSforge 激活 4000 年</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">kms4k</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><h3 id="KMS-HOST版本-2">KMS HOST版本</h3><p>虽然是KMS，但 <code>OfficeKMSHostVL_KMS_Host</code> 可以做到永久激活，不过无法使用普通KMS方式激活</p><p>需要注意 KMS HOST 版本的证书默认没有安装，需要手动下载安装</p><blockquote><ul><li>Office 2010 KMS HOST：<a href="https://download.microsoft.com/download/E/4/E/E4EF2B72-9F16-4D5F-9A1D-D9BE39A1C127/KeyManagementServiceHost_en-us.exe">https://download.microsoft.com/download/E/4/E/E4EF2B72-9F16-4D5F-9A1D-D9BE39A1C127/KeyManagementServiceHost_en-us.exe</a></li><li>Office 2013 KMS HOST：<a href="https://download.microsoft.com/download/3/4/2/342F1FEA-CCB2-4F68-A6DE-041934383C18/office2013volumelicensepack_4701-1000_en-us_x86.exe">https://download.microsoft.com/download/3/4/2/342F1FEA-CCB2-4F68-A6DE-041934383C18/office2013volumelicensepack_4701-1000_en-us_x86.exe</a></li><li>Office 2016 KMS HOST：<a href="https://download.microsoft.com/download/5/E/3/5E329B97-6DC5-4AA7-9AA7-A5B5D46AF82F/office2016volumelicensepack_4324-1002_en-us_x86.exe">https://download.microsoft.com/download/5/E/3/5E329B97-6DC5-4AA7-9AA7-A5B5D46AF82F/office2016volumelicensepack_4324-1002_en-us_x86.exe</a></li><li>Office 2019 KMS HOST：<a href="https://download.microsoft.com/download/F/6/0/F60CA746-5F03-482D-A331-15F38FB4AACA/office2019volumelicensepack_x86.exe">https://download.microsoft.com/download/F/6/0/F60CA746-5F03-482D-A331-15F38FB4AACA/office2019volumelicensepack_x86.exe</a></li><li>Office 2021 KMS HOST：<a href="https://download.microsoft.com/download/8/e/e/8eef6160-396a-4c26-830d-9e2a24c00309/Office2021VolumeLicensePack_x86.exe">https://download.microsoft.com/download/8/e/e/8eef6160-396a-4c26-830d-9e2a24c00309/Office2021VolumeLicensePack_x86.exe</a></li><li>Office 2024 KMS HOST：<a href="https://download.microsoft.com/download/1/4/0/140c97ae-7360-4dfc-9ba0-5f509600a06e/Office2024VolumeLicensePack_x86.exe">https://download.microsoft.com/download/1/4/0/140c97ae-7360-4dfc-9ba0-5f509600a06e/Office2024VolumeLicensePack_x86.exe</a></li></ul></blockquote><p>安装完成后，可以通过 <code>ospp.vbs /dstatusall</code> 查看</p><p>然后使用 TSforge 安装SKU ID，并进行 ZeroCID 永久激活</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:#ABB2BF">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">igpk</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span><span class="line"><span style="color:#ABB2BF">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">zcid</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><p>如果需要作为服务器使用，可以使用 TSforge 的“KMS Charger”充值到25个客户端</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">tsforge.</span><span style="color:#E06C75">exe</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75">kmsc</span><span style="color:#ABB2BF"> &#x3C;</span><span style="color:#E06C75">SKUID</span><span style="color:#ABB2BF">></span></span></code></pre></td></tr></tbody></table></figure><h2 id="演示">演示</h2><h3 id="激活-Windows-7-旗舰版">激活 Windows 7 旗舰版</h3><p><img src="tsforge/8f6839523cb009e8bbc92e9980624937.avif" alt="ZeroCID"></p><h3 id="激活-Windows-10-专业版">激活 Windows 10 专业版</h3><p><img src="tsforge/409c75db1702aef2032c52e42b51e0da.avif" alt="KMS4k"></p><p><img src="tsforge/9197efb5f9850b93fe54e31af9258803.avif" alt="指定数字激活KEY+ZeroCID"></p><h3 id="激活-Office-2013">激活 Office 2013</h3><p><img src="tsforge/9197efb5f9850b93fe54e31af9258803.avif" alt="KMS4k+ZeroCID"></p><h2 id="结尾">结尾</h2><p>TSForge暴露了微软SPP长期依赖“单向信任机制”的缺陷，即数据写入后缺乏二次验证。尽管用户可免费激活系统，但此漏洞可能被滥用，导致盗版泛滥。微软尚未公开回应，但预计将发布紧急补丁修复。</p><p>尽管TSForge技术强大，但请尊重版权，仅用于学习测试，正式使用请购买正版</p><h2 id="参考">参考</h2><p><a href="https://github.com/massgravel/TSforge">https://github.com/massgravel/TSforge</a></p><p><a href="https://massgrave.dev/blog/tsforge">https://massgrave.dev/blog/tsforge</a></p><p><a href="https://mp.weixin.qq.com/s/7nuP0WuGqze7aNqSj0lW6w">https://mp.weixin.qq.com/s/7nuP0WuGqze7aNqSj0lW6w</a></p><p><a href="https://mp.weixin.qq.com/s/SVWSRzeCHFMipQhMstTDyw">https://mp.weixin.qq.com/s/SVWSRzeCHFMipQhMstTDyw</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;TSforge 开启 Windows / Office 激活的新纪元。&lt;/p&gt;
&lt;p&gt;仓库：&lt;a href=&quot;https://github.com/massgravel/TSforge&quot;&gt;https://github.com/massgravel/TSforge&lt;/a&gt;&lt;/</summary>
      
    
    
    
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Windows" scheme="https://www.xrgzs.top/tags/windows/"/>
    
    <category term="Office" scheme="https://www.xrgzs.top/tags/office/"/>
    
    <category term="激活" scheme="https://www.xrgzs.top/tags/%E6%BF%80%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>解决 MinIO 反代 403 SignatureDoesNotMatch</title>
    <link href="https://www.xrgzs.top/posts/minio-403"/>
    <id>https://www.xrgzs.top/posts/minio-403</id>
    <published>2025-02-19T01:00:00.000Z</published>
    <updated>2025-02-19T01:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近开始折腾自建存储，原计划使用 WebDAV。但发现套了 Cloudflare CDN 只能上传 100MB 的文件，直连服务器上传比较慢，因此考虑使用 MinIO 对象存储 + 分片上传绕过这个限制。不过发现 Cloudflare + MinIO 不是开箱即用的，存在很多坑，比如这个 SignatureDoesNotMatch 问题，没搜到一篇有用的文章，故在此记录一下思路和解决方案。</p><h2 id="问题">问题</h2><pre class="mermaid">flowchart LR    n1["Rclone"] --> n2["Cloudflare"]    n2 --> n3["MinIO"]</pre><p>如图部署的MinIO对象存储服务器，无法使用客户端链接，报错 <code>SignatureDoesNotMatch</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">❯</span><span style="color:#98C379"> rclone</span><span style="color:#D19A66"> --config</span><span style="color:#98C379"> rclone.conf</span><span style="color:#98C379"> lsd</span><span style="color:#98C379"> minio:</span></span><span class="line"><span style="color:#61AFEF">2025/**/**</span><span style="color:#E5C07B"> **</span><span style="color:#98C379">:</span><span style="color:#E5C07B">**</span><span style="color:#98C379">:</span><span style="color:#E5C07B">**</span><span style="color:#98C379"> ERROR</span><span style="color:#98C379"> :</span><span style="color:#98C379"> :</span><span style="color:#98C379"> error</span><span style="color:#98C379"> listing:</span><span style="color:#98C379"> operation</span><span style="color:#98C379"> error</span><span style="color:#98C379"> S3:</span><span style="color:#98C379"> ListObjectsV2,</span><span style="color:#98C379"> https</span><span style="color:#98C379"> response</span><span style="color:#98C379"> error</span><span style="color:#98C379"> StatusCode:</span><span style="color:#98C379"> 403,</span><span style="color:#98C379"> RequestID:</span><span style="color:#E5C07B"> *****</span><span style="color:#98C379">,</span><span style="color:#98C379"> HostID:</span><span style="color:#E5C07B"> *****</span><span style="color:#98C379">,</span><span style="color:#98C379"> api</span><span style="color:#98C379"> error</span><span style="color:#98C379"> SignatureDoesNotMatch:</span><span style="color:#98C379"> The</span><span style="color:#98C379"> request</span><span style="color:#98C379"> signature</span><span style="color:#98C379"> we</span><span style="color:#98C379"> calculated</span><span style="color:#98C379"> does</span><span style="color:#98C379"> not</span><span style="color:#98C379"> match</span><span style="color:#98C379"> the</span><span style="color:#98C379"> signature</span><span style="color:#98C379"> you</span><span style="color:#98C379"> provided.</span><span style="color:#98C379"> Check</span><span style="color:#98C379"> your</span><span style="color:#98C379"> key</span><span style="color:#98C379"> and</span><span style="color:#98C379"> signing</span><span style="color:#98C379"> method.</span></span><span class="line"><span style="color:#61AFEF">2025/**/**</span><span style="color:#E5C07B"> **</span><span style="color:#98C379">:</span><span style="color:#E5C07B">**</span><span style="color:#98C379">:</span><span style="color:#E5C07B">**</span><span style="color:#98C379"> NOTICE:</span><span style="color:#98C379"> Failed</span><span style="color:#98C379"> to</span><span style="color:#98C379"> lsd</span><span style="color:#98C379"> with</span><span style="color:#D19A66"> 2</span><span style="color:#98C379"> errors:</span><span style="color:#98C379"> last</span><span style="color:#98C379"> error</span><span style="color:#98C379"> was:</span><span style="color:#98C379"> operation</span><span style="color:#98C379"> error</span><span style="color:#98C379"> S3:</span><span style="color:#98C379"> ListObjectsV2,</span><span style="color:#98C379"> https</span><span style="color:#98C379"> response</span><span style="color:#98C379"> error</span><span style="color:#98C379"> StatusCode:</span><span style="color:#98C379"> 403,</span><span style="color:#98C379"> RequestID:</span><span style="color:#E5C07B"> *****</span><span style="color:#98C379">,</span><span style="color:#98C379"> HostID:</span><span style="color:#E5C07B"> *****</span><span style="color:#98C379">,</span><span style="color:#98C379"> api</span><span style="color:#98C379"> error</span><span style="color:#98C379"> SignatureDoesNotMatch:</span><span style="color:#98C379"> The</span><span style="color:#98C379"> request</span><span style="color:#98C379"> signature</span><span style="color:#98C379"> we</span><span style="color:#98C379"> calculated</span><span style="color:#98C379"> does</span><span style="color:#98C379"> not</span><span style="color:#98C379"> match</span><span style="color:#98C379"> the</span><span style="color:#98C379"> signature</span><span style="color:#98C379"> you</span><span style="color:#98C379"> provided.</span><span style="color:#98C379"> Check</span><span style="color:#98C379"> your</span><span style="color:#98C379"> key</span><span style="color:#98C379"> and</span><span style="color:#98C379"> signing</span><span style="color:#98C379"> method.</span></span></code></pre></td></tr></tbody></table></figure><h2 id="解决过程">解决过程</h2><p>重装 + 升级最新版本：无效。</p><p>使用密码直连：无效。</p><p>控制台设置 Access Policy 为 Public：无效。</p><p>直连服务器没有问题：</p><pre class="mermaid">flowchart LR    n1["Client"] --> n2["MinIO"]</pre><p>所以问题可能出现在 Cloudflare CDN 上。</p><p>查到这两篇文章让关缓存：</p><ul><li><p><a href="https://github.com/minio/minio/discussions/20188">https://github.com/minio/minio/discussions/20188</a></p></li><li><p><a href="https://www.linkedin.com/pulse/resolving-minio-behind-cloudflare-proxy-403-error-bhavesh-deshmukh-pk1nf">https://www.linkedin.com/pulse/resolving-minio-behind-cloudflare-proxy-403-error-bhavesh-deshmukh-pk1nf</a></p></li></ul><p>但屁用没有，抓到响应头：<code>cf-cache-status: DYNAMIC</code>。</p><p>确认 Cloudflare Dashboard 中已经添加好 Cache Rule。</p><p>确认未使用 SAAS 域名。</p><p>确认未开启标头转换，这个应该只能改响应头，改不了请求头。</p><p><img src="minio-403/3685e90832d3a689362c4306a22b9a16.png" alt=""></p><p>最后尝试使用 NGINX 反代 + 改头，更改架构为：</p><pre class="mermaid">flowchart LR    n1["Client"] --> n2["Cloudflare"]    n2 --> n3["Nginx"]    n3 --> n4["MinIO"]</pre><p>MinIO 官方给的配置：<a href="https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html">https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html</a></p><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><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">upstream</span><span style="color:#ABB2BF"> minio_s3 &#123;</span></span><span class="line"><span style="color:#C678DD">   least_conn</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-01.internal-domain.com:9000;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-02.internal-domain.com:9000;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-03.internal-domain.com:9000;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-04.internal-domain.com:9000;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">upstream</span><span style="color:#ABB2BF"> minio_console &#123;</span></span><span class="line"><span style="color:#C678DD">   least_conn</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-01.internal-domain.com:9001;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-02.internal-domain.com:9001;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-03.internal-domain.com:9001;</span></span><span class="line"><span style="color:#C678DD">   server</span><span style="color:#ABB2BF"> minio-04.internal-domain.com:9001;</span></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">   listen </span><span style="color:#ABB2BF"> [::]:80;</span></span><span class="line"><span style="color:#C678DD">   server_name </span><span style="color:#ABB2BF"> minio.example.net;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">   # Allow special characters in headers</span></span><span class="line"><span style="color:#C678DD">   ignore_invalid_headers </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">   # Allow any size file to be uploaded.</span></span><span class="line"><span style="color:#7F848E;font-style:italic">   # Set to a value such as 1000m; to restrict file size to a specific value</span></span><span class="line"><span style="color:#C678DD">   client_max_body_size </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">   # Disable buffering</span></span><span class="line"><span style="color:#C678DD">   proxy_buffering </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">   proxy_request_buffering </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">;</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_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">http_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:#C678DD">      proxy_connect_timeout </span><span style="color:#D19A66">300</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      # Default is HTTP/1, keepalive is only enabled in HTTP/1.1</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 style="color:#C678DD">      chunked_transfer_encoding </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">      proxy_pass </span><span style="color:#ABB2BF">https://minio_s3; </span><span style="color:#7F848E;font-style:italic"># This uses the upstream directive definition to load balance</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"> /minio/ui/ &#123;</span></span><span class="line"><span style="color:#C678DD">      rewrite</span><span style="color:#E06C75"> ^/minio/ui/(.*) /$</span><span style="color:#ABB2BF">1 </span><span style="color:#C678DD">break</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">      proxy_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">http_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:#C678DD">      proxy_set_header </span><span style="color:#ABB2BF">X-NginX-Proxy </span><span style="color:#D19A66">true</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">      # This is necessary to pass the correct IP to be hashed</span></span><span class="line"><span style="color:#C678DD">      real_ip_header </span><span style="color:#ABB2BF">X-Real-IP;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">      proxy_connect_timeout </span><span style="color:#D19A66">300</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">      # To support websockets in MinIO versions released after January 2023</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">Upgrade $</span><span style="color:#E06C75">http_upgrade</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">"upgrade"</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      # Some environments may encounter CORS errors (Kubernetes + Nginx Ingress)</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      # Uncomment the following line to set the Origin request to an empty string</span></span><span class="line"><span style="color:#7F848E;font-style:italic">      # proxy_set_header Origin '';</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">      chunked_transfer_encoding </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">;</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">      proxy_pass </span><span style="color:#ABB2BF">https://minio_console; </span><span style="color:#7F848E;font-style:italic"># This uses the upstream directive definition to load balance</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><p>部分文章说需要更改 Host 为 IP:Port：</p><p><a href="https://blog.csdn.net/a_123_4/article/details/136448688">https://blog.csdn.net/a_123_4/article/details/136448688</a></p><p>结果还是有问题。</p><p>最后找到这个 Issue：</p><p><a href="https://github.com/minio/minio/issues/15506">https://github.com/minio/minio/issues/15506</a></p><p>说 Cloudflare 存在改头问题导致签名不匹配。</p><p>提到还需添加 <code>Accept-Encoding: indentity</code>，这里添加后就能连上，问题解决。</p><p>经过测试，最简配置，少一个都不行：</p><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">location</span><span style="color:#ABB2BF"> / &#123;</span></span><span class="line"><span style="color:#C678DD">  proxy_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">http_host</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">  proxy_set_header </span><span style="color:#ABB2BF">Accept-Encoding </span><span style="color:#98C379">"identity"</span><span style="color:#ABB2BF">;</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_pass </span><span style="color:#ABB2BF">http://127.0.0.1:9000;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>其他的配置不需要都能连上，当然最好都按照官方的添加一下。</p><h2 id="解决方案">解决方案</h2><p>NGINX 反代 + 改头：</p><pre class="mermaid">flowchart LR    n1["Client"] --> n2["Cloudflare"]    n2 --> n3["Nginx"]    n3 --> n4["MinIO"]</pre><figure class="highlight nginx"><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">proxy_set_header </span><span style="color:#ABB2BF">Host $</span><span style="color:#E06C75">http_host</span><span style="color:#ABB2BF">;</span></span><span class="line"><span style="color:#C678DD">proxy_set_header </span><span style="color:#ABB2BF">Accept-Encoding </span><span style="color:#98C379">"identity"</span><span style="color:#ABB2BF">;</span></span></code></pre></td></tr></tbody></table></figure><h2 id="复盘">复盘</h2><p>查看客户端的请求，发现 <code>accept-encoding: identity</code>，估计是传回服务器的时候被改成 <code>gzip</code> 之类的了。响应的 MIME 类型是 <code>application/xml</code>。</p><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Encoding">https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Encoding</a></p><blockquote><p><code>identity</code>：表示恒等函数（即不作任何修改或压缩）。即使省略，此值始终被视为是可接受的。</p></blockquote><p>由于机器性能不佳，我这里的 NGINX 已经关闭 GZIP 压缩。</p><figure class="highlight nginx"><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">gzip </span><span style="color:#D19A66">off</span><span style="color:#ABB2BF">; </span></span></code></pre></td></tr></tbody></table></figure><p>所以 NGINX 也有可能会出现这个问题，具体情况具体分析。</p><p>尝试添加压缩规则，发现无法提交规则：<code>exceeded the maximum number of rules in the phase http_response_compression: 1 out of 0</code>，应该是只对付费用户开放。</p><p><img src="minio-403/c0105d4e6830fee85731691c79ebbd4d.png" alt=""></p><p>在这个页面发现这篇文档：<a href="https://developers.cloudflare.com/speed/optimization/content/compression/">https://developers.cloudflare.com/speed/optimization/content/compression/</a></p><p>从客户端发送请求到 Cloudflare CDN，没有对请求头的 <code>Accept-Encoding</code> 进行修改：</p><p><img src="minio-403/a4ce405c5f0fbf2f4d6cd0d60f708f3a.png" alt=""></p><p>从 Cloudflare CDN 将客户端的请求转发回服务器，发现对请求头的 <code>Accept-Encoding</code> 进行了修改：</p><p><img src="minio-403/b98445b1255bcdae503b408a596c44a6.png" alt=""></p><p>反正通篇文章没有提到这个 <code>identity</code>。</p><p>具体验证被改成啥，服务器 NGINX 上加个头试试：</p><figure class="highlight nginx"><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">add_header </span><span style="color:#ABB2BF">IF-CF-Modify-Accept-Encoding $</span><span style="color:#E06C75">http_accept_encoding</span><span style="color:#ABB2BF">;</span></span></code></pre></td></tr></tbody></table></figure><p>客户端响应到：<code>if-cf-modify-accept-encoding: gzip, br</code>，看来真的会被改头。另外可以发现头的 Key 被改成了小写。</p><p>关于响应内容，客户端没有出现乱码，返回的响应内容应该是能够正常解压的。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近开始折腾自建存储，原计划使用 WebDAV。但发现套了 Cloudflare CDN 只能上传 100MB 的文件，直连服务器上传比较慢，因此考虑使用 MinIO 对象存储 + 分片上传绕过这个限制。不过发现 Cloudflare + MinIO 不是开箱即用的，存在很</summary>
      
    
    
    
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Cloudflare" scheme="https://www.xrgzs.top/tags/cloudflare/"/>
    
    <category term="MinIO" scheme="https://www.xrgzs.top/tags/minio/"/>
    
    <category term="Nginx" scheme="https://www.xrgzs.top/tags/nginx/"/>
    
  </entry>
  
  <entry>
    <title>Scoop 搭建 Windows 开发环境</title>
    <link href="https://www.xrgzs.top/posts/scoop-dev-setup"/>
    <id>https://www.xrgzs.top/posts/scoop-dev-setup</id>
    <published>2025-01-09T05:00:00.000Z</published>
    <updated>2025-08-12T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Scoop 是一个 Windows 下基于 PowerShell 编写的的第三方包管理器，能够自动下载、解压、设置环境变量、添加 shim、持久化用户数据。</p><p>本文将使用 Scoop 来安装一些工具，简化开发环境搭建，让开发人员重装系统后可以更快配置环境。</p><p>本文提供的方法非特别说明无需特殊网络环境即可直接下载安装，并配置好国内镜像。</p><h2 id="安装-Scoop">安装 Scoop</h2><p>这里使用我们的<a href="https://github.com/xrgzs/scoop">特别版 Scoop</a>，加入了一些特性，能够在国内环境自动切换镜像使用。</p><p>打开 PowerShell，执行以下命令安装 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">irm c.xrgzs.top</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">c</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">scoop | iex</span></span></code></pre></td></tr></tbody></table></figure><p>执行完上面的命令，你的电脑中已经安装好了 Git for Windows、7-Zip、Aria2，并添加了常用的仓库，可以基本满足使用。</p><blockquote><p>TIPS：正常情况下 Scoop 安装完成的软件不会创建桌面快捷方式，可以在开始菜单的 Scoop Apps 文件夹中找到，将其拖到桌面即可。如果你希望自动创建快捷方式到桌面，可以使用桌面整理软件给 <code>%APPDATA%\Microsoft\Windows\Start Menu\Programs\Scoop Apps</code> 映射到格子即可。</p><p>但如果您使用我们提供的修改版，默认情况会同时创建快捷方式到桌面，可以取消，参考 <a href="https://github.com/xrgzs/scoop?tab=readme-ov-file">README</a>。</p></blockquote><h2 id="配置-Git">配置 Git</h2><p>配置好 Git 以便能够拉取提交代码。如果不需要，可以跳过此步骤。</p><p>需要准备一个电子邮箱，且绑定到 GitHub / Gitee 等代码托管平台。</p><p>打开 Git Bash：</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">git-bash.exe</span></span></code></pre></td></tr></tbody></table></figure><p>配置 Git 的用户名和邮箱：</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">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> user.name</span><span style="color:#98C379"> "Your Name"</span></span><span class="line"><span style="color:#61AFEF">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> user.email</span><span style="color:#98C379"> "youremail@example.com"</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">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> credential.helper</span><span style="color:#98C379"> store</span></span></code></pre></td></tr></tbody></table></figure><p>（可选）配置默认文本编辑器（如果不想用默认的 Vim）：</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">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> core.editor</span><span style="color:#98C379"> "code --wait"</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">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --list</span></span></code></pre></td></tr></tbody></table></figure><h3 id="配置-SSH-推拉代码">配置 SSH 推拉代码</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">git</span><span style="color:#98C379"> clone</span><span style="color:#98C379"> git@gitee.com/user/repo.git</span></span></code></pre></td></tr></tbody></table></figure><p>生成 SSH 密钥：</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">ssh-keygen</span><span style="color:#D19A66"> -t</span><span style="color:#98C379"> ed25519</span></span></code></pre></td></tr></tbody></table></figure><p>根据提示输入路径（回车即可）、密码（可为空）。</p><p>密钥和公钥默认存放到 <code>%USERPROFILE%\.ssh</code> 目录下，其中 <code>id_ed25519</code> 为私钥，<code>id_ed25519.pub</code> 为公钥。</p><p>显示 SSH 公钥：</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"> .ssh/id_ed25519.pub</span></span></code></pre></td></tr></tbody></table></figure><p>将 SSH 公钥的内容复制到 GitHub / Gitee 等平台的 SSH 管理中。</p><p>TIPS：如果你有服务器的话，可以将这个文件也上传到服务器上的 <code>~\.ssh\id_ed25519.pub</code>，这样就可以实现 SSH 免密登录。</p><p>此时即可正常推拉代码，且提交具有身份验证，网页端会显示 <code>Verified</code>。</p><h3 id="配置-HTTPS">配置 HTTPS</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">git</span><span style="color:#98C379"> clone</span><span style="color:#98C379"> https://gitee.com/user/repo.git</span></span></code></pre></td></tr></tbody></table></figure><p>这种情况下，拉取/推送代码时可以配置登录信息。一般情况下到这里已经够用。</p><p>一些仓库要求强制 <code>Verified</code>，需要配置 GPG 签名验证身份。</p><h4 id="配置-GPG-签名">配置 GPG 签名</h4><p>生成 GPG 私钥：</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"># Ensure gpg is installed</span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --version</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Generate GPG key</span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --full-generate-key</span></span></code></pre></td></tr></tbody></table></figure><p>根据提示输入用户名、电子邮箱（要求同上），还有私钥的密码（请妥善保管，提交 commit 时要输入）。</p><p>生成 GPG 公钥：</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"># Get your GPG key ID</span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --list-secret-keys</span><span style="color:#D19A66"> --keyid-format=long</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Generate GPG public key as ASCII armored output</span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --armor</span><span style="color:#D19A66"> --export</span><span style="color:#98C379"> &#123;key_id&#125;</span></span></code></pre></td></tr></tbody></table></figure><p>将得到的 GPG 公钥复制到 GitHub / Gitee 等平台的 GPG 管理中。</p><p>配置 Git 的默认 GPG 签名：</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">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> user.signingkey</span><span style="color:#98C379"> &#123;key_id&#125;</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Use GPG signature for commit by default</span></span><span class="line"><span style="color:#61AFEF">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> commit.gpgsign</span><span style="color:#D19A66"> true</span></span><span class="line"><span style="color:#61AFEF">git</span><span style="color:#98C379"> config</span><span style="color:#D19A66"> --global</span><span style="color:#98C379"> tag.gpgSign</span><span style="color:#D19A66"> true</span></span></code></pre></td></tr></tbody></table></figure><p>配置完成后每次 commit 时都会自动签名，并在网页端显示 <code>Verified</code>。</p><h4 id="备份-GPG-密钥">备份 GPG 密钥</h4><p>这里我们的密钥保存到 <code>~/.gnupg</code> ，如果重装系统可以备份这个目录。或者使用以下命令将设备的所有证书导出到一个设备：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">cd f:</span></span><span class="line"><span style="color:#ABB2BF">gpg </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">armor </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">export > mygpg.pub</span></span><span class="line"><span style="color:#ABB2BF">gpg </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">armor </span><span style="color:#56B6C2">--export--</span><span style="color:#ABB2BF">secret</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">key > mygpg.sec</span></span></code></pre></td></tr></tbody></table></figure><p>需要注意的是，私钥一定不能丢失或外泄。为了以防万一，我们生成一份<strong>吊销证书</strong>，用以在特殊情况时吊销该密钥，当然吊销证书也应该妥善保管。</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:#ABB2BF">gpg </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">armor </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">gen</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">revoke > mygpg.rev</span></span></code></pre></td></tr></tbody></table></figure><h4 id="（可选）发布-GPG-公钥">（可选）发布 GPG 公钥</h4><p><strong>将公钥发布到密钥服务器上是不可逆行为，请谨慎操作</strong></p><p>一些公钥服务器：</p><ul><li><p>Ubuntu：<a href="http://keyserver.ubuntu.com">keyserver.ubuntu.com</a></p></li><li><p>MIT：<a href="http://pgp.mit.edu">pgp.mit.edu</a></p></li></ul><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">gpg </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">keyserver </span><span style="color:#56B6C2">keyserver.ubuntu.com</span><span style="color:#56B6C2"> --send-keys</span><span style="color:#ABB2BF"> &#123;key_id&#125;</span></span></code></pre></td></tr></tbody></table></figure><h4 id="（可选）导入平台-GPG-公钥">（可选）导入平台 GPG 公钥</h4><p>信任 GitHub 平台的默认公钥：</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">curl</span><span style="color:#98C379"> https://github.com/web-flow.gpg</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --import</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># GitHub (web-flow commit signing) &#x3C;noreply@github.com></span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --sign-key</span><span style="color:#98C379"> 4AEE18F83AFDEB23</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># GitHub &#x3C;noreply@github.com></span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --sign-key</span><span style="color:#98C379"> B5690EEEBB952194</span></span></code></pre></td></tr></tbody></table></figure><p>信任 Gitee 平台的默认公钥：</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">curl</span><span style="color:#98C379"> https://gitee.com/gitee.gpg</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --import</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Gitee (Gitee web-flow commit signing.) &#x3C;noreply@gitee.com></span></span><span class="line"><span style="color:#61AFEF">gpg</span><span style="color:#D19A66"> --sign-key</span><span style="color:#98C379"> 173E9B9CA92EEF8F</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">git</span><span style="color:#98C379"> log</span><span style="color:#D19A66"> --show-signature</span></span></code></pre></td></tr></tbody></table></figure><h4 id="（可选）安装-Gpg4win">（可选）安装 Gpg4win</h4><p>安装<code>Gpg4win</code>的目的是替换<code>git-for-windows</code>默认的<code>pinentry</code>程序（用于输入密码），<code>Gpg4win</code> 会提供一个 <code>pinentry-qt</code> 程序来代替默认的 <code>pinentry</code>，主要是界面好看一点。其次，Gpg4win 提供了一个GUI 程序 <code>Kleopatra</code>，可以更方便地管理密钥和使用 GPG 进行签名、加密。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">gpg4win</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test</span></span><span class="line"><span style="color:#ABB2BF">gpg </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><p>然后我们需要修改 Git 的默认配置使其调用 Scoop 中安装的 Gpg4win：</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">git config </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">global gpg.program </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop which gpg | </span><span style="color:#56B6C2">Resolve-Path</span><span style="color:#C678DD">)</span><span style="color:#98C379">"</span></span></code></pre></td></tr></tbody></table></figure><p>这里有个坑，默认情况下 Gpg4win 的默认配置目录为 <code>%APPDATA%\gnupg</code>，并非 Git for Windows 默认的 <code>~\.gnupg</code>，会导致我们打开 Kleopatra 看不到之前添加的密钥，根据文档，需要我们手动修改一下注册表或者环境变量：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#56B6C2">Set-ItemProperty</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"HKCU:\Software\GNU\GnuPG"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name </span><span style="color:#98C379">'HomeDir'</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Value </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:USERPROFILE</span><span style="color:#98C379">\.gnupg"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Type String </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># reg.exe add "HKCU\Software\GNU\GnuPG" /v HomeDir /t REG_SZ /d "$env:USERPROFILE\.gnupg" /f</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># [Environment]::SetEnvironmentVariable("GNUPGHOME", "$env:USERPROFILE\.gnupg", "User")</span></span></code></pre></td></tr></tbody></table></figure><ul><li><a href="https://www.gpg4win.org/doc/en/gpg4win-compendium_28.html">Gpg4win Compendium – 22 Files and settings in Gpg4win</a></li><li><a href="https://gnupg.org/documentation/manuals/gnupg/Agent-Options.html#option-_002d_002dhomedir">Agent Options (Using the GNU Privacy Guard)</a></li></ul><p>此时执行 <code>gpg --version</code> 即可看到 Home 路径已被修改。如果 Kleopatra 已经运行了的话需要托盘退出重启一下才能看到更改。</p><h2 id="配置终端">配置终端</h2><h3 id="安装-Windows-Terminal">安装 Windows Terminal</h3><p>Windows 11 已经默认安装，除非你是 LTSC 版本。</p><p><strong>建议通过商店安装 <a href="https://apps.microsoft.com/detail/9N0DX20HK701">Windows Terminal</a>，并配置为默认终端。</strong></p><p>亦可使用 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 install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">windows</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">terminal</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix windows</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">terminal</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-context.reg"</span></span></code></pre></td></tr></tbody></table></figure><p>必须要安装一个终端软件，不要使用系统内置的 <code>conhost.exe</code>，不然折磨死人。</p><h3 id="升级到-PowerShell-Core">升级到 PowerShell Core</h3><p>由于 Windows 自带的 PowerShell 5.1 速度慢、兼容性差、功能少，且默认输出带 BOM，这里建议升级到跨平台的 PowerShell Core。在其他 Shell 中，Scoop 会自动切换为 PowerShell Core。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Install PowerShell Core Non-portable</span></span><span class="line"><span style="color:#ABB2BF">scoop install gsudo</span></span><span class="line"><span style="color:#ABB2BF">gsudo scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">pwsh</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">np</span></span><span class="line"><span style="color:#ABB2BF">scoop uninstall pwsh</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">np</span></span></code></pre></td></tr></tbody></table></figure><p>为了避免文件编码错误，我们<strong>重启到 PowerShell</strong> 完成后续的操作。</p><h3 id="优化-Windows-Terminal-配置">优化 Windows Terminal 配置</h3><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><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"># 获取 WT 配置路径</span></span><span class="line"><span style="color:#E06C75">$wtSettingsPath</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$($</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Item</span><span style="color:#C678DD"> $</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Command</span><span style="color:#ABB2BF"> scoop.ps1).Path).Directory.Parent.FullName</span><span style="color:#C678DD">)</span><span style="color:#98C379">\apps\windows-terminal\current\settings"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Where-Object</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#56B6C2">Test-Path</span><span style="color:#98C379"> "$_\settings.json"</span><span style="color:#ABB2BF"> &#125; | </span><span style="color:#56B6C2">Select-Object</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">First </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:#E06C75">$wtSettings</span><span style="color:#56B6C2"> =</span><span style="color:#56B6C2"> Get-Content</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$wtSettingsPath</span><span style="color:#98C379">\settings.json"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">ConvertFrom-Json</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">AsHashtable</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 设置启动居中</span></span><span class="line"><span style="color:#E06C75">$wtSettings.centerOnLaunch</span><span style="color:#56B6C2"> =</span><span style="color:#D19A66"> $True</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"># $wtSettings.useAcrylicInTabRow = $True</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.defaults.opacity</span><span style="color:#56B6C2"> =</span><span style="color:#D19A66"> 90</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.defaults.useAcrylic</span><span style="color:#56B6C2"> =</span><span style="color:#D19A66"> $True</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"># $wtSettings | ConvertTo-Json -Depth 100 | Set-Content  "$wtSettingsPath\settings.json" -Encoding UTF8</span></span></code></pre></td></tr></tbody></table></figure><h4 id="（可选）设置-CMD-为默认-Shell">（可选）设置 CMD 为默认 Shell</h4><p>由于 PowerShell 中部分体验和 CMD 不一致，个人喜欢将 CMD 设为默认 Shell，然后再安装 Clink 来增强 CMD 的体验。</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:#E06C75">$wtSettings.profiles.defaultProfile</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;0caa0dad-35be-5f56-a8ff-afceeeaa6101&#125;"</span></span></code></pre></td></tr></tbody></table></figure><h4 id="安装-Nerd-Font">安装 Nerd Font</h4><p>这里我们需要安装 Nerd Font 并将其设为默认，避免一些字符无法显示。个人喜欢使用 Cascadia Code。</p><h5 id="Cascadia-Code">Cascadia Code</h5><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install nerd</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">fonts</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">Cascadia</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Code</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Apply to Windows Terminal</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.defaults.font</span><span style="color:#56B6C2"> =</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123; </span><span style="color:#E06C75">face</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "Cascadia Code NF"</span><span style="color:#ABB2BF"> &#125;</span></span></code></pre></td></tr></tbody></table></figure><p>新装的字体需要重启 Windows Terminal 才能被使用，但先不着急重启。</p><h4 id="添加-Git-Bash-到-Windows-Terminal">添加 Git Bash 到 Windows Terminal</h4><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#E06C75">$wtSettings.profiles.list</span><span style="color:#56B6C2"> +=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">    commandline</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix git</span><span style="color:#C678DD">)</span><span style="color:#98C379">\bin\bash.exe -i -l"</span></span><span class="line"><span style="color:#E06C75">    guid</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;2ece5bfe-50ed-5f3a-ab87-5cd4baafed2b&#125;"</span></span><span class="line"><span style="color:#E06C75">    icon</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix git</span><span style="color:#C678DD">)</span><span style="color:#98C379">\mingw64\share\git\git-for-windows.ico"</span></span><span class="line"><span style="color:#E06C75">    name</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "Git Bash"</span></span><span class="line"><span style="color:#E06C75">    startingDirectory</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "%USERPROFILE%"</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><h4 id="添加-MSYS2-到-Windows-Terminal">添加 MSYS2 到 Windows Terminal</h4><p>如果后面你想使用 MSYS2，可以添加到 Windows Terminal 中。</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><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># UCRT64</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.list</span><span style="color:#56B6C2"> +=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">    commandline</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\msys2_shell.cmd -defterm -here -no-start -ucrt64"</span></span><span class="line"><span style="color:#E06C75">    guid</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;17da3cac-b318-431e-8a3e-7fcdefe6d114&#125;"</span></span><span class="line"><span style="color:#E06C75">    icon</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\ucrt64.ico"</span></span><span class="line"><span style="color:#E06C75">    name</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "UCRT64 / MSYS2"</span></span><span class="line"><span style="color:#E06C75">    startingDirectory</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\home\%USERNAME%"</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># MSYS</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.list</span><span style="color:#56B6C2"> +=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">    commandline</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\msys2_shell.cmd -defterm -here -no-start -msys"</span></span><span class="line"><span style="color:#E06C75">    guid</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;71160544-14d8-4194-af25-d05feeac7233&#125;"</span></span><span class="line"><span style="color:#E06C75">    icon</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\msys2.ico"</span></span><span class="line"><span style="color:#E06C75">    name</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "MSYS / MSYS2"</span></span><span class="line"><span style="color:#E06C75">    startingDirectory</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\home\%USERNAME%"</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># MinGW64</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.list</span><span style="color:#56B6C2"> +=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">    commandline</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\msys2_shell.cmd -defterm -here -no-start -mingw64"</span></span><span class="line"><span style="color:#E06C75">    guid</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;f0fc850d-4a2a-4bac-bf9c-96958e1d6246&#125;"</span></span><span class="line"><span style="color:#E06C75">    icon</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\mingw64.ico"</span></span><span class="line"><span style="color:#E06C75">    name</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "MinGW64 / MSYS2"</span></span><span class="line"><span style="color:#E06C75">    startingDirectory</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\home\%USERNAME%"</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Clang64</span></span><span class="line"><span style="color:#E06C75">$wtSettings.profiles.list</span><span style="color:#56B6C2"> +=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span></span><span class="line"><span style="color:#E06C75">    commandline</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\msys2_shell.cmd -defterm -here -no-start -clang64"</span></span><span class="line"><span style="color:#E06C75">    guid</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "&#123;0b71a086-0652-4e48-b9d4-d4b674eab96a&#125;"</span></span><span class="line"><span style="color:#E06C75">    icon</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\clang64.ico"</span></span><span class="line"><span style="color:#E06C75">    name</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "Clang64 / MSYS2"</span></span><span class="line"><span style="color:#E06C75">    startingDirectory</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\home\%USERNAME%"</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 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:#E06C75">$wtSettings</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">ConvertTo-Json</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Depth </span><span style="color:#D19A66">100</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#98C379">  "</span><span style="color:#E06C75">$wtSettingsPath</span><span style="color:#98C379">\settings.json"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Encoding UTF8</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Shell">配置 Shell</h2><h3 id="PowerShell">PowerShell</h3><p>加了美化和插件会拖慢 PowerShell 速度，如果您的设备性能较差或无法在 1s 内启动 PowerShell，建议删除美化。</p><h4 id="安装自动补全-命令预测">安装自动补全 / 命令预测</h4><p>由于 PSReadLine 新版 PowerShell 已经自带（虽然版本号稍有落后），因此我们只需要再安装一下对应的补全即可。</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><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"># https://github.com/abgox/PSCompletions</span></span><span class="line"><span style="color:#56B6C2">Install-Module</span><span style="color:#ABB2BF"> PSCompletions </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Repository PSGallery </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span><span class="line"><span style="color:#98C379">"# PSCompletions</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Import-Module PSCompletions</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Completion Predictor</span></span><span class="line"><span style="color:#56B6C2">Install-Module</span><span style="color:#ABB2BF"> CompletionPredictor </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Repository PSGallery </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># Completion Predictor</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Import-Module CompletionPredictor</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Set-PSReadLineOption -PredictionSource HistoryAndPlugin</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Scoop Completion</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">scoop</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">completion</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># Scoop Completion</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Import-Module </span><span style="color:#56B6C2">`"</span><span style="color:#C678DD">$($</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Item</span><span style="color:#C678DD"> $</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Command</span><span style="color:#ABB2BF"> scoop.ps1).Path).Directory.Parent.FullName</span><span style="color:#C678DD">)</span><span style="color:#98C379">\modules\scoop-completion</span><span style="color:#56B6C2">`"`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Git Completion and Tip</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">posh</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">git</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># Git Completion and Tip</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Import-Module </span><span style="color:#56B6C2">`"</span><span style="color:#C678DD">$($</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Item</span><span style="color:#C678DD"> $</span><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Command</span><span style="color:#ABB2BF"> scoop.ps1).Path).Directory.Parent.FullName</span><span style="color:#C678DD">)</span><span style="color:#98C379">\modules\posh-git</span><span style="color:#56B6C2">`"`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># https://github.com/sigoden/argc-completions</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">argc</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># argc-completions</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete</span><span style="color:#56B6C2">`n`$</span><span style="color:#98C379">argc_scripts = Get-ChildItem -File -Path (</span><span style="color:#56B6C2">`$</span><span style="color:#98C379">env:ARGC_COMPLETIONS_PATH -split ';') | ForEach-Object &#123; </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">_.BaseName &#125;</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># argc --argc-completions powershell </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">argc_scripts | Out-String | Invoke-Expression</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n`$</span><span style="color:#98C379">PSCompletions.argc_completions(</span><span style="color:#56B6C2">`$</span><span style="color:#98C379">argc_scripts)</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span></code></pre></td></tr></tbody></table></figure><p><strong>用法：<strong>按 <code>Tab</code> 键弹出</strong>命令补全窗口</strong>，按 <code>F2</code> 键切换为<strong>竖屏预测</strong>模式。</p><p>对于 <a href="https://github.com/abgox/PSCompletions">PSCompletions</a>，可以执行以下命令安装所有补全规则，<strong>支持提示中文</strong>：</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">psc add </span><span style="color:#56B6C2">*</span></span></code></pre></td></tr></tbody></table></figure><p>同时引入了 <a href="https://github.com/sigoden/argc-completions">argc-completions</a>，可以和 <a href="https://github.com/abgox/PSCompletions">PSCompletions</a> 一起工作，覆盖的工具足够多，基本能够满足大部分使用。</p><h4 id="其他">其他</h4><p>你可以添加其他的配置来<a href="https://learn.microsoft.com/zh-cn/powershell/scripting/learn/shell/creating-profiles">自定义 shell 环境</a>。</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">code $PROFILE</span></span></code></pre></td></tr></tbody></table></figure><h5 id="别名">别名</h5><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Customized alias</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name ping </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Test-Connection</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name nslookup </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Resolve-DnsName</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name ifconfig </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Get-NetIPConfiguration</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name netstat </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Get-NetTCPConnection</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name zip </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Compress-Archive</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name unzip </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Expand-Archive</span></span><span class="line"><span style="color:#56B6C2">Set-Alias</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Name which </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Value </span><span style="color:#56B6C2">Get-Command</span></span><span class="line"></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> ..</span><span style="color:#ABB2BF"> &#123; cd .. &#125;</span></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> traceroute</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#56B6C2">Test-Connection</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">ComputerName $args[</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">] </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Traceroute &#125;</span></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> wget</span><span style="color:#ABB2BF"> &#123; aria2c </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">c </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">R </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">retry</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">wait</span><span style="color:#56B6C2">=</span><span style="color:#D19A66">5</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">x16 </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">s16 </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">j16 </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">k1M $args &#125;</span></span></code></pre></td></tr></tbody></table></figure><h5 id="自动补全">自动补全</h5><p>如果您的工具比较冷门，但支持<code>completion powershell</code> 这个命令规范，可用以下方法增加补全，但存在安全和性能问题问题：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">@</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"rclone"</span><span style="color:#ABB2BF">) | </span><span style="color:#56B6C2">ForEach-Object</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#56B6C2">    Invoke-Expression</span><span style="color:#98C379"> "$_ completion powershell | Out-String | Invoke-Expression"</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span></code></pre></td></tr></tbody></table></figure><h4 id="美化：Oh-My-Posh">美化：Oh My Posh</h4><p><a href="https://ohmyposh.dev/">Oh My Posh</a> 是一个适用于任何 shell 的提示主题引擎。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Install oh-my-posh</span></span><span class="line"><span style="color:#ABB2BF">scoop install oh</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">my</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">posh</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add to profile</span></span><span class="line"><span style="color:#98C379">"# Enable oh-my-posh</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">oh-my-posh init pwsh --config '</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix oh</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">my</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">posh</span><span style="color:#C678DD">)</span><span style="color:#98C379">\themes\powerlevel10k_modern.omp.json' | Invoke-Expression</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add to Windows PowerShell profile (Slow)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 'oh-my-posh init pwsh | Invoke-Expression' | Set-Content "$env:USERPROFILE\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"</span></span></code></pre></td></tr></tbody></table></figure><p>可以到<a href="https://ohmyposh.dev/docs/themes">主题文档</a>查看并选择一个合适的主题来替换掉上面的 <code>powerlevel10k_modern.omp.json</code>。</p><h4 id="美化：Starship">美化：Starship</h4><p>如果不喜欢 oh-my-posh，我们可以使用 <a href="https://starship.rs/zh-cn/">Starship</a> 来代替。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install starship</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add to profile</span></span><span class="line"><span style="color:#98C379">"</span><span style="color:#56B6C2">`n</span><span style="color:#98C379"># Enable starship</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">Invoke-Expression (&#x26;starship init powershell)</span><span style="color:#56B6C2">`n</span><span style="color:#98C379">"</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Out-File</span><span style="color:#ABB2BF"> $PROFILE </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Append</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Export preset</span></span><span class="line"><span style="color:#ABB2BF">starship preset plain</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">text</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">symbols > ~\.config\starship.toml</span></span></code></pre></td></tr></tbody></table></figure><h3 id="CMD">CMD</h3><p>CMD 是 Windows 传统的命令行，功能简陋。</p><h4 id="BusyBox">BusyBox</h4><p>为了拓展 CMD 的命令，我们可以安装 <a href="https://busybox.net/">BusyBox</a>。</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">scoop install busybox</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成你就可以使用 <code>ls</code>、<code>cat</code>、<code>grep</code>、<code>dd</code> 等一些 Linux 命令了，虽然是精简版的，但有总比没有强。</p><p>注意安装 BusyBox 会覆盖掉一些已经安装的 shims，所以尽量提前安装。尤其是 Git Bash，如果被覆盖了可以通过以下命令恢复：</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">scoop reset git</span></span></code></pre></td></tr></tbody></table></figure><h4 id="Clink">Clink</h4><p>我们可以安装 <a href="https://chrisant996.github.io/clink/clink.html">Clink</a> 来优化 CMD 的体验。个人感觉优化下来比 PowerShell 好用。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install clink </span><span style="color:#E06C75">clink</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">completions</span></span><span class="line"><span style="color:#ABB2BF">clink autorun install</span></span><span class="line"><span style="color:#ABB2BF">@rem Disable auto </span><span style="color:#E06C75">update</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">use</span><span style="color:#E06C75"> scoop</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">clink</span><span style="color:#C678DD"> set </span><span style="color:#ABB2BF">clink.autoupdate off</span></span></code></pre></td></tr></tbody></table></figure><p>现在启动 CMD 就可以看到 Clink 的提示信息了。</p><p>如果想要配置一下外观，可以执行：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">@rem Make the startup message shorter</span></span><span class="line"><span style="color:#ABB2BF">clink</span><span style="color:#C678DD"> set </span><span style="color:#ABB2BF">clink.logo short</span></span><span class="line"><span style="color:#ABB2BF">@rem Enable flex </span><span style="color:#E06C75">prompt</span><span style="color:#ABB2BF"> (not necessary </span><span style="color:#C678DD">if</span><span style="color:#ABB2BF"> you want </span><span style="color:#C678DD">to</span><span style="color:#ABB2BF"> use </span><span style="color:#E06C75">oh</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">my</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">posh</span><span style="color:#ABB2BF"> /</span><span style="color:#E06C75"> starship</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">scoop install </span><span style="color:#E06C75">clink</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">flex</span><span style="color:#ABB2BF">-</span><span style="color:#E06C75">prompt</span></span><span class="line"><span style="color:#ABB2BF">flexprompt configure</span></span></code></pre></td></tr></tbody></table></figure><p>根据提示完成向导即可。</p><h3 id="Bash">Bash</h3><p>由于 Git Bash 自带 Bash 配置，且有性能问题，一般情况下无需优化。</p><h4 id="X-CMD">X CMD</h4><p>可以安装 <a href="https://cn.x-cmd.com/">X CMD</a>，增强 Bash 的体验。</p><p>打开 Git Bash 执行以下命令即可。</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">eval</span><span style="color:#98C379"> "$(</span><span style="color:#61AFEF">curl</span><span style="color:#98C379"> https://get.x-cmd.com)"</span></span></code></pre></td></tr></tbody></table></figure><p>目前已经<a href="https://cn.x-cmd.com/start/powershell">支持 PowerShell</a>，可以使用以下命令自动配置：</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">x</span><span style="color:#98C379"> pwsh</span><span style="color:#D19A66"> --setup</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Visual-Studio-Code">安装 Visual Studio Code</h2><p>Visual Studio Code 是微软出品的一款强大的编辑器，通过配置插件可以当作集成开发环境使用。前任产品为 GitHub Atom，都是采用了 Electron 技术实现跨平台。</p><p>注意 Visual Studio（VS）和 Visual Studio Code（VSC）的区别，前者是 IDE（类似于 macOS 上的 Xcode），后者是编辑器。如果你不搞 Windows C/C++、C# 开发，只需安装后者（VSC）。</p><p>建议从官网安装，以便获得更好的自动更新体验。</p><p>官网：<a href="https://code.visualstudio.com/">https://code.visualstudio.com/</a></p><p>也可以使用 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><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">scoop install vscode</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 执行关联</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix vscode</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-context.reg"</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix vscode</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-associations.reg"</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix vscode</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-github-integration.reg"</span></span></code></pre></td></tr></tbody></table></figure><p>最好到设置里面关闭自动更新：</p><figure class="highlight json"><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:#98C379">"update.enableWindowsBackgroundUpdates"</span><span style="color:#ABB2BF">: </span><span style="color:#D19A66">false</span></span><span class="line"><span style="color:#98C379">"update.mode"</span><span style="color:#ABB2BF">: </span><span style="color:#98C379">"none"</span></span></code></pre></td></tr></tbody></table></figure><p>你可能需要自行安装一下中文语言包和亿些插件。</p><h2 id="安装-NeoVim">安装 NeoVim</h2><p>Neovim 是一款现代、快速且功能丰富的编辑器，与 Vim 完全兼容。它支持插件、GUI、LSP、Lua 等。</p><p>官网：<a href="https://neovim.io/">https://neovim.io/</a></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 neovim</span></span></code></pre></td></tr></tbody></table></figure><p>配置文件位于 <code>%LOCALAPPDATA%</code>。</p><p>一般使用 Windows Terminal 即可满足编辑文件的需求，如果想要获得更好的炫酷的体验，可以安装 GUI，如 <a href="https://github.com/neovide/neovide">neovide</a>：</p><p>文档：<a href="https://neovide.dev/">https://neovide.dev/</a></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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">neovide</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 执行关联</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix neovide</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-context.reg"</span></span></code></pre></td></tr></tbody></table></figure><p>默认情况下 Neovim 和 Vim 差不多，只是毛胚房，需要我们安装一些插件才能正常用于开发。折腾插件需要耗费大量精力，这里以 <a href="https://www.lazyvim.org/">LazyVim</a> 这个懒人包为例：</p><ul><li><p>备份当前的 Neovim 文件：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># required</span></span><span class="line"><span style="color:#56B6C2">Move-Item</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim"</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim.bak"</span></span><span class="line"><span style="color:#56B6C2">Remove-Item</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Recurse </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># optional but recommended</span></span><span class="line"><span style="color:#56B6C2">Move-Item</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim-data"</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim-data.bak"</span></span><span class="line"><span style="color:#56B6C2">Remove-Item</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim-data"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Recurse </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>使用 Git 加速克隆 starter 配置，并换源（会报错<code>origin faild</code>，不用管它）：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">git clone </span><span style="color:#98C379">"https://gh.xrgzs.top/https://github.com/LazyVim/starter"</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># replace git mirror in lua script</span></span><span class="line"><span style="color:#ABB2BF">(</span><span style="color:#56B6C2">Get-Content</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim\lua\config\lazy.lua"</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">-replace</span><span style="color:#98C379"> '(https://github.com)'</span><span style="color:#ABB2BF">,</span><span style="color:#98C379">'https://gh.xrgzs.top/$1'</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim\lua\config\lazy.lua"</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>删除 <code>.git</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:#56B6C2">Remove-Item</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:LOCALAPPDATA</span><span style="color:#98C379">\nvim\.git"</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Recurse </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Force</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>开始使用 Neovim，首次打开会卡一段时间，为正常现象。如果为卡死红屏报错，请从官方仓库安装。</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">nvim</span></span></code></pre></td></tr></tbody></table></figure><p>请参阅文件中有关如何自定义 LazyVim 的注释。建议安装后运行 <code>:LazyHealth</code>。这将加载所有插件并检查环境是否正常。</p></li></ul><h2 id="安装-JetBrains-全家桶">安装 JetBrains 全家桶</h2><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:#7F848E;font-style:italic"># scoop install extras/idea</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">idea</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">ultimate</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># scoop install extras/pycharm</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">pycharm</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">professional</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">phpstorm</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">goland</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">rider</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">clion</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">rustrover</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">webstorm</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">rubymine</span></span><span class="line"><span style="color:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">datagrip</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># You know</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">jetbra</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># irm ckey.run|iex</span></span></code></pre></td></tr></tbody></table></figure><h2 id="安装-Typora">安装 Typora</h2><p>Markdown 编辑器。</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 sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">typora</span></span></code></pre></td></tr></tbody></table></figure><p>此版本安装后会自动配置好关联。</p><h2 id="配置-C-C-开发环境">配置 C/C++ 开发环境</h2><p>这里不使用 Visual Studio（MSVC），而是配置 unix-like 的开发环境，原理是在 Windows 上提供一个 POSIX 环境，如 Cygwin、MSYS2 等。</p><p>最好先安装/更新一下 VC 运行库：</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 install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">vcredist2022</span></span><span class="line"><span style="color:#ABB2BF">scoop uninstall vcredist2022</span></span></code></pre></td></tr></tbody></table></figure><h3 id="方法一：Winlibs">方法一：<a href="https://winlibs.com/">Winlibs</a></h3><p>与下一种方案不同的是，这种方法更加简单，直接使用 Scoop 管理开发工具链，不需要安装 MSYS2。缺点是丢失了包管理器，不过对于初学者来说足够了。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install mingw</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">winlibs</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test GCC</span></span><span class="line"><span style="color:#ABB2BF">gcc </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#ABB2BF">cmake </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><p>现在 GCC 工具链已经安装好了，可以在 VSCode 等工具中使用了。</p><p>如需 LLVM 工具链，可以将上面的安装命令中的包名改为：<code>versions/mingw-winlibs-llvm-ucrt</code></p><h3 id="方法二：MSYS2-（建议）">方法二：<a href="https://www.msys2.org/">MSYS2</a> （建议）</h3><p>MSYS2 是一个适用于 Windows 的软件分发和构建平台，由 <code>mintty</code>、<code>bash</code> 命令行终端，<code>git</code>、<code>subversion</code> 等版本控制系统，<code>tar</code>、<code>awk</code> 等工具，<code>autotools</code> 等构建系统组成。可以理解为一个的“子系统”，让 Windows 也能享受 GNU 和开源工具，提供类似于 WSL 的功能。</p><p>我们可以在 MSYS2 里面安装 GCC、LLVM 等一些 C/C++ 开发工具链，以及一些使用对应工具链编译出来的二进制文件（不用你手动折腾编译），且可以通过 <code>pacman</code> （Arch Linux 也在用）这个包管理器进行方便的安装和管理。</p><p>MSYS2 的 unixy 工具直接基于 Cygwin，对原生 Windows API 支持更好。这里不直接使用 Cygwin 因为其提供的包比较少，且安装使用起来不太友好。</p><p>我们平时使用的 Git for Windows 就使用到了 MSYS2 的 MINGW64 环境。虽然 Git for Windows 提供了 MSYS2 的 MINGW64 环境，但 Git Bash 默认包含 Windows 的用户环境（不干净），默认家目录为 Windows 的用户目录，且没有内置 <code>pacman</code> 包管理器，仅用于跑 git。而 MSYS2 更倾向于提供“子系统”，设计上会有些不同，不会继承 Windows 的用户环境，家目录也与 Windows 的用户目录不同，不适合作为日常终端环境使用。</p><p>MSYS2 提供了 MSYS、UCRT64、CLANG64、MINGW64 等<a href="https://www.msys2.org/docs/environments">环境</a>。关于这些环境的区别可以点击链接查看文档中的表格。这里使用 MSYS2 的 UCRT64 环境，与 MSVC 兼容性更好，且使用 gcc 工具链方便使用。如果你需要使用 llvm 工具链，可以使用 MSYS2 的 CLANG64 环境。</p><p>这里我们使用 scoop 安装 MSYS2 会将其安装到用户目录下，通常情况下我们会将 MSYS2 安装到 <code>C:\msys64</code> 目录下方便使用，需要注意一下可能和你看的教程不同。通过以下命令安装会自动配置好国内加速镜像，并安装 GCC 工具链和 CMake：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install dorado</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Add UCRT64 to User's PATH permanently</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#C678DD">$</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\ucrt64\bin;"</span><span style="color:#56B6C2"> +</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)), </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Refresh Current PATH</span></span><span class="line"><span style="color:#E06C75">$Env:Path</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">'Path'</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'Machine'</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">+</span><span style="color:#98C379"> ';'</span><span style="color:#56B6C2"> +</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">'Path'</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'User'</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Install mingw-w64 GCC toolchain and CMake</span></span><span class="line"><span style="color:#ABB2BF">ucrt64 </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">c </span><span style="color:#98C379">"pacman --noconfirm -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test GCC</span></span><span class="line"><span style="color:#ABB2BF">gcc </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#ABB2BF">cmake </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><p>此时 GCC 环境已经配置好，可以在 VSCode 等工具中使用了。</p><p>由于 MSYS2 能自己通过 <code>pacman -Syu</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 hold msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span></span></code></pre></td></tr></tbody></table></figure><h4 id="（可选）让-MSYS2-能够访问-Windows-的环境">（可选）让 MSYS2 能够访问 Windows 的环境</h4><p>默认情况下 MSYS2 无法访问 Windows 的环境，需要加上参数 <code>-use-full-path</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">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"MSYS2_PATH_TYPE"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"inherit"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><p>设置完成后，家目录仍不互通。</p><h4 id="（可选）让-Windows-能够访问-MSYS2-的环境">（可选）让 Windows 能够访问 MSYS2 的环境</h4><p>默认情况下 Windows 无法调用 MSYS2 中安装的工具，可以将它添加到 PATH：</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">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#C678DD">$</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">+</span><span style="color:#98C379"> ";</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix msys2</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">cn</span><span style="color:#C678DD">)</span><span style="color:#98C379">\usr\bin"</span><span style="color:#ABB2BF">), </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><p>危险：这种行为可能会造成系统不稳定，这里将其添加到最后尽量避免出现问题，家目录也不互通。一般加 UCRT64 解决编译问题就够了。</p><h2 id="配置-Python-开发环境">配置 Python 开发环境</h2><p>这里我们使用 scoop 安装最新版 Python（MSVC），并配置好国内镜像。</p><h3 id="安装最新版-Python">安装最新版 Python</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install python</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Allow applications and third-party installers to find python</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix python</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-pep-514.reg"</span></span><span class="line"></span></code></pre></td></tr></tbody></table></figure><h4 id="更换-Pypi-镜像源">更换 Pypi 镜像源</h4><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:#7F848E;font-style:italic"># Use TUNA's mirror</span></span><span class="line"><span style="color:#ABB2BF">pip config set global.index</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">url https:</span><span style="color:#56B6C2">//</span><span style="color:#ABB2BF">mirrors.tuna.tsinghua.edu.cn</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">pypi</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">web</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">simple</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 chsrc：</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 install chsrc</span></span><span class="line"><span style="color:#ABB2BF">chsrc set python</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装不同版本的-Python">安装不同版本的 Python</h3><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 install python38</span></span><span class="line"><span style="color:#ABB2BF">reg import </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix python38</span><span style="color:#C678DD">)</span><span style="color:#98C379">\install-pep-514.reg"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="切换-Python-版本">切换 Python 版本</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop reset python</span></span><span class="line"><span style="color:#ABB2BF">python </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Output: Python 3.13.1</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">scoop reset python38</span></span><span class="line"><span style="color:#ABB2BF">python </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Output: Python 3.8.10</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-uv">安装 uv</h3><p>uv 是一款全新的 Python 包和项目管理器，使用 Rust 开发，速度飞快。相当于 Python 的 NPM。</p><p>文档：<a href="https://docs.astral.sh/uv/">https://docs.astral.sh/uv/</a></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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install uv</span></span><span class="line"><span style="color:#ABB2BF">mkdir </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:APPDATA</span><span style="color:#98C379">\uv"</span></span><span class="line"><span style="color:#98C379">'[[index]]</span></span><span class="line"><span style="color:#98C379">url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"</span></span><span class="line"><span style="color:#98C379">default = true'</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:APPDATA</span><span style="color:#98C379">\uv\uv.toml"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Miniconda">安装 Miniconda</h3><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><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:#ABB2BF">scoop install extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">miniconda3</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Use TUNA's mirror</span></span><span class="line"><span style="color:#98C379">'channels:</span></span><span class="line"><span style="color:#98C379">  - defaults</span></span><span class="line"><span style="color:#98C379">show_channel_urls: True</span></span><span class="line"><span style="color:#98C379">envs_dirs:</span></span><span class="line"><span style="color:#98C379">  - ~/.conda/envs</span></span><span class="line"><span style="color:#98C379">pkgs_dirs:</span></span><span class="line"><span style="color:#98C379">  - ~/.conda/pkgs</span></span><span class="line"><span style="color:#98C379">default_channels:</span></span><span class="line"><span style="color:#98C379">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main</span></span><span class="line"><span style="color:#98C379">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r</span></span><span class="line"><span style="color:#98C379">  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2</span></span><span class="line"><span style="color:#98C379">custom_channels:</span></span><span class="line"><span style="color:#98C379">  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><span class="line"><span style="color:#98C379">  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><span class="line"><span style="color:#98C379">  auto: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud</span></span><span class="line"><span style="color:#98C379">'</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:USERPROFILE</span><span style="color:#98C379">\.condarc"</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Node-js-开发环境">配置 Node.js 开发环境</h2><p>这里使用 Scoop 管理 Node.js 版本，一般推荐安装 <code>nvm-windows</code> 管理多版本的 Node.js。</p><h3 id="安装最新版">安装最新版</h3><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 nodejs</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装长期支持版本">安装长期支持版本</h3><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 nodejs</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">lts</span></span></code></pre></td></tr></tbody></table></figure><h3 id="npm-换源">npm 换源</h3><p>使用 npm：</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">npm config set registry https:</span><span style="color:#56B6C2">//registry.npmmirror.com</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 chsrc：</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 install chsrc</span></span><span class="line"><span style="color:#ABB2BF">chsrc set node</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-pnpm">安装 pnpm</h3><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 pnpm</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 npm：</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">npm install </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">g pnpm</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># corepack enable pnpm</span></span></code></pre></td></tr></tbody></table></figure><h3 id="修复-Windows-上-npm-run-默认使用-CMD">修复 Windows 上 <code>npm run</code> 默认使用 CMD</h3><p>一些包会在 <code>package.json</code> 里面写 POSIX Shell，需要更换为 Bash (Git Bash) 以解决这个问题。</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">npm config script</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">shell bash</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-MySQL-开发环境">配置 MySQL 开发环境</h2><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install mysql mysql</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">shell</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Start MySQL</span></span><span class="line"><span style="color:#ABB2BF">mysqld </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">console</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Launch MySQL Shell</span></span><span class="line"><span style="color:#ABB2BF">mysqlsh</span></span><span class="line"><span style="color:#ABB2BF">\connect root</span><span style="color:#E06C75">@127.0.0.1</span><span style="color:#ABB2BF">:</span><span style="color:#D19A66">3306</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Register MySQL as a service</span></span><span class="line"><span style="color:#ABB2BF">sudo mysqld </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">install MySQL </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">defaults</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">file</span><span style="color:#56B6C2">=</span><span style="color:#98C379">"C:\Users\Admin\scoop\apps\mysql\current\my.ini"</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Stop MySQL</span></span><span class="line"><span style="color:#ABB2BF">sc stop MySQL</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Delete MySQL</span></span><span class="line"><span style="color:#ABB2BF">sc delete MySQL</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-PostgreSQL-开发环境">配置 PostgreSQL 开发环境</h2><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 postgresql</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Java-开发环境">配置 Java 开发环境</h2><h3 id="安装-OpenJDK">安装 OpenJDK</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install java</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">openjdk</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># scoop install java/openjdk17</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Java</span></span><span class="line"><span style="color:#ABB2BF">java </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Maven">安装 Maven</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install maven</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Set M2_HOME</span></span><span class="line"><span style="color:#E06C75">$Env:M2_HOME</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix maven</span><span style="color:#C678DD">)</span><span style="color:#98C379">"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"M2_HOME"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Env:M2_HOME</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><h4 id="Maven-换源">Maven 换源</h4><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><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:#7F848E;font-style:italic"># Use NJU mirror</span></span><span class="line"><span style="color:#98C379">'&#x3C;?xml version="1.0" encoding="UTF-8"?></span></span><span class="line"><span style="color:#98C379">&#x3C;settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"</span></span><span class="line"><span style="color:#98C379">          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"</span></span><span class="line"><span style="color:#98C379">          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd"></span></span><span class="line"><span style="color:#98C379">    &#x3C;mirrors></span></span><span class="line"><span style="color:#98C379">       &#x3C;mirror></span></span><span class="line"><span style="color:#98C379">            &#x3C;id>nju_mirror&#x3C;/id></span></span><span class="line"><span style="color:#98C379">            &#x3C;mirrorOf>*&#x3C;/mirrorOf></span></span><span class="line"><span style="color:#98C379">            &#x3C;url>https://repo.nju.edu.cn/maven/&#x3C;/url></span></span><span class="line"><span style="color:#98C379">        &#x3C;/mirror></span></span><span class="line"><span style="color:#98C379">    &#x3C;/mirrors></span></span><span class="line"><span style="color:#98C379">&#x3C;/settings></span></span><span class="line"><span style="color:#98C379">'</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"</span><span style="color:#E06C75">$Env:M2_HOME</span><span style="color:#98C379">\conf\settings.xml"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Maven</span></span><span class="line"><span style="color:#ABB2BF">mvn </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#ABB2BF">mvn help:effective</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">settings</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Gradle">安装 Gradle</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install gradle</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Set GRADLE_HOME</span></span><span class="line"><span style="color:#E06C75">$Env:GRADLE_HOME</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix gradle</span><span style="color:#C678DD">)</span><span style="color:#98C379">"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"GRADLE_HOME"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Env:GRADLE_HOME</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><h4 id="Gradle-换源">Gradle 换源</h4><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><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Use NJU mirror</span></span><span class="line"><span style="color:#98C379">@"</span></span><span class="line"><span style="color:#98C379">allprojects &#123;</span></span><span class="line"><span style="color:#98C379">    buildscript &#123;</span></span><span class="line"><span style="color:#98C379">        repositories &#123;</span></span><span class="line"><span style="color:#98C379">            def NJU_REPOSITORY_URL = 'https://repo.nju.edu.cn/maven/'</span></span><span class="line"><span style="color:#98C379">            all &#123; ArtifactRepository repo -></span></span><span class="line"><span style="color:#98C379">                if (repo instanceof MavenArtifactRepository) &#123;</span></span><span class="line"><span style="color:#98C379">                    def url = repo.url.toString()</span></span><span class="line"><span style="color:#98C379">                    if (url.startsWith('https://repo1.maven.org/maven2')) &#123;</span></span><span class="line"><span style="color:#98C379">                        project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; replaced by </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">NJU_REPOSITORY_URL."</span></span><span class="line"><span style="color:#98C379">                        remove repo</span></span><span class="line"><span style="color:#98C379">                    &#125;</span></span><span class="line"><span style="color:#98C379">                    if (url.startsWith('https://jcenter.bintray.com/')) &#123;</span></span><span class="line"><span style="color:#98C379">                        project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                        remove repo</span></span><span class="line"><span style="color:#98C379">                    &#125;</span></span><span class="line"><span style="color:#98C379">                    if (url.startsWith('https://dl.google.com/dl/android/maven2/')) &#123;</span></span><span class="line"><span style="color:#98C379">                        project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                        remove repo</span></span><span class="line"><span style="color:#98C379">                    &#125;</span></span><span class="line"><span style="color:#98C379">                    if (url.contains('plugins.gradle.org/m2')) &#123;</span></span><span class="line"><span style="color:#98C379">                        project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                        remove repo</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:#98C379">            maven &#123; url NJU_REPOSITORY_URL &#125;</span></span><span class="line"><span style="color:#98C379">            mavenLocal()</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><span class="line"><span style="color:#98C379">    repositories &#123;</span></span><span class="line"><span style="color:#98C379">        def NJU_REPOSITORY_URL = 'https://repo.nju.edu.cn/maven/'</span></span><span class="line"><span style="color:#98C379">        all &#123; ArtifactRepository repo -></span></span><span class="line"><span style="color:#98C379">            if (repo instanceof MavenArtifactRepository) &#123;</span></span><span class="line"><span style="color:#98C379">                def url = repo.url.toString()</span></span><span class="line"><span style="color:#98C379">                if (url.startsWith('https://repo1.maven.org/maven2')) &#123;</span></span><span class="line"><span style="color:#98C379">                    project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; replaced by </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">NJU_REPOSITORY_URL."</span></span><span class="line"><span style="color:#98C379">                    remove repo</span></span><span class="line"><span style="color:#98C379">                &#125;</span></span><span class="line"><span style="color:#98C379">                if (url.startsWith('https://jcenter.bintray.com/')) &#123;</span></span><span class="line"><span style="color:#98C379">                    project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                    remove repo</span></span><span class="line"><span style="color:#98C379">                &#125;</span></span><span class="line"><span style="color:#98C379">                if (url.startsWith('https://dl.google.com/dl/android/maven2/')) &#123;</span></span><span class="line"><span style="color:#98C379">                    project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                    remove repo</span></span><span class="line"><span style="color:#98C379">                &#125;</span></span><span class="line"><span style="color:#98C379">                if (url.contains('plugins.gradle.org/m2')) &#123;</span></span><span class="line"><span style="color:#98C379">                    project.logger.lifecycle "Repository </span><span style="color:#56B6C2">`$</span><span style="color:#98C379">&#123;repo.url&#125; deleted."</span></span><span class="line"><span style="color:#98C379">                    remove repo</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:#98C379">        maven &#123; url NJU_REPOSITORY_URL &#125;</span></span><span class="line"><span style="color:#98C379">        mavenLocal()</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">"@</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix gradle</span><span style="color:#C678DD">)</span><span style="color:#98C379">\init.d\init.gradle"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Gradle</span></span><span class="line"><span style="color:#ABB2BF">gradle </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Tomcat">安装 Tomcat</h3><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 install tomcat</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># CATALINA_HOME and CATALINA_BASE has been set.</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Android-开发环境">配置 Android 开发环境</h2><p>安装 Android Studio：</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 extras</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">android</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">studio</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Android-命令行工具（可选）">安装 Android 命令行工具（可选）</h3><p>适合于 C 盘够大或者 Scoop 安装到 D 盘的情况。否则建议使用下一种方式。</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 android</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">clt</span></span></code></pre></td></tr></tbody></table></figure><p>需要注意 Scoop 会自动设置 <code>ANDROID_HOME</code>（Android SDK 目录）环境变量，请在 Android Studio 内确认 File | Settings | Languages &amp; Frameworks | Android SDK -&gt; Android SDK Location 为执行 <code>scoop prefix android-clt</code> 后显示的路径。<strong>配置不当会造成安卓虚拟机无法启动。</strong></p><p>之后 Android SDK 将安装到 Scoop 目录且被 Scoop 持久化。</p><h3 id="更改-Android-SDK-目录">更改 Android SDK 目录</h3><p>默认为 <code>%LocalAppData%\Android\Sdk</code>，由于 Android SDK 体积较大，我们可能需要将其存放到其它分区。</p><p><a href="https://developer.android.com/tools/variables">https://developer.android.com/tools/variables</a></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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Set ANDROID_HOME</span></span><span class="line"><span style="color:#E06C75">$Env:ANDROID_HOME</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "D:\Android\Sdk"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"ANDROID_HOME"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Env:ANDROID_HOME</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">mkdir </span><span style="color:#E06C75">$Env:ANDROID_HOME</span></span></code></pre></td></tr></tbody></table></figure><p>打开 Android Studio 安装 Android SDK。注意 Android Studio 不会自动读取环境变量，需要自行设置安装目录。<strong>安装目录选择错误可能会导致安卓虚拟机无法使用。</strong></p><p>之后使用以下命令添加到 PATH：</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"</span><span style="color:#E06C75">$Env:ANDROID_HOME</span><span style="color:#98C379">\bin;</span><span style="color:#E06C75">$Env:ANDROID_HOME</span><span style="color:#98C379">\tools\bin;</span><span style="color:#E06C75">$Env:ANDROID_HOME</span><span style="color:#98C379">\platform-tools;"</span><span style="color:#56B6C2"> +</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">"PATH"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">), </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Refresh Current PATH</span></span><span class="line"><span style="color:#E06C75">$Env:Path</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">'Path'</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'Machine'</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">+</span><span style="color:#98C379"> ';'</span><span style="color:#56B6C2"> +</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::GetEnvironmentVariable(</span><span style="color:#98C379">'Path'</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'User'</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#56B6C2">adb.exe</span><span style="color:#56B6C2"> --</span><span style="color:#ABB2BF">version</span></span></code></pre></td></tr></tbody></table></figure><h4 id="更改-Android-SDK-用户目录">更改 Android SDK 用户目录</h4><p>默认为 <code>~\.android</code>，此目录可能会存放安卓虚拟机的镜像、ADB 密钥、配置文件，我们可能需要将其存放到其它分区。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Set ANDROID_USER_HOME</span></span><span class="line"><span style="color:#E06C75">$Env:ANDROID_USER_HOME</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "D:\Android\Sdk\.android"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"ANDROID_USER_HOME"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Env:ANDROID_USER_HOME</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">mkdir </span><span style="color:#E06C75">$Env:ANDROID_USER_HOME</span></span></code></pre></td></tr></tbody></table></figure><h4 id="仅更改-Android-AVD-目录">仅更改 Android AVD 目录</h4><p>默认为 <code>$ANDROID_EMULATOR_HOME\avd</code>【默认为 <code>$ANDROID_USER_HOME\avd</code>（默认为 <code>~\.android\avd</code>）】，此目录会存放安卓虚拟机的镜像文件，我们可能需要将其存放到其它分区。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Set ANDROID_EMULATOR_HOME</span></span><span class="line"><span style="color:#E06C75">$Env:ANDROID_EMULATOR_HOME</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "D:\Android\Sdk\.android\avd"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"ANDROID_EMULATOR_HOME"</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Env:ANDROID_EMULATOR_HOME</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">mkdir </span><span style="color:#E06C75">$Env:ANDROID_EMULATOR_HOME</span></span></code></pre></td></tr></tbody></table></figure><h3 id="安装-Android-SDK">安装 Android SDK</h3><p>需要确保能够访问 <a href="https://dl.google.com">https://dl.google.com</a> （浏览器访问出现 <code>404. That’s an error. That’s all we know.</code> 为正常现象，正常情况为国内 IP 可以裸连）</p><p>运行 Android Studio</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">. </span><span style="color:#98C379">"</span><span style="color:#C678DD">$(</span><span style="color:#ABB2BF">scoop prefix android</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">studio</span><span style="color:#C678DD">)</span><span style="color:#98C379">\bin\studio64.exe"</span></span></code></pre></td></tr></tbody></table></figure><p>拒绝收集隐私信息</p><p><img src="scoop-dev-setup/56920f192767434cce86a1fc334e52d1.png" alt=""></p><p>一般无需配置代理</p><p><img src="scoop-dev-setup/8352f9e0cab5cf10b9e092315bffa0bc.png" alt=""></p><p>自定义安装</p><p><img src="scoop-dev-setup/52131b2fb6c3239972487f80eabc2e1b.png" alt=""></p><p>更改 Android SDK 安装路径（建议）</p><p><img src="scoop-dev-setup/f98d46aea6ff96f8959b2683a0fdd2bd.png" alt=""></p><h4 id="汉化">汉化</h4><p>前往 File | Settings | Plugins</p><ol><li><p>下载 IDEA 汉化插件包，手动安装。</p><ul><li><a href="https://plugins.jetbrains.com/plugin/index?xmlId=com.intellij.zh">https://plugins.jetbrains.com/plugin/index?xmlId=com.intellij.zh</a></li></ul><p><img src="scoop-dev-setup/470b63ea63085cb1576f4065f6f4352e.png" alt=""></p></li><li><p>如果无法安装（提示版本号不匹配），到这个 GitHub 仓库上下载修改过的然后手动安装。</p><p><img src="scoop-dev-setup/c8f434c6b27a0ba64462fa7f8cfdb3cf.png" alt=""></p><ul><li><p><a href="https://github.com/sollyu/AndroidStudioChineseLanguagePack">https://github.com/sollyu/AndroidStudioChineseLanguagePack</a></p></li><li><p>注意第三方修改版插件可能会存在安全风险。</p></li></ul></li></ol><p>安装完成后点击 Apply，随后即可到 File | Settings | Appearance &amp; Behavior | System Settings | Language and Region 设置为中文。</p><p><img src="scoop-dev-setup/cbb247f99b7c423d1dd118a722cdbd02.png" alt=""></p><h2 id="配置-Flutter-开发环境">配置 Flutter 开发环境</h2><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 flutter</span></span></code></pre></td></tr></tbody></table></figure><h3 id="Flutter-换源">Flutter 换源</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Use flutter-io.cn</span></span><span class="line"><span style="color:#E06C75">$env:PUB_HOSTED_URL</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> 'https://pub.flutter-io.cn'</span></span><span class="line"><span style="color:#E06C75">$env:FLUTTER_STORAGE_BASE_URL</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> 'https://storage.flutter-io.cn'</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">'PUB_HOSTED_URL'</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$env:PUB_HOSTED_URL</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'User'</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">System.Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">'FLUTTER_STORAGE_BASE_URL'</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$env:FLUTTER_STORAGE_BASE_URL</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'User'</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Flutter</span></span><span class="line"><span style="color:#ABB2BF">flutter doctor </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">v</span></span></code></pre></td></tr></tbody></table></figure><p>可能还需：</p><ul><li><a href="#%E9%85%8D%E7%BD%AE-java-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83">配置 Java 开发环境</a></li><li><a href="#%E9%85%8D%E7%BD%AE-android-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83">配置 Android 开发环境</a></li><li><a href="https://visualstudio.microsoft.com/downloads/">安装 Visual Studio（MSVC）</a></li></ul><h2 id="配置-Go-开发环境">配置 Go 开发环境</h2><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 go</span></span></code></pre></td></tr></tbody></table></figure><h3 id="设置-GOPROXY">设置 GOPROXY</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Use Goproxy.cn</span></span><span class="line"><span style="color:#ABB2BF">go env </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">w GO111MODULE</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">on</span></span><span class="line"><span style="color:#ABB2BF">go env </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">w GOPROXY</span><span style="color:#56B6C2">=</span><span style="color:#ABB2BF">https:</span><span style="color:#56B6C2">//</span><span style="color:#ABB2BF">goproxy.cn,direct</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Go</span></span><span class="line"><span style="color:#ABB2BF">go version</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 chsrc：</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 install chsrc</span></span><span class="line"><span style="color:#ABB2BF">chsrc set go</span></span></code></pre></td></tr></tbody></table></figure><h3 id="配置-CGO">配置 CGO</h3><p>部分项目要求 CGO，<strong>请按照上文<a href="#%E9%85%8D%E7%BD%AE-cc-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83">配置 C/C++ 开发环境</a>。</strong></p><h2 id="配置-C-开发环境">配置 C# 开发环境</h2><p>建议安装 Visual Studio，当然也可以继续用 Visual Studio Code。</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 install dotnet</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">sdk</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># scoop install dotnet-sdk-lts</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Rust-开发环境">配置 Rust 开发环境</h2><p>由于不安装 Visual Studio（MSVC），这里使用 GCC 版的 Rust。</p><p><strong>请先按照上文<a href="#%E9%85%8D%E7%BD%AE-cc-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83">配置 C/C++ 开发环境</a>。</strong></p><h3 id="方法一：使用-Rustup-安装">方法一：使用 Rustup 安装</h3><p>官方建议的方式。但需要注意 Rustup 能自己管理版本，<code>rustup update</code> 会把自己升级，所以请先使用 Scoop 更新 Rustup。</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><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"># Set Rustup mirror</span></span><span class="line"><span style="color:#E06C75">$env:RUSTUP_UPDATE_ROOT</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "https://mirrors.nju.edu.cn/rustup/rustup"</span></span><span class="line"><span style="color:#E06C75">$env:RUSTUP_DIST_SERVER</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "https://mirrors.nju.edu.cn/rustup"</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"RUSTUP_UPDATE_ROOT"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"https://mirrors.nju.edu.cn/rustup/rustup"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">[</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::SetEnvironmentVariable(</span><span style="color:#98C379">"RUSTUP_DIST_SERVER"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"https://mirrors.nju.edu.cn/rustup"</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">"User"</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Install Rustup</span></span><span class="line"><span style="color:#ABB2BF">scoop install rustup</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">gnu</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Test Rust</span></span><span class="line"><span style="color:#ABB2BF">rustc </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#ABB2BF">cargo </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">version</span></span><span class="line"><span style="color:#ABB2BF">cargo new </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">bin rust</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">test</span></span><span class="line"><span style="color:#ABB2BF">cd rust</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">test</span></span><span class="line"><span style="color:#ABB2BF">cargo build</span></span><span class="line"><span style="color:#ABB2BF">cargo run</span></span></code></pre></td></tr></tbody></table></figure><h3 id="方法二：使用-Scoop-安装">方法二：使用 Scoop 安装</h3><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 rust</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">gnu</span></span></code></pre></td></tr></tbody></table></figure><h3 id="方法三：使用-MSYS2-安装">方法三：使用 MSYS2 安装</h3><p><strong>请先按照上文配置 C/C++ 开发 MSYS2 环境。</strong></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">ucrt64 </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">c </span><span style="color:#98C379">"pacman --noconfirm -S mingw-w64-ucrt-x86_64-rust"</span></span></code></pre></td></tr></tbody></table></figure><h3 id="Cargo-换源">Cargo 换源</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">mkdir </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:USERPROFILE</span><span style="color:#98C379">\.cargo"</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># Use USTC mirror</span></span><span class="line"><span style="color:#98C379">@"</span></span><span class="line"><span style="color:#98C379">[source.crates-io]</span></span><span class="line"><span style="color:#98C379">replace-with = 'ustc'</span></span><span class="line"></span><span class="line"><span style="color:#98C379">[source.ustc]</span></span><span class="line"><span style="color:#98C379">registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"</span></span><span class="line"></span><span class="line"><span style="color:#98C379">[registries.ustc]</span></span><span class="line"><span style="color:#98C379">index = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"</span></span><span class="line"><span style="color:#98C379">"@</span><span style="color:#ABB2BF"> | </span><span style="color:#56B6C2">Set-Content</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">Path </span><span style="color:#98C379">"</span><span style="color:#E06C75">$env:USERPROFILE</span><span style="color:#98C379">\.cargo\config.toml"</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 chsrc：</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 install chsrc</span></span><span class="line"><span style="color:#ABB2BF">chsrc set rust</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-Ruby-开发环境">配置 Ruby 开发环境</h2><p><strong>请先按照上文配置 C/C++ 开发 MSYS2 环境。</strong></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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">scoop install ruby</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Install MSYS2 and MINGW development toolchain</span></span><span class="line"><span style="color:#ABB2BF">ridk install </span><span style="color:#D19A66">3</span></span></code></pre></td></tr></tbody></table></figure><h3 id="Rubygems-换源">Rubygems 换源</h3><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Use USTC mirror</span></span><span class="line"><span style="color:#ABB2BF">gem sources</span></span><span class="line"><span style="color:#ABB2BF">gem sources </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">remove https:</span><span style="color:#56B6C2">//</span><span style="color:#ABB2BF">rubygems.org</span><span style="color:#56B6C2">/</span></span><span class="line"><span style="color:#ABB2BF">gem sources </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">a https:</span><span style="color:#56B6C2">//</span><span style="color:#ABB2BF">mirrors.ustc.edu.cn</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">rubygems</span><span style="color:#56B6C2">/</span></span></code></pre></td></tr></tbody></table></figure><p>或者使用 chsrc：</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 install chsrc</span></span><span class="line"><span style="color:#ABB2BF">chsrc set ruby</span></span></code></pre></td></tr></tbody></table></figure><h2 id="配置-WSL2-Docker-开发环境">配置 WSL2 + Docker 开发环境</h2><p>如果您想使用 VMware / VirtualBox 等虚拟机，请不要使用此方案，此方案会启用 Hyper-V，CPU 硬件虚拟化指令被 Hyper-V 独占，会导致第三方虚拟机运行卡顿。</p><p>以下命令需要使用<strong>管理员权限</strong>执行，可以按 Win + X 打开 终端管理员。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># Install WSL2</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">wsl2</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Install Ubuntu (Optional)</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">wsl</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">ubuntu</span></span><span class="line"><span style="color:#ABB2BF">scoop hold wsl</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">ubuntu </span><span style="color:#7F848E;font-style:italic"># prevent from updating</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># Install docker-desktop</span></span><span class="line"><span style="color:#ABB2BF">scoop install sdoog</span><span style="color:#56B6C2">/</span><span style="color:#ABB2BF">docker</span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">desktop</span></span></code></pre></td></tr></tbody></table></figure><p>安装完成后重启电脑即可使用 Docker Desktop 客户端。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;Scoop 是一个 Windows 下基于 PowerShell 编写的的第三方包管理器，能够自动下载、解压、设置环境变量、添加 shim、持久化用户数据。&lt;/p&gt;
&lt;p&gt;本文将使用 Scoop 来安装一些工具，简化开发环境搭建，让开发人员重装系统后可以更快配置环境。&lt;/p</summary>
      
    
    
    
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Windows" scheme="https://www.xrgzs.top/tags/windows/"/>
    
    <category term="PowerShell" scheme="https://www.xrgzs.top/tags/powershell/"/>
    
    <category term="Scoop" scheme="https://www.xrgzs.top/tags/scoop/"/>
    
    <category term="MSYS2" scheme="https://www.xrgzs.top/tags/msys2/"/>
    
    <category term="WSL" scheme="https://www.xrgzs.top/tags/wsl/"/>
    
    <category term="开发" scheme="https://www.xrgzs.top/tags/%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>PowerShell PNG 转 ICO</title>
    <link href="https://www.xrgzs.top/posts/powershell-png-to-ico"/>
    <id>https://www.xrgzs.top/posts/powershell-png-to-ico</id>
    <published>2025-01-06T02:00:00.000Z</published>
    <updated>2025-01-06T02:00:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>使用 PowerShell 将 PNG 文件转换为 ICO 文件有多种方法，但是都不太方便，且不易理解。鉴于我们现在的操作系统版本大多已经在 Windows Vista 以上，无需考虑以前的 BMP 多分辨率多位数模式，可以直接使用 PNG 加上头信息构造出 ICO 文件。这样就可以非常方便的将 PNG 转换为 ICO，同时可以无损保留 PNG。</p><h2 id="结构">结构</h2><p>这里我们不引入任何依赖，手动合成，因此首先需要了解一下 ICO 文件的结构。</p><p>参考:</p><ul><li><p><a href="https://en.wikipedia.org/wiki/ICO_(file_format)#Structure_of_image_directory">https://en.wikipedia.org/wiki/ICO_(file_format)#Structure_of_image_directory</a></p></li><li><p><a href="https://msdn.microsoft.com/en-us/library/ms997538.aspx">https://msdn.microsoft.com/en-us/library/ms997538.aspx</a></p></li></ul><p>简言之，ICO 格式由头文件和图片数据组成，其中头文件的十六进制数据结构如下：</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><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"># ICONDIR 0x00 - 0x06 : 0x06</span></span><span class="line"><span style="color:#D19A66">00</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 保留</span></span><span class="line"><span style="color:#D19A66">01</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 图像类型，ico 为 1</span></span><span class="line"><span style="color:#D19A66">01</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 图像数量，1 张</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># ICONDIRENTRY 如有多张，每张图像来一个 0x06 - 0x16 : 0x10 (16 bytes)</span></span><span class="line"><span style="color:#D19A66">00</span><span style="color:#7F848E;font-style:italic"> # 宽度</span></span><span class="line"><span style="color:#D19A66">00</span><span style="color:#7F848E;font-style:italic"> # 高度</span></span><span class="line"><span style="color:#D19A66">00</span><span style="color:#7F848E;font-style:italic"> # 颜色，大于8bpp为0（无调色板）</span></span><span class="line"><span style="color:#D19A66">00</span><span style="color:#7F848E;font-style:italic"> # 保留</span></span><span class="line"><span style="color:#D19A66">01</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 颜色平面</span></span><span class="line"><span style="color:#D19A66">20</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 每像素位数（32bpp）</span></span><span class="line"><span style="color:#D19A66">6F</span><span style="color:#D19A66"> 48</span><span style="color:#D19A66"> 00</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 图像文件大小（需要修改） 0x0e - 0x12 : 0x04</span></span><span class="line"><span style="color:#D19A66">16</span><span style="color:#D19A66"> 00</span><span style="color:#D19A66"> 00</span><span style="color:#D19A66"> 00</span><span style="color:#7F848E;font-style:italic"> # 图像数据偏移量</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 总共: 22 bytes</span></span></code></pre></td></tr></tbody></table></figure><p>ICO 格式的字节序为 Little-Endian（如上方的 <code>6F 48 00 00</code> 为从右往左读作 <code>48 6F</code>），Windows 下默认表现为 LE，通常编写 PowerShell 时无需在意这个问题，自动生成出来就是 LE 的。</p><p>由于我们只有一张图，且图片为 PNG 格式，因此我们只需要修改 <code>ICONDIRENTRY</code> 中的图像文件大小即可。</p><h2 id="代码">代码</h2><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><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">function</span><span style="color:#61AFEF"> Convert-PngToIco</span><span style="color:#ABB2BF"> &#123;</span></span><span class="line"><span style="color:#C678DD">    param</span><span style="color:#ABB2BF"> (</span></span><span class="line"><span style="color:#ABB2BF">        [</span><span style="color:#C678DD">string</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$PngPath</span><span style="color:#ABB2BF">,</span></span><span class="line"><span style="color:#ABB2BF">        [</span><span style="color:#C678DD">string</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$IcoPath</span></span><span class="line"><span style="color:#ABB2BF">    )</span></span><span class="line"><span style="color:#7F848E;font-style:italic">    # 读取文件</span></span><span class="line"><span style="color:#E06C75">    $png</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.IO.File</span><span style="color:#ABB2BF">]::ReadAllBytes(</span><span style="color:#E06C75">$pngPath</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:#E06C75">    $ico</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.IO.MemoryStream</span><span style="color:#ABB2BF">]::new()</span></span><span class="line"><span style="color:#E06C75">    $bin</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.IO.BinaryWriter</span><span style="color:#ABB2BF">]::new(</span><span style="color:#E06C75">$ico</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">    # 写入ICONDIR结构</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint16</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 保留</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint16</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 图像类型，ico 为 1</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint16</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 图像数量，1 张</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">    # 写入ICONDIRENTRY结构</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">sbyte</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 宽度</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">sbyte</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 高度</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">sbyte</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 颜色</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">sbyte</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 保留</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint16</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 颜色平面</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint16</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">32</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 每像素位数（32bpp）</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint32</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$png.Length</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 图像文件大小</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">uint32</span><span style="color:#ABB2BF">]</span><span style="color:#D19A66">22</span><span style="color:#ABB2BF">) </span><span style="color:#7F848E;font-style:italic"># 图像数据偏移量</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic">    # 写入图像数据</span></span><span class="line"><span style="color:#E06C75">    $bin.Write</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$png</span><span style="color:#ABB2BF">)</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">System.IO.File</span><span style="color:#ABB2BF">]::WriteAllBytes(</span><span style="color:#E06C75">$icoPath</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$ico.ToArray</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:#E06C75">    $bin.Dispose</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#E06C75">    $ico.Dispose</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"># 使用示例</span></span><span class="line"><span style="color:#E06C75">$pngPath</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> ".\image.png"</span></span><span class="line"><span style="color:#E06C75">$icoPath</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> ".\image.ico"</span></span><span class="line"><span style="color:#56B6C2">Convert-PngToIco</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">PngPath </span><span style="color:#E06C75">$pngPath</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">IcoPath </span><span style="color:#E06C75">$icoPath</span></span></code></pre></td></tr></tbody></table></figure><h2 id="技巧">技巧</h2><p>PNG 图片可以先使用 TinyPNG 压缩，这样制作出来的图标体积更小。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;使用 PowerShell 将 PNG 文件转换为 ICO 文件有多种方法，但是都不太方便，且不易理解。鉴于我们现在的操作系统版本大多已经在 Windows Vista 以上，无需考虑以前的 BMP 多分辨率多位数模式，可以直接使用 PNG 加上头信息构造出 ICO 文件。</summary>
      
    
    
    
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Windows" scheme="https://www.xrgzs.top/tags/windows/"/>
    
    <category term="脚本" scheme="https://www.xrgzs.top/tags/%E8%84%9A%E6%9C%AC/"/>
    
    <category term="PowerShell" scheme="https://www.xrgzs.top/tags/powershell/"/>
    
  </entry>
  
  <entry>
    <title>SEEWO SV21 RK3568 刷机折腾</title>
    <link href="https://www.xrgzs.top/posts/rk3568-seewo-sv21"/>
    <id>https://www.xrgzs.top/posts/rk3568-seewo-sv21</id>
    <published>2024-12-27T00:50:00.000Z</published>
    <updated>2026-03-06T00:50:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>SEEWO SV21 RK3568 是希沃录播机的主板，带双网口（其中一个支持POE，另外两个网口为麦克风）、3个HDMI（1xIn，2xOut）、1个USB3.0端口、2个USB2.0端口、1个SATA接口（5V）等，采用12V DC5521 电源输入，2G RAM + 4G ROM，原厂预装系统为 4.19 内核的 Buildroot，目前开源DTS暂不完善。</p><p><img src="rk3568-seewo-sv21/77bfed9f7670c3a0bc711f21ab91be57.webp" alt="正面"></p><p><img src="rk3568-seewo-sv21/4af5799bceac99e1eccad14b0ac1ab2f.webp" alt="反面"></p><h2 id="一些资源">一些资源</h2><ul><li><p>原厂固件TTL波特率：115200</p></li><li><p>其他RK系统TTL波特率：1500000</p></li><li><p>U-Boot：<a href="https://github.com/ophub/u-boot/tree/main/u-boot/rockchip/seewo-sv21">https://github.com/ophub/u-boot/tree/main/u-boot/rockchip/seewo-sv21</a></p></li><li><p>Ophub Armbian 固件：<a href="https://github.com/ophub/amlogic-s9xxx-armbian">https://github.com/ophub/amlogic-s9xxx-armbian</a></p></li><li><p>Ophub OpenWrt 固件：<a href="https://github.com/ophub/amlogic-s9xxx-openwrt">https://github.com/ophub/amlogic-s9xxx-openwrt</a></p></li><li><p>RK33XX/RK35XX系列设备启动流程、写入系统及编译流程：<a href="https://github.com/ophub/amlogic-s9xxx-armbian/discussions/1634">https://github.com/ophub/amlogic-s9xxx-armbian/discussions/1634</a></p></li><li><p>瑞芯微 RK 系列芯片启动流程简析：<a href="https://www.w568w.eu.org/rockchip-boot-process.html">https://www.w568w.eu.org/rockchip-boot-process.html</a></p></li><li><p>SV21 All In Boom：<a href="https://www.bilibili.com/opus/961259216355459077">https://www.bilibili.com/opus/961259216355459077</a></p></li></ul><blockquote><p>目前建议使用卖家提供的 Armian 固件，其他固件要么识别不了双网卡，要么识别不了SATA，要么连关机都管不掉，要么 CPU/GPU/NPU 无法频率有问题。</p></blockquote><p>RockChip 启动流程：<a href="https://opensource.rock-chips.com/wiki_Boot_option">https://opensource.rock-chips.com/wiki_Boot_option</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#ABB2BF">[root@SV21:/]# fdisk -l</span></span><span class="line"><span style="color:#61AFEF">Found</span><span style="color:#98C379"> valid</span><span style="color:#98C379"> GPT</span><span style="color:#98C379"> with</span><span style="color:#98C379"> protective</span><span style="color:#98C379"> MBR</span><span style="color:#ABB2BF">; </span><span style="color:#61AFEF">using</span><span style="color:#98C379"> GPT</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Disk</span><span style="color:#98C379"> /dev/mmcblk0:</span><span style="color:#D19A66"> 7733248</span><span style="color:#98C379"> sectors,</span><span style="color:#98C379"> 3776M</span></span><span class="line"><span style="color:#61AFEF">Logical</span><span style="color:#98C379"> sector</span><span style="color:#98C379"> size:</span><span style="color:#D19A66"> 512</span></span><span class="line"><span style="color:#61AFEF">Disk</span><span style="color:#98C379"> identifier</span><span style="color:#ABB2BF"> (GUID): ac5b0000-0000-4f31-8000-2d0900006325</span></span><span class="line"><span style="color:#61AFEF">Partition</span><span style="color:#98C379"> table</span><span style="color:#98C379"> holds</span><span style="color:#98C379"> up</span><span style="color:#98C379"> to</span><span style="color:#D19A66"> 128</span><span style="color:#98C379"> entries</span></span><span class="line"><span style="color:#61AFEF">First</span><span style="color:#98C379"> usable</span><span style="color:#98C379"> sector</span><span style="color:#98C379"> is</span><span style="color:#98C379"> 34,</span><span style="color:#98C379"> last</span><span style="color:#98C379"> usable</span><span style="color:#98C379"> sector</span><span style="color:#98C379"> is</span><span style="color:#D19A66"> 7733214</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Number</span><span style="color:#98C379">  Start</span><span style="color:#ABB2BF"> (sector)    End (</span><span style="color:#61AFEF">sector</span><span style="color:#ABB2BF">)  Size Name</span></span><span class="line"><span style="color:#61AFEF">     1</span><span style="color:#D19A66">           16384</span><span style="color:#D19A66">           24575</span><span style="color:#98C379"> 4096K</span><span style="color:#98C379"> uboot</span></span><span class="line"><span style="color:#61AFEF">     2</span><span style="color:#D19A66">           24576</span><span style="color:#D19A66">           32767</span><span style="color:#98C379"> 4096K</span><span style="color:#98C379"> misc</span></span><span class="line"><span style="color:#61AFEF">     3</span><span style="color:#D19A66">           32768</span><span style="color:#D19A66">           98303</span><span style="color:#98C379"> 32.0M</span><span style="color:#98C379"> boot</span></span><span class="line"><span style="color:#61AFEF">     4</span><span style="color:#D19A66">           98304</span><span style="color:#D19A66">          163839</span><span style="color:#98C379"> 32.0M</span><span style="color:#98C379"> recovery</span></span><span class="line"><span style="color:#61AFEF">     5</span><span style="color:#D19A66">          163840</span><span style="color:#D19A66">          229375</span><span style="color:#98C379"> 32.0M</span><span style="color:#98C379"> backup</span></span><span class="line"><span style="color:#61AFEF">     6</span><span style="color:#D19A66">          229376</span><span style="color:#D19A66">         2326527</span><span style="color:#98C379"> 1024M</span><span style="color:#98C379"> rootfs</span></span><span class="line"><span style="color:#61AFEF">     7</span><span style="color:#D19A66">         2326528</span><span style="color:#D19A66">         4423679</span><span style="color:#98C379"> 1024M</span><span style="color:#98C379"> oem</span></span><span class="line"><span style="color:#61AFEF">     8</span><span style="color:#D19A66">         4423680</span><span style="color:#D19A66">         7733214</span><span style="color:#98C379"> 1615M</span><span style="color:#98C379"> userdata</span></span></code></pre></td></tr></tbody></table></figure><h2 id="刷机教程">刷机教程</h2><p>首先需要准备好一台好用的电脑和：</p><ul><li><p>RK3568开发板</p></li><li><p>12V5521电源</p></li><li><p>双公头USB线</p><p>如果没有双公头USB线的话，可以找一个Type-C母转USB公的转换器，然后用手机数据线连接即可。或者自行找两个坏鼠标啥的，给USB线剪断然后自己DIY一根。</p></li><li><p>USB转TTL线（非必须）</p></li></ul><p>下载系统镜像并解压，解压后的文件后缀名应为img</p><p>电脑安装瑞芯微的驱动：</p><ul><li><code>DriverAssitant_v4.8.zip</code></li><li><code>DriverAssitant_v5.1.1.zip</code></li></ul><p>实际可能两个版本都要同时安装，否则进入MASKROM后会出现无法识别USB设备的错误。</p><p>驱动安装后，下载瑞芯微开发工具并解压，建议使用2.8.X版本刷机工具。</p><ul><li><code>RKDevTool.7z</code></li></ul><p>下面是刷机过程：</p><p>在刷机前，建议解压好固件为img格式，运行好RK开发工具，避免无法识别。</p><p>首先需要进入LOADER模式，如果您的刷机包非统一固件 <code>update.img</code> 类型的（如Armbian、OpenWrt），需要从LOADER模式进入MASKROM模式才能下载到EMMC。</p><p><img src="rk3568-seewo-sv21/6dfbc7dde974b718bffa76910c9fe18a.webp" alt=""></p><p>打开刷机工具，界面应为</p><p><img src="rk3568-seewo-sv21/c63fedffda21f2e7e0cdeca6dbee66b3.png" alt=""></p><blockquote><p>system 的地址是 0x00000000 意思是从开头覆盖 eMMC 中的数据</p><p>Loader 的地址 0xCCCCCCCC 是一个保留地址，用于指示工具发送loader文件</p></blockquote><p><img src="rk3568-seewo-sv21/8240b425879aec93464efa600cd2303f.jpg" alt=""></p><p><strong>注：刷armbian和openwrt固件需用名为<code>rk356x_spl_loader_ddr1056_v1.12.109_no_check_todly.bin</code>的loader文件。</strong></p><p><img src="rk3568-seewo-sv21/8240b425879aec93464efa600cd2303f.jpg" alt=""></p><p><img src="rk3568-seewo-sv21/54522a389c779ec1d224bcec32cd2788.png" alt=""></p><p>如按键后加电无法进入LOADER模式请使用硬件短接方式直接进入MASKROM模式后刷机。</p><p><img src="rk3568-seewo-sv21/9e125fa9f3da081948a819dc009b08fe.webp" alt=""></p><p><img src="rk3568-seewo-sv21/6a2b2603e24c4229a6e19abc0d2c8c5e.webp" alt=""></p><p><strong>注：MASKROM短接点在散热片下方的板子背面，很小的两个铜触点（根据方便文件表示判断）</strong></p><p>短接进入MASKROM刷机模式方法：用镊子或其他金属导体短接后不送手，然后加电，2秒后松开短接点即可进入MASKROM模式（一般听到电脑发现硬件提示音就可以松开短接）</p><p>此时，点击下方的执行按钮，就会开始强刷流程。如果遇到读取boot失败错误，请强制重启设备并重新执行上述流程。</p><blockquote><p>线刷不成功可能是由于3个原因造成:</p><ol><li>线刷包损坏。（云编译的固件可能存在此问题）</li><li>刷入损坏的线刷包导致的分区信息丢失。（解决方案：可以通过bin文件重建）</li><li>刷机工具因为某种奇怪的原因，无法刷入固件。（解决方案：重新解压一组刷机工具、更换USB接口或直接更换较老的电脑）</li></ol></blockquote><h3 id="Android刷机方法">Android刷机方法</h3><p><strong>安卓需要8G ROM，本人的为 4G ROM，暂不支持刷安卓</strong></p><p>刷原厂固件需要使用此方法，建议在刷之前点击 擦除Flash。</p><p>由于网盘提供的是Android11升级包形式兼容固件，所以刷机特别简单。</p><p>首先打开刷机工具，进入loader模式（MASKROM模式也可以）然后按下图即可完成刷机。</p><p><strong>刷机过程所有固件均要是解压后的img后缀镜像文件</strong></p><p><strong>刷机过程所有固件均要是解压后的img后缀镜像文件</strong></p><p><strong>刷机过程所有固件均要是解压后的img后缀镜像文件</strong></p><p><img src="rk3568-seewo-sv21/8b012a1a7850cfeddada614ab03d6e54.png" alt=""></p><p><img src="rk3568-seewo-sv21/ac58d10e6707786d412caf019c5d1bc4.png" alt=""></p><p><img src="rk3568-seewo-sv21/ac58d10e6707786d412caf019c5d1bc4.png" alt=""></p><h2 id="安装-Ubuntu-Base">安装 Ubuntu Base</h2><p>如果您需要使用到 GPU / NPU 等硬件功能，由于目前DTS不完善，不想使用开源内核，可以在原厂固件的基础上替换掉 Buildroot rootfs，无需重新编译内核，功能包好。不过 Rockchip 内核对 Docker 支持不完善，这种方法仅供嵌入式开发生产环境使用。</p><h3 id="制作镜像">制作镜像</h3><p>首先需要准备一个 Linux 构建环境，<s>这里使用 Ubuntu / Debian 都可以</s>，务必使用相近的版本，不然后面无法扩容。这里使用 x86 架构的处理器，需要通过转译的方式 chroot 运行 arm64 的 apt。</p><p>下载 Ubuntu Base 的 arm64 镜像，建议不要用新版本，因为原厂固件 4.19 内核较老，避免运行软件出现问题。这边使用 20.04 LTS 版本。</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 安装构建rootfs所需的工具</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"> qemu-user-static</span><span style="color:#98C379"> wget</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> https://mirror.nju.edu.cn/ubuntu-cdimage/ubuntu-base/focal/daily/current/focal-base-arm64.tar.gz</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 解压Ubuntu Base</span></span><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> ubuntu_rootfs</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tar</span><span style="color:#98C379"> xzvf</span><span style="color:#98C379"> focal-base-arm64.tar.gz</span><span style="color:#D19A66"> -C</span><span style="color:#98C379"> ubuntu_rootfs/</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 由于是x86环境，需要通过转译才能运行arm64的应用</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cp</span><span style="color:#98C379"> /usr/bin/qemu-aarch64-static</span><span style="color:#98C379"> ubuntu_rootfs/usr/bin/</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 必须设置DNS，不然chroot无法联网</span></span><span class="line"><span style="color:#56B6C2">echo</span><span style="color:#D19A66"> -e</span><span style="color:#98C379"> "nameserver 223.5.5.5\nnameserver 119.29.29.29"</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#98C379"> ubuntu_rootfs/etc/resolv.conf</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># echo -e "[Resolve]\nDNS=223.5.5.5 119.29.29.29" | sudo tee ubuntu_rootfs/etc/systemd/resolved.conf</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"> sed</span><span style="color:#D19A66"> -i</span><span style="color:#98C379"> 's/\(archive\|security\|ports\)\.ubuntu\.com/mirrors.aliyun.com/g'</span><span style="color:#98C379"> ubuntu_rootfs/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\|ports\)\.ubuntu\.com/mirrors.aliyun.com/g'</span><span style="color:#98C379"> ubuntu_rootfs/etc/apt/sources.list.d/</span><span style="color:#E5C07B">*</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 拷入SSH公钥，以便免密登录（请替换成自己的公钥）</span></span><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"> ubuntu_rootfs/root/.ssh</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#D19A66"> 700</span><span style="color:#98C379"> ubuntu_rootfs/root/.ssh</span></span><span class="line"><span style="color:#56B6C2">echo</span><span style="color:#98C379"> "ssh-rsa AAAAAAABBBBBBBCCCCCCCC"</span><span style="color:#ABB2BF"> | </span><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> tee</span><span style="color:#D19A66"> -a</span><span style="color:#98C379"> ubuntu_rootfs/root/.ssh/authorized_keys</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#D19A66"> 600</span><span style="color:#98C379"> ubuntu_rootfs/root/.ssh/authorized_keys</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"> mount</span><span style="color:#D19A66"> -t</span><span style="color:#98C379"> proc</span><span style="color:#98C379">  /proc</span><span style="color:#98C379"> ubuntu_rootfs/proc</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> mount</span><span style="color:#D19A66"> -t</span><span style="color:#98C379"> sysfs</span><span style="color:#98C379"> /sys</span><span style="color:#98C379">  ubuntu_rootfs/sys</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> mount</span><span style="color:#D19A66"> -o</span><span style="color:#98C379"> bind</span><span style="color:#98C379">  /dev</span><span style="color:#98C379">  ubuntu_rootfs/dev</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 进入rootfs</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chroot</span><span style="color:#98C379"> ubuntu_rootfs/</span><span style="color:#98C379"> /bin/bash</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><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></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">apt</span><span style="color:#98C379"> update</span></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"> dist-upgrade</span><span style="color:#D19A66"> -y</span></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"> nano</span><span style="color:#98C379"> openssh-server</span><span style="color:#98C379">  iputils-ping</span><span style="color:#98C379"> sudo</span><span style="color:#98C379"> iproute2</span><span style="color:#98C379"> ifupdown</span><span style="color:#98C379"> init</span><span style="color:#98C379"> dbus</span><span style="color:#98C379"> udev</span><span style="color:#98C379"> kmod</span><span style="color:#D19A66"> --no-install-recommends</span><span style="color:#D19A66"> -y</span></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"> curl</span><span style="color:#98C379"> wget</span><span style="color:#98C379"> tzdata</span><span style="color:#98C379"> bash-completion</span><span style="color:#98C379"> ethtool</span><span style="color:#98C379"> net-tools</span><span style="color:#98C379"> usbutils</span><span style="color:#98C379"> vim</span><span style="color:#98C379"> ca-certificates</span><span style="color:#D19A66"> --no-install-recommends</span><span style="color:#D19A66"> -y</span></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"> clean</span></span><span class="line"><span style="color:#61AFEF">rm</span><span style="color:#D19A66"> -rf</span><span style="color:#98C379"> /var/lib/apt/lists/</span><span style="color:#E5C07B">*</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 更改默认root密码，以便 TTL 登录</span></span><span class="line"><span style="color:#61AFEF">passwd</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 退出chroot</span></span><span class="line"><span style="color:#56B6C2">history</span><span style="color:#D19A66"> -c</span><span style="color:#ABB2BF"> &#x26;&#x26; </span><span style="color:#56B6C2">exit</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><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></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"> umount</span><span style="color:#98C379"> ubuntu_rootfs/proc</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> umount</span><span style="color:#98C379"> ubuntu_rootfs/sys</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> umount</span><span style="color:#98C379"> ubuntu_rootfs/dev</span></span><span class="line"></span><span class="line"><span style="color:#7F848E;font-style:italic"># 制作img刷机镜像</span></span><span class="line"><span style="color:#61AFEF">dd</span><span style="color:#98C379"> if=/dev/zero</span><span style="color:#98C379"> of=ubuntu_rootfs.img</span><span style="color:#98C379"> bs=1M</span><span style="color:#98C379"> count=</span><span style="color:#D19A66">2048</span><span style="color:#98C379"> oflag=direct</span><span style="color:#98C379"> status=progress</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> mkfs.ext4</span><span style="color:#98C379"> ubuntu_rootfs.img</span></span><span class="line"><span style="color:#61AFEF">mkdir</span><span style="color:#D19A66"> -p</span><span style="color:#98C379"> ubuntu_base_rootfs</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> chmod</span><span style="color:#D19A66"> 777</span><span style="color:#98C379"> ubuntu_base_rootfs</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> mount</span><span style="color:#98C379"> ubuntu_rootfs.img</span><span style="color:#98C379"> ubuntu_base_rootfs</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> cp</span><span style="color:#D19A66"> -rfp</span><span style="color:#98C379"> ubuntu_rootfs/</span><span style="color:#E5C07B">*</span><span style="color:#98C379"> ubuntu_base_rootfs/</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> umount</span><span style="color:#98C379"> ubuntu_base_rootfs/</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> e2fsck</span><span style="color:#D19A66"> -p</span><span style="color:#D19A66"> -f</span><span style="color:#98C379"> ubuntu_rootfs.img</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> resize2fs</span><span style="color:#D19A66"> -M</span><span style="color:#98C379"> ubuntu_rootfs.img</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"> rm</span><span style="color:#D19A66"> -rf</span><span style="color:#98C379"> ubuntu_base_rootfs/</span></span><span class="line"><span style="color:#61AFEF">sudo</span><span style="color:#98C379"> rm</span><span style="color:#D19A66"> -rf</span><span style="color:#98C379"> ubuntu_rootfs/</span></span></code></pre></td></tr></tbody></table></figure><p>此时我们就可以将 <code>ubuntu_rootfs.img</code> 烧录到 EMMC 的 rootfs 分区了。</p><h3 id="烧录镜像">烧录镜像</h3><p>进入 Loader / MaskROM 模式，电脑上打开开发工具，选择下载镜像，点击查看设备分区表，将 ubuntu_rootfs.img 刷入到对应 rootfs 的地址（0x00038000）。刷机完成后会自动重启，建议提前连接好 TTL。</p><p><img src="rk3568-seewo-sv21/25f4d75e9f52d376f6a74ff316877022.png" alt=""></p><h3 id="初始化系统">初始化系统</h3><p>由于 u-boot 设置启动参数不通过显示器输出tty，需要通过 TTL 登录系统，执行以下命令初始化用户环境：</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></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">timedatectl</span><span style="color:#98C379"> set-timezone</span><span style="color:#98C379"> Asia/Shanghai</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">hostnamectl</span><span style="color:#98C379"> set-hostname</span><span style="color:#98C379"> sv21</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">tee</span><span style="color:#98C379"> /etc/network/interfaces</span><span style="color:#ABB2BF"> &#x3C;&#x3C;</span><span style="color:#ABB2BF">'EOF'</span></span><span class="line"><span style="color:#98C379">auto lo</span></span><span class="line"><span style="color:#98C379">iface lo inet loopback</span></span><span class="line"></span><span class="line"><span style="color:#98C379">auto eth1</span></span><span class="line"><span style="color:#98C379">iface eth1 inet dhcp</span></span><span class="line"></span><span class="line"><span style="color:#98C379">#auto enxbc6bff423336</span></span><span class="line"><span style="color:#98C379">#iface enxbc6bff423336 inet dhcp</span></span><span class="line"></span><span class="line"><span style="color:#ABB2BF">EOF</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">systemctl</span><span style="color:#98C379"> restart</span><span style="color:#98C379"> networking</span></span><span class="line"></span></code></pre></td></tr></tbody></table></figure><blockquote><p>备注：默认DNS由systemd-resolved管理。</p></blockquote><p>默认 rootfs 分区为 2G 或 1G，如果不够用，可以使用 <code>cfdisk</code> 删除 oem 和 userdata 分区，然后给 rootfs 分区扩容（resize）。</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"># 删除 oem 和 userdata 分区</span></span><span class="line"><span style="color:#61AFEF">cfdisk</span><span style="color:#98C379"> /dev/mmcblk0</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><span class="line"><span style="color:#61AFEF">losetup</span><span style="color:#98C379"> /dev/loop0</span><span style="color:#98C379"> /dev/mmcblk0p6</span></span><span class="line"><span style="color:#61AFEF">resize2fs</span><span style="color:#98C379"> /dev/loop0</span></span><span class="line"><span style="color:#61AFEF">losetup</span><span style="color:#D19A66"> -d</span><span style="color:#98C379"> /dev/loop0</span></span><span class="line"><span style="color:#61AFEF">reboot</span></span></code></pre></td></tr></tbody></table></figure><h3 id="已知问题">已知问题</h3><ol><li><p><code>enxbc6bff423336</code> 这张网卡（非PoE的那个口）DHCP 开机会卡半天。</p></li><li><p>经过测试发现此内核缺少对 Docker 的支持。</p></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><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@localhost:~#</span><span style="color:#98C379"> bash</span><span style="color:#98C379"> check-config.sh</span></span><span class="line"><span style="color:#61AFEF">info:</span><span style="color:#98C379"> reading</span><span style="color:#98C379"> kernel</span><span style="color:#98C379"> config</span><span style="color:#98C379"> from</span><span style="color:#98C379"> /proc/config.gz</span><span style="color:#98C379"> ...</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Generally</span><span style="color:#98C379"> Necessary:</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> cgroup</span><span style="color:#98C379"> hierarchy:</span><span style="color:#98C379"> properly</span><span style="color:#98C379"> mounted</span><span style="color:#ABB2BF"> [/sys/fs/cgroup]</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NAMESPACES:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NET_NS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_PID_NS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IPC_NS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_UTS_NS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUPS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_CPUACCT:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_DEVICE:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_FREEZER:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_SCHED:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CPUSETS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_MEMCG:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_KEYS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_VETH:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_BRIDGE:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_BRIDGE_NETFILTER:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_NF_FILTER:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_NF_MANGLE:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_NF_TARGET_MASQUERADE:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NETFILTER_XT_MATCH_ADDRTYPE:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NETFILTER_XT_MATCH_CONNTRACK:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NETFILTER_XT_MATCH_IPVS:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NETFILTER_XT_MARK:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_NF_NAT:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NF_NAT:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_POSIX_MQUEUE:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NF_NAT_IPV4:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NF_NAT_NEEDED:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_BPF:</span><span style="color:#98C379"> missing</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Optional</span><span style="color:#98C379"> Features:</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_USER_NS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_SECCOMP:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_SECCOMP_FILTER:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_PIDS:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_MEMCG_SWAP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_MEMCG_SWAP_ENABLED:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IOSCHED_CFQ:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CFQ_GROUP_IOSCHED:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_BLK_CGROUP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_BLK_DEV_THROTTLING:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_PERF:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_HUGETLB:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_NET_CLS_CGROUP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CGROUP_NET_PRIO:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_CFS_BANDWIDTH:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_FAIR_GROUP_SCHED:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_NF_TARGET_REDIRECT:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_VS:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_VS_NFCT:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_VS_PROTO_TCP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_VS_PROTO_UDP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_IP_VS_RR:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_SECURITY_SELINUX:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_SECURITY_APPARMOR:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_EXT4_FS:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_EXT4_FS_POSIX_ACL:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> CONFIG_EXT4_FS_SECURITY:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> Network</span><span style="color:#98C379"> Drivers:</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "overlay":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_VXLAN:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_BRIDGE_VLAN_FILTERING:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">      Optional</span><span style="color:#ABB2BF"> (for </span><span style="color:#98C379">encrypted</span><span style="color:#98C379"> networks</span><span style="color:#ABB2BF">):</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_CRYPTO:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_CRYPTO_AEAD:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_CRYPTO_GCM:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_CRYPTO_SEQIV:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_CRYPTO_GHASH:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_XFRM:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_XFRM_USER:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_XFRM_ALGO:</span><span style="color:#98C379"> enabled</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_INET_ESP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_NETFILTER_XT_MATCH_BPF:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">      -</span><span style="color:#98C379"> CONFIG_INET_XFRM_MODE_TRANSPORT:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "ipvlan":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_IPVLAN:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "macvlan":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_MACVLAN:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_DUMMY:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "ftp,tftp client in container":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_NF_NAT_FTP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_NF_CONNTRACK_FTP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_NF_NAT_TFTP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_NF_CONNTRACK_TFTP:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> Storage</span><span style="color:#98C379"> Drivers:</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "btrfs":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_BTRFS_FS:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_BTRFS_FS_POSIX_ACL:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "overlay":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> CONFIG_OVERLAY_FS:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">  -</span><span style="color:#98C379"> "zfs":</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> /dev/zfs:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> zfs</span><span style="color:#98C379"> command:</span><span style="color:#98C379"> missing</span></span><span class="line"><span style="color:#61AFEF">    -</span><span style="color:#98C379"> zpool</span><span style="color:#98C379"> command:</span><span style="color:#98C379"> missing</span></span><span class="line"></span><span class="line"><span style="color:#61AFEF">Limits:</span></span><span class="line"><span style="color:#61AFEF">-</span><span style="color:#98C379"> /proc/sys/kernel/keys/root_maxkeys:</span><span style="color:#D19A66"> 1000000</span></span><span class="line"></span></code></pre></td></tr></tbody></table></figure><h3 id="编译-kmod-驱动">编译 kmod 驱动</h3><p>由于本人有使用 ch340 USB转TTL线的需求，直接连接模块发现无法识别，经过排查发现是因为 Buildroot 内核缺少 ch340 驱动导致的。幸好可以使用 kmod 的方式加载模块。</p><p>参考：<a href="https://blog.yaria.top/posts/dad8e69b">https://blog.yaria.top/posts/dad8e69b</a></p><ol><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#61AFEF">root@sv21:~#</span><span style="color:#98C379"> uname</span><span style="color:#D19A66"> -a</span></span><span class="line"><span style="color:#61AFEF">Linux</span><span style="color:#98C379"> sv21</span><span style="color:#98C379"> 4.19.172-ab92</span><span style="color:#7F848E;font-style:italic"> #1 SMP Fri Feb 24 16:05:46 CST 2023 aarch64 aarch64 aarch64 GNU/Linux</span></span><span class="line"><span style="color:#61AFEF">root@sv21:~#</span><span style="color:#98C379"> cat</span><span style="color:#98C379"> ^C</span></span><span class="line"><span style="color:#61AFEF">root@sv21:~#</span><span style="color:#98C379"> cat</span><span style="color:#98C379"> /proc/version</span></span><span class="line"><span style="color:#61AFEF">Linux</span><span style="color:#98C379"> version</span><span style="color:#98C379"> 4.19.172-ab92</span><span style="color:#ABB2BF"> (slave@lubo-server) V1.1.18.863330_b427e0cbd2ef_VBBoard (</span><span style="color:#61AFEF">gcc</span><span style="color:#98C379"> version</span><span style="color:#D19A66"> 6.3.1</span><span style="color:#D19A66"> 20170404</span><span style="color:#ABB2BF"> (Linaro </span><span style="color:#98C379">GCC</span><span style="color:#98C379"> 6.3-2017.05</span><span style="color:#ABB2BF">), GNU ld (</span><span style="color:#61AFEF">Linaro_Binutils-2017.05</span><span style="color:#ABB2BF">) 2.27.0.20161019) </span><span style="color:#7F848E;font-style:italic">#1 SMP Fri Feb 24 16:05:46 CST 2023</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>准备编译环境，这里在 Ubuntu 20.04 LTS x86_64 虚拟机下交叉编译。</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><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"> 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"> build-essential</span><span style="color:#98C379"> git</span><span style="color:#98C379"> python2</span><span style="color:#98C379"> bison</span><span style="color:#98C379"> flex</span><span style="color:#98C379"> libncurses-dev</span><span style="color:#98C379"> libssl-dev</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"> gcc-aarch64-linux-gnu</span><span style="color:#98C379"> g++-aarch64-linux-gnu</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">aarch64-linux-gnu-gcc</span><span style="color:#D19A66"> --version</span></span></code></pre></td></tr></tbody></table></figure><p>执行后如果输出类似 <code>aarch64-linux-gnu-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0</code>，说明安装成功。</p></li><li><p>获取内核源码，这里使用瑞芯微官方的内核源码，避免折腾 <a href="http://kernel.org">kernel.org</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:#7F848E;font-style:italic"># 下载对应内核源码</span></span><span class="line"><span style="color:#61AFEF">git</span><span style="color:#98C379"> clone</span><span style="color:#98C379"> https://gh.xrgzs.top/https://github.com/rockchip-linux/kernel.git</span><span style="color:#D19A66"> -b</span><span style="color:#98C379"> develop-4.19</span><span style="color:#D19A66"> --depth</span><span style="color:#D19A66"> 1</span></span><span class="line"><span style="color:#56B6C2">cd</span><span style="color:#98C379"> kernel</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>需要注意的是，这个内核的版本虽然都是4.19，但实际上只是差不多，并非完全相同，我们需要修改 <code>Makefiles</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">vim</span><span style="color:#98C379"> Makefile</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 找到：</span></span><span class="line"><span style="color:#61AFEF">VERSION</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 4</span></span><span class="line"><span style="color:#61AFEF">PATCHLEVEL</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 19</span></span><span class="line"><span style="color:#61AFEF">SUBLEVEL</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> 172</span><span style="color:#7F848E;font-style:italic"> # 这里改成 172</span></span><span class="line"><span style="color:#61AFEF">EXTRAVERSION</span><span style="color:#98C379"> =</span><span style="color:#D19A66"> -ab92</span><span style="color:#7F848E;font-style:italic">  # 必须加这个后缀！</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 保存退出</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@sv21:~#</span><span style="color:#98C379"> insmod</span><span style="color:#98C379"> ch341.ko</span></span><span class="line"><span style="color:#ABB2BF">[ 3305.113286] ch341: version magic </span><span style="color:#98C379">'4.19.232 SMP mod_unload aarcinsmod: ERROR: could not insert module ch341.ko: Invalid module format</span></span><span class="line"><span style="color:#98C379">root@sv21:~# h64'</span><span style="color:#ABB2BF"> should be </span><span style="color:#98C379">'4.19.172-ab92 SMP mod_unload aarch64'</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>配置内核，编译 <code>ch341.ko</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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#7F848E;font-style:italic"># 配置RK3568默认配置</span></span><span class="line"><span style="color:#61AFEF">make</span><span style="color:#98C379"> ARCH=arm64</span><span style="color:#98C379"> rockchip_linux_defconfig</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 开启CH341模块编译</span></span><span class="line"><span style="color:#61AFEF">make</span><span style="color:#98C379"> ARCH=arm64</span><span style="color:#98C379"> menuconfig</span><span style="color:#7F848E;font-style:italic"> # 勾选 Device Drivers→USB→USB Serial→CH341 (M模式)</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 准备模块编译环境</span></span><span class="line"><span style="color:#61AFEF">make</span><span style="color:#98C379"> ARCH=arm64</span><span style="color:#98C379"> CROSS_COMPILE=aarch64-linux-gnu-</span><span style="color:#98C379"> modules_prepare</span></span><span class="line"><span style="color:#7F848E;font-style:italic"># 单独编译ch341.ko</span></span><span class="line"><span style="color:#61AFEF">make</span><span style="color:#98C379"> ARCH=arm64</span><span style="color:#98C379"> CROSS_COMPILE=aarch64-linux-gnu-</span><span style="color:#98C379"> M=drivers/usb/serial</span><span style="color:#98C379"> ch341.ko</span></span></code></pre></td></tr></tbody></table></figure><p>menuconfig那里超级难选，下面给出具体截图：</p><p><img src="./rk3568-seewo-sv21/1462c2aa64bc84c2b32e2479eae7a824.png" alt=""></p><p><img src="./rk3568-seewo-sv21/c884aa7e2dc4922de3a1781cd7f361d7.png" alt=""></p></li><li><p>编译完成后会在 <code>drivers/usb/serial</code> 目录下生成 <code>ch341.ko</code> 模块文件。</p><p><img src="./rk3568-seewo-sv21/c8f3c1a5790fb24b97b89effe50da445.png" alt=""></p></li><li><p>将其复制到 SV21 上的 <code>/lib/modules/4.19.172-ab92/kernel/drivers/usb/serial/</code> 目录下，然后执行 <code>depmod -a</code> 更新模块依赖，最后使用 <code>modprobe ch341</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></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"> /lib/modules/4.19.172-ab92/kernel/drivers/usb/serial</span></span><span class="line"><span style="color:#61AFEF">wget</span><span style="color:#98C379"> http://10.0.1.111:8080/ch341.ko</span><span style="color:#D19A66"> -O</span><span style="color:#98C379"> /lib/modules/4.19.172-ab92/kernel/drivers/usb/serial/ch341.ko</span></span><span class="line"><span style="color:#61AFEF">depmod</span><span style="color:#D19A66"> -a</span></span><span class="line"><span style="color:#61AFEF">modprobe</span><span style="color:#98C379"> ch341</span></span></code></pre></td></tr></tbody></table></figure></li></ol><p>未完待续…</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;SEEWO SV21 RK3568 是希沃录播机的主板，带双网口（其中一个支持POE，另外两个网口为麦克风）、3个HDMI（1xIn，2xOut）、1个USB3.0端口、2个USB2.0端口、1个SATA接口（5V）等，采用12V DC5521 电源输入，2G RAM + </summary>
      
    
    
    
    <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="RockChip" scheme="https://www.xrgzs.top/tags/rockchip/"/>
    
  </entry>
  
  <entry>
    <title>PotPlayer + LAV + madVR 配置</title>
    <link href="https://www.xrgzs.top/posts/potplayer"/>
    <id>https://www.xrgzs.top/posts/potplayer</id>
    <published>2024-12-25T13:46:38.000Z</published>
    <updated>2024-12-25T13:46:38.000Z</updated>
    
    <content type="html"><![CDATA[<p>PotPlayer 是 Windows 下一款界面简洁、功能齐全的高清影音播放器，与 MPC、MPV 相比更加人性化。但其默认方案可能处于兼容性和功能新考虑，画质、音质十分糟糕，一直饱受诟病。本人已经使用 PotPlayer 数年，将为大家分享一下配置方案，希望对大家有所帮助。</p><h2 id="安装">安装</h2><p>需要注意的是，PotPlayer 原版是具有广告的，只是在国内环境下无法连接广告服务器而已（<s>GFW 最有用的一集</s>）。这里我们可以使用去广告的版本，可以从 <a href="https://www.423down.com/3050.html">423down</a> 下载。但这种绿色版本使用起来与安装版的不一致，容易被当作垃圾清理或出现无法关联的问题。</p><p>此处强烈推荐直接安装完美解码，其附带的 PotPlayer 已经进行了相关修改，无广告：</p><blockquote><p>完美解码是一款为高清影视爱好者精心打造的影音解码包，能软硬件解码播放流行的视频和音频格式。集成多媒体播放器 PotPlayer、MPC-HC、MPC-BE，分离器解码器套件 LAV Filters，高画质渲染器 madVR，支持中英语言安装使用。</p></blockquote><p>本人更推荐直接使用完美解码加上 <a href="https://www.deviantart.com/mr-web/art/Modern-W10-Skin-for-PotPlayer-ITA-ENG-678381013">ModernW10</a> 皮肤，以获得一个更加友好的体验。需要注意的是，该皮肤在 deviantart 上仅提供 English 版本，我们使用的是 <a href="https://tieba.baidu.com/p/7196432805">@断剑留痕</a> 的汉化版本。</p><p><img src="potplayer/7d43c23ce5c16d30513f24c1696e0270.webp" alt=""></p><p>本人需要在系统中集成完美解码，因此制作一键静默安装包，只需双击即可安装，同时自动换肤、卸载系统自带的 UWP 播放器。下载地址：</p><p><a href="https://url.xrgzs.top/PureCodec">https://url.xrgzs.top/PureCodec</a></p><p>如果您希望手动安装完美解码，可以去完美解码官网下载，需要注意的是，网络上搜到的完美解码官网（完美下载）并非作者发布地址，但应该是有一定的关系，进去下载的时候需要避免点击到假的下载地址。真正的发布地址为 <a href="http://diodiy.top/?thread-1.htm">http://diodiy.top/?thread-1.htm</a> 。</p><blockquote><p>完美解码已被 WinGet 收录，可以一行命令安装：</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">winget install </span><span style="color:#56B6C2">--</span><span style="color:#ABB2BF">id Dio.PureCodec</span></span></code></pre></td></tr></tbody></table></figure></blockquote><h2 id="原理">原理</h2><p>默认情况下，PotPlayer 的解码方案简化如下图所示。</p><pre class="mermaid">flowchart TD    A1["MP4文件"] --> B["分离器（Pot）"]    A2["MKV文件"] --> B    B --> C["视频流（HEVC）"] & D["音频流（TrueHD）"] & E["字幕（PGS）"]    C --4k 23.976P 10bit--> C1["视频解码器（FFmpeg）"]    C1 --8bit--> C2["视频滤镜（Pot）"]    C2 --> C3["视频渲染器（EVR）"]    C3 --> C4["视频呈现器（D3D）"]    C4 --> C5["显卡"]    C5 --4K 60P 8bit limit--> C6["显示器"]    D --48k 24bit 8ch--> D1["音频解码器（FFmpeg）"]    D1 --16bit PCM--> D2["音频滤镜（Pot）"]    D2 --2ch--> D3["音频渲染器（DirectSound）"]    D3 --> D4["WASAPI（Vista+）"]    D4 --> D5["声卡"]    D5 --44.1k 16bit--> D6["扬声器"]    E --> E1["字幕插件（Pot）"]    E1 --Overlay Mixer/VMR/EVR--> C2</pre><p>我们简单介绍一下，PotPlayer 为了实现亮度调整、音量规格化等复杂的功能，和传统播放器一样，引入了一些滤镜，但这些滤镜对 10 bit 视频甚至 24 bit 音频都不支持，会将 10 bit / 24 bit 数据砍成 8 bit / 16 bit 然后再给滤镜处理，导致播放效果一坨屎，出现色带、失真等问题。</p><p>为了解决这个问题，我们需要修改 PotPlayer 默认糟糕的配置：更换 PotPlayer 的渲染器，并关闭 PotPlayer 的滤镜。</p><pre class="mermaid">flowchart TD    A1["MP4文件"] --> B["分离器（LAV Filter）"]    A2["MKV文件"] --> B    B --> C["视频流（HEVC）"] & D["音频流（TrueHD）"] & E["字幕（PGS）"]    C --4k 23.976P 10bit--> C1["视频解码器（LAV Video Decoder）"]    C1 --10bit YUV--> C2["视频滤镜（disabled）"]    C2 --10bit--> C3["视频渲染器（madVR）"]    C3 --> C4["视频呈现器（D3D）"]    C4 --> C5["显卡"]    C5 --4k 23.976P 10bit full--> C6["显示器"]    D  --48k 24bit 8ch--> D1["音频解码器（LAV Audio Decoder）"]    D1 --2ch 24bit PCM--> D2["音频滤镜（disabled）"]    D2 --> D3["音频渲染器（WASAPI）"]    D3 --> D4["WASAPI（Vista+）"]    D4 --> D5["声卡"]    D5 --48k 24bit--> D6["扬声器"]    E --> E1["字幕插件（Pot）"]    E1 --Overlay Mixer/VMR/EVR--> C2</pre><p>madVR 是一种高质量的GPU辅助的视频渲染器。特点：高质量的色度上采样，高质量的缩放（bicubic、mitchell、lanczos、spline等），高质量的YCbCr -&gt; RGB转换，用于显示校准的色域和伽玛校正，完整的16位处理队列，最终的16位处理结果抖动到RGB输出位深，绕过显卡的视频（损伤）算法，所有工作都通过没有捷径，最高质量优先于其他任何事情的GPU着色器完成。</p><p>除了播放器这个因素，在使用 HDMI 外接显示器/电视时，由于传统电视的一些限制（可能是考虑对信号进行一些冗余，分辨率和色彩动态返回会进行一些裁切），显卡会将输出动态范围设置为有限（16-235），而不是完全（0-255），导致动态范围缩减，如果您连接的是显示器，此时会出现画面发白的情况，甚至画面都无法显示全屏。</p><p>此外，Windows 下默认声卡的采样率和量化位数是 48000 Hz / 24 bit，部分老版本的驱动会将其设置为 44100 Hz / 16 bit，而蓝光的标准是 48000 kHz 以上，错误的音频设置会造成重采样，导致播放声音失真，不如蓝光播放机，这点很少有人提。</p><p>从 Windows 95 开始，Windows 在 DirectX 中引入了 DirectSound 作为音频播放接口，但存在高延迟、低动态、不稳定等问题。一些厂家联合发布了 ASIO 音频接口，允许软件如 DAW 独占声卡，以解决 DirectSound 的问题。从 Windows Vista 开启重写了音频堆栈，引入了 WASAPI，支持独占模式，DirectSound、MME 这些传统的接口被模拟为 Windows Audio Session 实例。</p><pre class="mermaid">graph TD    A[Applications] --> B[Sessions]    B --> C["WASAPI (Windows Audio Session API)"]    C -->|Exclusive Mode| D[Unmixed Audio Streams Rendered Directly to Audio Adapter]    D --> E[No Other Application's Audio Plays, Signal Processing Has No Effect]    C -->|Shared Mode| F[Application Renders Audio Streams]    F --> G["Optional Per-Stream Audio Effects (LFX)"]    G --> H[Global Audio Engine Mixes Streams]    H --> I["Global Audio Effects (GFX) Applied"]    I --> J[Rendered on Audio Device]    J --> K["Host-Based Audio Processing (APOs)"]    L[Audio Driver] -->|Kernel Mode| M[Operates in Kernel Mode Only]    N[DirectSound and MME Emulated as Session Instances] --> B    O["Windows Kernel Mixer (KMixer)"] -->|Completely Gone| P[Prevents DirectSound Hardware Acceleration]    P --> Q[Removes Support for DirectSound3D and EAX Extensions]    R[ASIO and OpenAL APIs Not Affected] --> C</pre><p>不过 PotPlayer 不支持 ASIO、DSD，目前也只有对音频要求比较高的播放器如 Foobar2000 才能支持。且应该是处于兼容性考虑，默认使用 DirectSound 的方式来播放音频，这可能会导致与其他播放器抢占 Mixer 导致音频动态范围降低。</p><p>关于这个 WASAPI 还引入了一个叫做音频处理对象（APO）的东西，在这几年生产的 PC 上特别常见，电脑/主板厂商会增加什么杜比音效、DTS 音效 APO，这些 APO 会通过压缩等方法处理音频，从而使动态范围降低。部分声卡在安装原厂 APO 驱动后，还无法调高采样率。</p><h2 id="方法">方法</h2><h3 id="系统">系统</h3><p>建议升级到 Windows 10 1903 以上版本系统，避免不兼容的情况。</p><h3 id="显卡">显卡</h3><p>我们需要进入显卡的控制面板，将动态范围参数调整至“完全”，并根据设备的情况选择是否使用 10 bit 输出。</p><p>已知这种问题在 NVIDIA 的显卡上比较常见（So, fxxk NVIDIA），这边就以 NVIDIA 为例。</p><p>首先打开 NVIDIA Control Panel，如果找不到的话，需要您自行去 Microsoft Store 安装。</p><p><img src="potplayer/ca2ce612cd26904ac4f3459718559f93.webp" alt=""></p><p>在显示 -&gt; 更改分辨率，将对应显示器的动态范围参数调整至完全，输出颜色深度调到 10bpc 或者 8bpc（取决于显示器支持）。</p><p><img src="potplayer/3056d32366e3c19e2c68b743c2f4ebd3.png" alt=""></p><p>（可选）在视频 -&gt; 调整视频颜色设置，强制播放器输出完全动态范围。</p><p><img src="potplayer/b910ebbcacab8e70e8e91a9c523280d1.png" alt=""></p><p>Intel 和 AMD 由于不同版本的驱动配置差距较大，且本人无机器，此处暂不演示，以下截图转自 VCB-Studio：</p><p><img src="potplayer/96f7f50e5542e040e40333ddc705f849.webp" alt="Intel 核显用户，将量化范围设为全范围"></p><p><img src="potplayer/8aa2a577ce4f3adda85f9589e8c9028f.png" alt="AMD 显卡：新版设置界面，选择 Full RGB"></p><p><img src="potplayer/c4077e129d05891b75467420551cbdb6.png" alt="CCC 旧版界面，同样选择 Full RGB"></p><p>现在三家显卡均可以在控制面板开启此功能。如果仍有无法在上述面板开启此设定的情况，那么请安装完成后请使用 <code>C:\Program Files (x86)\Pure Codec\madVR\madLevelsTweaker.exe</code> 将显示色彩范围调整到 0-255（在 Force PC levels 选项上打勾再点击 Apply 按钮并且重新启动）</p><p><img src="potplayer/16ccf4045b58a1553767d854f736e381.png" alt=""></p><p>另外还有一个比较重要的点，如果您使用的是双显卡混合输出笔记本，需要在显卡控制面板中将 <code>C:\Program Files (x86)\Pure Codec\x64\PotPlayerMini64.exe</code> 设置为高性能（独显），或使用独显直连。对于 Windows 10 以上系统，可以在 Windows 设置中指定显卡。</p><p><img src="potplayer/014fcc01282f083ba34c73566287f609.webp" alt=""></p><h3 id="声卡">声卡</h3><p>如果您对音质没有特殊要求，可以跳过此配置。</p><p>如果您是使用板载声卡，请卸载专用驱动，使用公版驱动，并禁用所有音效，并确保声卡的采样率大于或与您的音源匹配。</p><p><img src="potplayer/af5d205003800c2f049918071f35bdf0.png" alt=""></p><p>如果您是 HDMI 或 S/PDIF 连接音响/功放，可以在播放器处配置直通，可播放测试片源，在功放处查看是否能够点亮次世代音频技术。</p><h3 id="PotPlayer">PotPlayer</h3><p>这边我们建议使用完美解码，避免过多折腾。</p><p>首先我们打开 <code>完美解码设置</code>，注意这是一个独立的设置程序，并非 PotPlayer 的设置。</p><p>默认的配置为 D3D11 渲染，不使用 EVR 渲染，同时开启相关滤镜，方便大众使用。</p><p><img src="potplayer/7f17ce62854e26d8af2c6c28948f97f0.png" alt=""></p><p>我们这里为了进一步提高播放体验，调整为下图所示：</p><p><img src="potplayer/6de05951be05260bfc3b9457041c4568.png" alt=""></p><p>如果嫌 madVR 太卡的话，换成 MPCVR 即可，支持 10 bit、HDR、单层杜比视界。</p><p>到此就基本配置完了，就是这么简单。这就是用完美解码的理由。</p><p>当然，此时音频还是使用 DirectSound，我们需要启动 PotPlayer，进入选项 -&gt; 声音，默认输出设备选择“内置 WASAPI 音频渲染器”，然后输出通道选择 24 位或更高。</p><p><img src="potplayer/fc1020232be5c64f76e6911bb6cff239.png" alt=""></p><h3 id="madVR">madVR</h3><p>madVR 我们只需简单处理一下即可，配置不难。</p><p>我们可以通过完美解码设置唤起 madVR 设置界面。</p><p><img src="potplayer/e4fd61dd944093f4b99b29cdd504cb2b.webp" alt=""></p><p>首先我们需要正确配置设备类型，此处我们使用的是显示器，那么选择 <code>Digital Monitor / TV</code> 即可。</p><p><img src="potplayer/395b3876a0c87f89eb156ce0249dd90f.png" alt=""></p><p>进入 <code>properties</code>，这里的 <code>RGB output levels</code> 和显卡的保持一致即可，<code>native display bitdepth</code> 根据面板的实际 bit 选择，如 6bit 抖 8bit 的面板，选择 7bit，这里选择 8bit。</p><p><img src="potplayer/104609affda816a13ab5c00befa49c4f.png" alt=""></p><p><code>calibration</code> 的话，由于我们没有专业校色设备，所以无法设置，如果您的显示器是校准过的，可以选择 <code>this display is already calibrated</code>，然后选择对应的色域。如果您有显示器的校色文件，可以选择 <code>calibrate this display by using eternal 3DLUT files</code>，然后加载对应 3DLUT 文件。</p><p><img src="potplayer/d7f5f27e26f96cf3d67ed188efdfa1a2.png" alt=""></p><p><code>display modes</code>通常无需额外配置。</p><p><code>hdr</code> 的话，如果您的显示器不支持 HDR，那么可以设置 <code>tone map HDR using pixel shaders</code>，然后输入您显示器的最大亮度尼特，然后观看时将您的显示器亮度调到最大。如果您有显示器的校色文件，可以选择 <code>tone map HDR using eternal 3DLUT</code>，然后加载对应 3DLUT 文件。</p><p><img src="potplayer/170d69a852b65126199612b29328701f.png" alt=""></p><p>相关参数可在网络上查询，无需自行测量。</p><p><img src="potplayer/3f3149fbfa8779c5e7f1e9f31bb90024.webp" alt=""></p><p>接下来我们进行图像处理环节，不会调的保持默认即可。</p><p>对于 <code>processing/artifact removal</code>（去伪影），我们打开去色带功能，强度保持默认的“low/high”即可。色带是最为常见的视频瑕疵之一，默认启用去色带是利远大于弊的。</p><p><img src="potplayer/ec00e88f70bf9fd1fd5aeb4d3a94e56d.png" alt=""></p><p>对于<code>scaling algorithm</code>（缩放算法），我们提供三套推荐的缩放算法相关配置，按对性能的要求分为低、中、高三档：</p><table><thead><tr><th></th><th><strong>chroma upscaling</strong></th><th><strong>image downscaling</strong></th><th><strong>image upscaling</strong></th></tr></thead><tbody><tr><td><strong>低</strong></td><td>Cubic (Bicubic50)</td><td>Cubic (Bicubic50) + LL</td><td>Lanczos (3 taps) + SL</td></tr><tr><td><strong>中</strong></td><td>Cubic (Bicubic50) + AR</td><td>Cubic (Bicubic50) + LL + AR (relaxed)</td><td>Jinc + AR + SL</td></tr><tr><td><strong>高</strong></td><td>super-xbr (100) + AR</td><td>SSIM (1D – 100%) + LL + AR (relaxed)</td><td>NGU Sharp (medium)</td></tr></tbody></table><ul><li><p>LL =“scale in <strong>l</strong>inear <strong>l</strong>ight”</p></li><li><p>SL =“scale in <strong>s</strong>igmoidal <strong>l</strong>ight”</p></li><li><p>AR =“activate <strong>a</strong>nti-<strong>r</strong>inging filter”</p></li><li><p>NGU Sharp 除了选择“luma doubling”质量以外，其余全部保留默认的“let madVR decide”即可</p></li></ul><blockquote><p>需要指出的是，这里对性能要求的高低并不绝对等同于画质的高低，“低”档设置的质量其实已经相当好了，而“高”档设置在一些低质量片源上反而有可能放大片源瑕疵。综合来说我们最推荐“中”档设置，是在画质、功耗和泛用性之间较为平衡的选择。</p></blockquote><p><img src="potplayer/083b1a3ebb3c222b2b529b4baf1b795f.png" alt=""></p><p><img src="potplayer/f38dc4141e2009ad3e3e637a4c3f8e41.png" alt=""></p><p><img src="potplayer/5dc327a81e603d23826b33a64622e189.png" alt=""></p><p>对于<code>rendering/smooth motion</code>（补帧），我们建议在显示器上启用，因为显示器通常不支持设置如 23.976 Hz 的刷新率，避免出现卡顿感。</p><p><img src="potplayer/08c712ecebaa825e02d2c7e9f23b8cb9.png" alt=""></p><p>测试视频：<a href="https://alist.xrgzs.top/d/pxy/Video/%E5%B8%A7%E7%8E%87%E6%B5%8B%E8%AF%95.m2ts">https://alist.xrgzs.top/d/pxy/Video/帧率测试.m2ts</a></p><p>如果您使用的是电视，通常支持这些 HDTV 制式，可以开启全屏独占模式（FSE，<code>rendering/general settings/enable automatic fullscreen exlucsive mode</code>）使其自动切换匹配刷新率，无需补帧。</p><p><img src="potplayer/777053c60248e93aab1ae58753873ea5.webp" alt=""></p><p>当然，如果您使用 madVR 就想实现补帧，可以保持 FSE 为关闭状态（完美解码默认），因为它带来的麻烦远多于好处。</p><h2 id="参考">参考</h2><p><a href="https://en.wikipedia.org/wiki/Technical_features_new_to_Windows_Vista#Audio_stack_architecture">https://en.wikipedia.org/wiki/Technical_features_new_to_Windows_Vista#Audio_stack_architecture</a></p><p><a href="https://vcb-s.com/archives/7228">https://vcb-s.com/archives/7228</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;PotPlayer 是 Windows 下一款界面简洁、功能齐全的高清影音播放器，与 MPC、MPV 相比更加人性化。但其默认方案可能处于兼容性和功能新考虑，画质、音质十分糟糕，一直饱受诟病。本人已经使用 PotPlayer 数年，将为大家分享一下配置方案，希望对大家有所帮</summary>
      
    
    
    
    <category term="软件资源" scheme="https://www.xrgzs.top/categories/%E8%BD%AF%E4%BB%B6%E8%B5%84%E6%BA%90/"/>
    
    
    <category term="资源" scheme="https://www.xrgzs.top/tags/%E8%B5%84%E6%BA%90/"/>
    
    <category term="影视" scheme="https://www.xrgzs.top/tags/%E5%BD%B1%E8%A7%86/"/>
    
    <category term="影音" scheme="https://www.xrgzs.top/tags/%E5%BD%B1%E9%9F%B3/"/>
    
  </entry>
  
  <entry>
    <title>PowerShell 解压 PSF 格式的更新包</title>
    <link href="https://www.xrgzs.top/posts/powershell-extract-psf"/>
    <id>https://www.xrgzs.top/posts/powershell-extract-psf</id>
    <published>2024-12-03T07:54:39.000Z</published>
    <updated>2026-02-08T12:55:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>发现 W10UI 中有一段 PowerShell 代码，能够解压 PSF 格式的更新包，支持最新 PA31，通过原生 C# + Win32 方法实现，无需 PSFExtractor.exe。提取出来发现可以直接使用，特此分享。</p><h2 id="使用方法">使用方法</h2><ol><li><p>准备文件：<code>Windows11.0-KB5046732-x64.wim</code>、<code>Windows11.0-KB5046732-x64.psf</code>。</p><ul><li>获取方式：<ul><li>UUPDUMP 下载</li><li>解压 MSU 格式更新包得到</li></ul></li><li>两个文件名相同，此处KB号仅供参考</li><li>第一个文件之前为 CAB 格式，现在改成了 WIM 格式</li></ul></li><li><p>打开 PowerShell。</p></li><li><p>导入函数：将下方 <a href="#%E5%87%BD%E6%95%B0%E5%86%85%E5%AE%B9">函数内容</a> 中的代码复制粘贴进去，然后回车。</p></li><li><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:#E5C07B">$ErrorActionPreference</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> 'SilentlyContinue'</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>解压 <code>Windows11.0-KB5046732-x64.wim</code> 或者 CAB 格式的补丁到 <code>Windows11.0-KB5046732-x64</code> 文件夹。</p><ul><li>文件夹要与更新包位于同一目录</li><li>可以直接右键 <code>Windows11.0-KB5046732-x64.wim</code> =&gt; <code>7-Zip</code>=&gt; <code>解压到 &quot;Windows11.0-KB5046732-x64\&quot;</code> 进行解压，报错“有效数据外包含额外数据”为正常现象</li></ul></li><li><p>PowerShell 中调用函数 <code>P</code> 解压 PSF（建议输入完整路径）：</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">P </span><span style="color:#98C379">"D:\TEMP\Windows11.0-KB5046732-x64.psf"</span></span></code></pre></td></tr></tbody></table></figure></li><li><p>通过解压后的文件夹大小判断是否解压正确。</p><ul><li>正常 1 GB 以上，如果只有几百兆肯定有问题</li></ul></li><li><p>此时可使用 DISM 添加包 <code>Windows11.0-KB5046732-x64\update.mum</code> 来更新系统。</p></li></ol><h3 id="特别说明">特别说明</h3><p>这个 <code>P</code> 函数的 第二个参数 <code>$DllFile</code> 貌似有坑，具体见这个 issue：</p><p><a href="https://github.com/Secant1006/PSFExtractor/issues/4">https://github.com/Secant1006/PSFExtractor/issues/4</a></p><p>如果有需要，请手动提取 <code>msdelta.dll</code> 或者 <code>UpdateCompression.dll</code>，然后指定路径。</p><p><code>UpdateCompression.dll</code> 可从 <code>DesktopDeployment.cab</code> 中提取。</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:#ABB2BF">P </span><span style="color:#98C379">"D:\TEMP\Windows11.0-KB5046732-x64.psf"</span><span style="color:#98C379"> "D:\TEMP\UpdateCompression.dll"</span></span></code></pre></td></tr></tbody></table></figure><h2 id="函数内容">函数内容</h2><p>方法来自：</p><ul><li><a href="https://www.elevenforum.com/t/a-script-to-extract-wim-and-psf-from-msu-windows-update-files.39262/">https://www.elevenforum.com/t/a-script-to-extract-wim-and-psf-from-msu-windows-update-files.39262/</a></li><li><a href="https://forums.mydigitallife.net/posts/1216064">https://forums.mydigitallife.net/posts/1216064</a></li><li><a href="https://github.com/abbodi1406/BatUtil/blob/master/W10UI/W10UI.cmd">https://github.com/abbodi1406/BatUtil/blob/master/W10UI/W10UI.cmd</a></li></ul><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><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></pre></td><td class="code"><pre class="shiki one-dark-pro"><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> Native</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$DllFile</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">    $Lib</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Path</span><span style="color:#ABB2BF">]::GetFileName(</span><span style="color:#E06C75">$DllFile</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $Marshal</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">System.Runtime.InteropServices.Marshal</span><span style="color:#ABB2BF">]</span></span><span class="line"><span style="color:#E06C75">    $Module</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">AppDomain</span><span style="color:#ABB2BF">]::CurrentDomain.DefineDynamicAssembly((</span><span style="color:#56B6C2">Get-Random</span><span style="color:#ABB2BF">), </span><span style="color:#98C379">'Run'</span><span style="color:#ABB2BF">).DefineDynamicModule((</span><span style="color:#56B6C2">Get-Random</span><span style="color:#ABB2BF">))</span></span><span class="line"><span style="color:#E06C75">    $Struct</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Module.DefineType</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'DI'</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1048841</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">ValueType</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Struct.DefineField</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'lpStart'</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">6</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Struct.DefineField</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'uSize'</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">UIntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">6</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Struct.DefineField</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'Editable'</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Boolean</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">6</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $DELTA_INPUT</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Struct.CreateType</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#E06C75">    $Struct</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Module.DefineType</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'DO'</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1048841</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">ValueType</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Struct.DefineField</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'lpStart'</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">6</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Struct.DefineField</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'uSize'</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">UIntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">6</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $DELTA_OUTPUT</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Struct.CreateType</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#E06C75">    $Class</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Module.DefineType</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'PSFE'</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1048961</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Object</span><span style="color:#ABB2BF">], </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Class.DefinePInvokeMethod</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'LoadLibraryW'</span><span style="color:#ABB2BF">, </span><span style="color:#98C379">'kernel32.dll'</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">22</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#C678DD">@</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">String</span><span style="color:#ABB2BF">]), </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">3</span><span style="color:#ABB2BF">).SetImplementationFlags(</span><span style="color:#D19A66">128</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Class.DefinePInvokeMethod</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'ApplyDeltaB'</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Lib</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">22</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Int32</span><span style="color:#ABB2BF">], </span><span style="color:#C678DD">@</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">Int64</span><span style="color:#ABB2BF">], [</span><span style="color:#C678DD">Type</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$DELTA_INPUT</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Type</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$DELTA_INPUT</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Type</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$DELTA_OUTPUT.MakeByRefType</span><span style="color:#ABB2BF">()), </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">3</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Class.DefinePInvokeMethod</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'DeltaFree'</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$Lib</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">22</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">Int32</span><span style="color:#ABB2BF">], </span><span style="color:#C678DD">@</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">]), </span><span style="color:#D19A66">1</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">3</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $Win32</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Class.CreateType</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> ApplyDelta</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$dBuffer</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$dFile</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">    $trg</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Activator</span><span style="color:#ABB2BF">]::CreateInstance(</span><span style="color:#E06C75">$DELTA_OUTPUT</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $src</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Activator</span><span style="color:#ABB2BF">]::CreateInstance(</span><span style="color:#E06C75">$DELTA_INPUT</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $dlt</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Activator</span><span style="color:#ABB2BF">]::CreateInstance(</span><span style="color:#E06C75">$DELTA_INPUT</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $dlt.lpStart</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $Marshal</span><span style="color:#ABB2BF">::AllocHGlobal(</span><span style="color:#E06C75">$dBuffer.Length</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $dlt.uSize</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">Activator</span><span style="color:#ABB2BF">]::CreateInstance([</span><span style="color:#C678DD">UIntPtr</span><span style="color:#ABB2BF">], </span><span style="color:#C678DD">@</span><span style="color:#ABB2BF">([</span><span style="color:#C678DD">UInt32</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$dBuffer.Length</span><span style="color:#ABB2BF">))</span></span><span class="line"><span style="color:#E06C75">    $dlt.Editable</span><span style="color:#56B6C2"> =</span><span style="color:#D19A66"> $true</span></span><span class="line"><span style="color:#E06C75">    $Marshal</span><span style="color:#ABB2BF">::Copy(</span><span style="color:#E06C75">$dBuffer</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$dlt.lpStart</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$dBuffer.Length</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Win32</span><span style="color:#ABB2BF">::ApplyDeltaB(</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$src</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$dlt</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">ref</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$trg</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">    if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$trg.lpStart</span><span style="color:#56B6C2"> -eq</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">]::Zero) &#123; </span><span style="color:#C678DD">return</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#E06C75">    $out</span><span style="color:#56B6C2"> =</span><span style="color:#56B6C2"> New-Object</span><span style="color:#ABB2BF"> byte[] </span><span style="color:#E06C75">$trg.uSize.ToUInt32</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#E06C75">    $Marshal</span><span style="color:#ABB2BF">::Copy(</span><span style="color:#E06C75">$trg.lpStart</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$out</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$out.Length</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">IO.File</span><span style="color:#ABB2BF">]::WriteAllBytes(</span><span style="color:#E06C75">$dFile</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$out</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">    if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$dlt.lpStart</span><span style="color:#56B6C2"> -ne</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">]::Zero) &#123; </span><span style="color:#E06C75">$Marshal</span><span style="color:#ABB2BF">::FreeHGlobal(</span><span style="color:#E06C75">$dlt.lpStart</span><span style="color:#ABB2BF">) &#125;</span></span><span class="line"><span style="color:#C678DD">    if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$trg.lpStart</span><span style="color:#56B6C2"> -ne</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IntPtr</span><span style="color:#ABB2BF">]::Zero) &#123; [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Win32</span><span style="color:#ABB2BF">::DeltaFree(</span><span style="color:#E06C75">$trg.lpStart</span><span style="color:#ABB2BF">) &#125;</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> G</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$DirectoryName</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">    $DeltaList</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">ordered</span><span style="color:#ABB2BF">] </span><span style="color:#C678DD">@</span><span style="color:#ABB2BF">&#123;&#125;</span></span><span class="line"><span style="color:#E06C75">    $doc</span><span style="color:#56B6C2"> =</span><span style="color:#56B6C2"> New-Object</span><span style="color:#ABB2BF"> xml</span></span><span class="line"><span style="color:#E06C75">    $doc.Load</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$DirectoryName</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> "\express.psf.cix.xml"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $child</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $doc.FirstChild.NextSibling.FirstChild</span></span><span class="line"><span style="color:#C678DD">    while</span><span style="color:#ABB2BF"> (!</span><span style="color:#E06C75">$child.LocalName.Equals</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"Files"</span><span style="color:#ABB2BF">)) &#123; </span><span style="color:#E06C75">$child</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $child.NextSibling</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#E06C75">    $FileList</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $child.ChildNodes</span></span><span class="line"><span style="color:#C678DD">    foreach</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$file</span><span style="color:#C678DD"> in</span><span style="color:#E06C75"> $FileList</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">        $fileChild</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $file.FirstChild</span></span><span class="line"><span style="color:#C678DD">        while</span><span style="color:#ABB2BF"> (!</span><span style="color:#E06C75">$fileChild.LocalName.Equals</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"Delta"</span><span style="color:#ABB2BF">)) &#123; </span><span style="color:#E06C75">$fileChild</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $fileChild.NextSibling</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#E06C75">        $deltaChild</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $fileChild.FirstChild</span></span><span class="line"><span style="color:#C678DD">        while</span><span style="color:#ABB2BF"> (!</span><span style="color:#E06C75">$deltaChild.LocalName.Equals</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"Source"</span><span style="color:#ABB2BF">)) &#123; </span><span style="color:#E06C75">$deltaChild</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $deltaChild.NextSibling</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#E06C75">        $DeltaList</span><span style="color:#ABB2BF">[</span><span style="color:#C678DD">$</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$file.id</span><span style="color:#ABB2BF">)] </span><span style="color:#56B6C2">=</span><span style="color:#C678DD"> @</span><span style="color:#ABB2BF">&#123;</span><span style="color:#E06C75">name</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $file.name</span><span style="color:#ABB2BF">; </span><span style="color:#E06C75">time</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $file.time</span><span style="color:#ABB2BF">; </span><span style="color:#E06C75">stype</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $deltaChild.type</span><span style="color:#ABB2BF">; </span><span style="color:#E06C75">offset</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $deltaChild.offset</span><span style="color:#ABB2BF">; </span><span style="color:#E06C75">length</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $deltaChild.length</span><span style="color:#ABB2BF"> &#125;;</span></span><span class="line"><span style="color:#ABB2BF">    &#125;</span></span><span class="line"><span style="color:#C678DD">    return</span><span style="color:#E06C75"> $DeltaList</span></span><span class="line"><span style="color:#ABB2BF">&#125;</span></span><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> P</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$CabFile</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$DllFile</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> 'msdelta.dll'</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:#E06C75">$DllFile</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> 'msdelta.dll'</span><span style="color:#56B6C2"> -and</span><span style="color:#ABB2BF"> (</span><span style="color:#56B6C2">Test-Path</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:SystemRoot</span><span style="color:#98C379">\System32\UpdateCompression.dll"</span><span style="color:#ABB2BF">)) &#123; </span><span style="color:#E06C75">$DllFile</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:SystemRoot</span><span style="color:#98C379">\System32\UpdateCompression.dll"</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#ABB2BF">    . Native(</span><span style="color:#E06C75">$DllFile</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$Win32</span><span style="color:#ABB2BF">::LoadLibraryW(</span><span style="color:#E06C75">$DllFile</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $DirectoryName</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $CabFile.Substring</span><span style="color:#ABB2BF">(</span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$CabFile.LastIndexOf</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">'.'</span><span style="color:#ABB2BF">))</span></span><span class="line"><span style="color:#E06C75">    $PSFFile</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $DirectoryName</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> ".psf"</span></span><span class="line"><span style="color:#D19A66">    $null</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Directory</span><span style="color:#ABB2BF">]::CreateDirectory(</span><span style="color:#E06C75">$DirectoryName</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">    $DeltaList</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> G  </span><span style="color:#E06C75">$DirectoryName</span></span><span class="line"><span style="color:#E06C75">    $PSFFileStream</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.File</span><span style="color:#ABB2BF">]::OpenRead([</span><span style="color:#C678DD">IO.Path</span><span style="color:#ABB2BF">]::GetFullPath(</span><span style="color:#E06C75">$PSFFile</span><span style="color:#ABB2BF">))</span></span><span class="line"><span style="color:#E06C75">    $cwd</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Path</span><span style="color:#ABB2BF">]::GetFullPath(</span><span style="color:#E06C75">$DirectoryName</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">    [</span><span style="color:#C678DD">Environment</span><span style="color:#ABB2BF">]::CurrentDirectory </span><span style="color:#56B6C2">=</span><span style="color:#E06C75"> $cwd</span></span><span class="line"><span style="color:#D19A66">    $null</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Directory</span><span style="color:#ABB2BF">]::CreateDirectory(</span><span style="color:#98C379">"000"</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">    foreach</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$DeltaFile</span><span style="color:#C678DD"> in</span><span style="color:#E06C75"> $DeltaList.Values</span><span style="color:#ABB2BF">) &#123;</span></span><span class="line"><span style="color:#E06C75">        $FullFileName</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $DeltaFile.name</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> (</span><span style="color:#56B6C2">Test-Path</span><span style="color:#E06C75"> $FullFileName</span><span style="color:#ABB2BF">) &#123; </span><span style="color:#C678DD">continue</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#E06C75">        $ShortFold</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Path</span><span style="color:#ABB2BF">]::GetDirectoryName(</span><span style="color:#E06C75">$FullFileName</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#E06C75">        $ShortFile</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Path</span><span style="color:#ABB2BF">]::GetFileName(</span><span style="color:#E06C75">$FullFileName</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#ABB2BF">        [</span><span style="color:#C678DD">bool</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$UseRobo</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> ((</span><span style="color:#E06C75">$cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $FullFileName</span><span style="color:#ABB2BF">).Length </span><span style="color:#56B6C2">-gt</span><span style="color:#D19A66"> 255</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">-or</span><span style="color:#ABB2BF"> ((</span><span style="color:#E06C75">$cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $ShortFold</span><span style="color:#ABB2BF">).Length </span><span style="color:#56B6C2">-gt</span><span style="color:#D19A66"> 248</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$UseRobo</span><span style="color:#56B6C2"> -eq</span><span style="color:#D19A66"> 0</span><span style="color:#56B6C2"> -and</span><span style="color:#E06C75"> $ShortFold.IndexOf</span><span style="color:#ABB2BF">(</span><span style="color:#98C379">"_"</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">-ne</span><span style="color:#D19A66"> -1</span><span style="color:#ABB2BF">) &#123; </span><span style="color:#D19A66">$null</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Directory</span><span style="color:#ABB2BF">]::CreateDirectory(</span><span style="color:#E06C75">$ShortFold</span><span style="color:#ABB2BF">) &#125;</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$UseRobo</span><span style="color:#56B6C2"> -eq</span><span style="color:#D19A66"> 0</span><span style="color:#ABB2BF">) &#123; </span><span style="color:#E06C75">$WhereFile</span><span style="color:#56B6C2"> =</span><span style="color:#E06C75"> $FullFileName</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#C678DD">        Else</span><span style="color:#ABB2BF"> &#123; </span><span style="color:#E06C75">$WhereFile</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "000\"</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $ShortFile</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#C678DD">        try</span><span style="color:#ABB2BF"> &#123; [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$PSFFileStream.Seek</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$DeltaFile.offset</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">) &#125; </span><span style="color:#C678DD">catch</span><span style="color:#ABB2BF"> &#123;&#125;</span></span><span class="line"><span style="color:#E06C75">        $Buffer</span><span style="color:#56B6C2"> =</span><span style="color:#56B6C2"> New-Object</span><span style="color:#ABB2BF"> byte[] </span><span style="color:#E06C75">$DeltaFile.length</span></span><span class="line"><span style="color:#C678DD">        try</span><span style="color:#ABB2BF"> &#123; [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$PSFFileStream.Read</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$Buffer</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$DeltaFile.length</span><span style="color:#ABB2BF">) &#125; </span><span style="color:#C678DD">catch</span><span style="color:#ABB2BF"> &#123;&#125;</span></span><span class="line"><span style="color:#E06C75">        $OutputFileStream</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.File</span><span style="color:#ABB2BF">]::Create(</span><span style="color:#E06C75">$WhereFile</span><span style="color:#ABB2BF">)</span></span><span class="line"><span style="color:#C678DD">        try</span><span style="color:#ABB2BF"> &#123; [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$OutputFileStream.Write</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75">$Buffer</span><span style="color:#ABB2BF">, </span><span style="color:#D19A66">0</span><span style="color:#ABB2BF">, </span><span style="color:#E06C75">$DeltaFile.length</span><span style="color:#ABB2BF">) &#125; </span><span style="color:#C678DD">catch</span><span style="color:#ABB2BF"> &#123;&#125;</span></span><span class="line"><span style="color:#ABB2BF">        [</span><span style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$OutputFileStream.Close</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$DeltaFile.stype</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> "PA30"</span><span style="color:#56B6C2"> -or</span><span style="color:#E06C75"> $DeltaFile.stype</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> "PA31"</span><span style="color:#ABB2BF">) &#123; ApplyDelta </span><span style="color:#E06C75">$Buffer</span><span style="color:#E06C75"> $WhereFile</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#D19A66">        $null</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.File</span><span style="color:#ABB2BF">]::SetLastWriteTimeUtc(</span><span style="color:#E06C75">$WhereFile</span><span style="color:#ABB2BF">, [</span><span style="color:#C678DD">DateTime</span><span style="color:#ABB2BF">]::FromFileTimeUtc(</span><span style="color:#E06C75">$DeltaFile.time</span><span style="color:#ABB2BF">))</span></span><span class="line"><span style="color:#C678DD">        if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$UseRobo</span><span style="color:#56B6C2"> -eq</span><span style="color:#D19A66"> 0</span><span style="color:#ABB2BF">) &#123; </span><span style="color:#C678DD">continue</span><span style="color:#ABB2BF"> &#125;</span></span><span class="line"><span style="color:#56B6C2">        Start-Process</span><span style="color:#56B6C2"> robocopy.exe</span><span style="color:#56B6C2"> -</span><span style="color:#ABB2BF">NoNewWindow </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">Wait </span><span style="color:#56B6C2">-</span><span style="color:#ABB2BF">ArgumentList (</span><span style="color:#98C379">'"'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\000'</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '"'</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> ' '</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '"'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $ShortFold</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '"'</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> ' '</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $ShortFile</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> ' /MOV /R:1 /W:1 /NS /NC /NFL /NDL /NP /NJH /NJS'</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 style="color:#C678DD">void</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$PSFFileStream.Close</span><span style="color:#ABB2BF">()</span></span><span class="line"><span style="color:#D19A66">    $null</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> [</span><span style="color:#C678DD">IO.Directory</span><span style="color:#ABB2BF">]::Delete(</span><span style="color:#98C379">"000"</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></code></pre></td></tr></tbody></table></figure><h2 id="功能解释">功能解释</h2><p>（以下内容由AI生成）这段PowerShell代码是一个完整的PSF文件（Patch Streaming Format）解包工具，核心功能是从PSF/CAB文件中提取并还原被差分压缩（delta compression）的文件。下面我会分模块、分函数详细解释每一部分的作用和核心逻辑。</p><h3 id="整体功能概述">整体功能概述</h3><p>这段代码实现了以下核心流程：</p><ol><li>解析PSF文件配套的XML配置文件（<code>express.psf.cix.xml</code>），获取差分文件的元数据（偏移、长度、压缩类型等）。</li><li>动态调用系统的差分压缩库（<code>msdelta.dll</code>/<code>UpdateCompression.dll</code>），通过P/Invoke方式封装底层Windows API。</li><li>从PSF文件中读取差分数据，调用API还原原始文件。</li><li>处理长路径问题（超过255字符），通过<code>robocopy</code>完成文件移动，最终输出完整的解包文件。</li></ol><h3 id="逐函数详细解释">逐函数详细解释</h3><h4 id="1-function-Native-DllFile-动态封装Windows差分压缩API">1. <code>function Native($DllFile)</code> - 动态封装Windows差分压缩API</h4><p>这是整个工具的核心底层函数，作用是<strong>动态创建.NET类型并封装P/Invoke调用</strong>，绕开直接声明C#/PowerShell DllImport的限制，调用系统差分压缩库的API。</p><table><thead><tr><th>代码段</th><th>核心作用</th></tr></thead><tbody><tr><td><code>$Lib = [IO.Path]::GetFileName($DllFile)</code></td><td>获取DLL文件名（如<code>msdelta.dll</code>），用于后续P/Invoke声明</td></tr><tr><td><code>$Module = [AppDomain]::CurrentDomain.DefineDynamicAssembly(...)</code></td><td>创建动态程序集/模块，作为后续定义类型的容器</td></tr><tr><td>定义<code>DI</code>结构体（<code>DELTA_INPUT</code>）</td><td>对应Windows <code>DELTA_INPUT</code>结构体，用于传递差分输入数据的指针、大小、是否可编辑</td></tr><tr><td>定义<code>DO</code>结构体（<code>DELTA_OUTPUT</code>）</td><td>对应Windows <code>DELTA_OUTPUT</code>结构体，用于接收还原后的输出数据指针和大小</td></tr><tr><td><code>$Class = $Module.DefineType('PSFE', ...)</code></td><td>定义动态类<code>PSFE</code>，用于封装P/Invoke方法</td></tr><tr><td><code>DefinePInvokeMethod('LoadLibraryW', ...)</code></td><td>封装<code>kernel32.dll</code>的<code>LoadLibraryW</code>：加载指定的差分压缩DLL</td></tr><tr><td><code>DefinePInvokeMethod('ApplyDeltaB', ...)</code></td><td>封装差分库的<code>ApplyDeltaB</code>：核心API，应用差分数据还原原始文件</td></tr><tr><td><code>DefinePInvokeMethod('DeltaFree', ...)</code></td><td>封装差分库的<code>DeltaFree</code>：释放<code>ApplyDeltaB</code>分配的内存</td></tr><tr><td><code>$Win32 = $Class.CreateType()</code></td><td>编译动态类，生成可调用的<code>Win32</code>类型</td></tr></tbody></table><p><strong>关键说明</strong>：</p><ul><li><code>1048841</code>/<code>1048961</code>是.NET类型属性的数值常量，分别代表“结构体+密封”、“类+公共”。</li><li><code>[System.Runtime.InteropServices.Marshal]</code>用于内存的分配/拷贝/释放（跨托管/非托管内存）。</li></ul><h4 id="2-function-ApplyDelta-dBuffer-dFile-应用差分数据还原文件">2. <code>function ApplyDelta($dBuffer, $dFile)</code> - 应用差分数据还原文件</h4><p>该函数是<code>ApplyDeltaB</code> API的封装，接收差分数据缓冲区和输出文件路径，完成“差分数据→原始文件”的还原。</p><p><strong>核心执行流程</strong>：</p><ol><li>创建<code>DELTA_INPUT</code>（源/差分）和<code>DELTA_OUTPUT</code>（输出）结构体实例。</li><li>分配非托管内存，将差分数据缓冲区（<code>$dBuffer</code>）拷贝到非托管内存，绑定到<code>dlt</code>（差分输入）结构体。</li><li>调用<code>Win32::ApplyDeltaB</code>：传入差分数据，还原出原始文件的内存指针和大小。</li><li>将还原后的内存数据拷贝到托管字节数组，写入目标文件（<code>$dFile</code>）。</li><li>释放所有分配的非托管内存（避免内存泄漏）。</li></ol><p><strong>关键判断</strong>：</p><ul><li><code>if ($trg.lpStart -eq [IntPtr]::Zero) &#123; return &#125;</code>：如果还原失败（输出指针为空），直接退出。</li></ul><h4 id="3-function-G-DirectoryName-解析XML配置文件提取元数据">3. <code>function G($DirectoryName)</code> - 解析XML配置文件提取元数据</h4><p>该函数的作用是读取<code>express.psf.cix.xml</code>（PSF文件的配套配置文件），解析出每个差分文件的关键元数据，并返回有序字典。</p><p><strong>核心执行流程</strong>：</p><ol><li>加载XML文件，遍历节点找到<code>&lt;Files&gt;</code>标签（包含所有文件列表）。</li><li>对每个文件节点，遍历找到<code>&lt;Delta&gt;</code>→<code>&lt;Source&gt;</code>子节点（差分数据的元数据）。</li><li>提取关键信息并存储到<code>$DeltaList</code>：<ul><li><code>name</code>：文件路径/名称</li><li><code>time</code>：文件最后修改时间</li><li><code>stype</code>：差分压缩类型（如PA30/PA31）</li><li><code>offset</code>：差分数据在PSF文件中的偏移量</li><li><code>length</code>：差分数据的长度</li></ul></li></ol><p><strong>返回值</strong>：<code>[ordered] @&#123;&#125;</code> 有序字典，键为文件ID，值为上述元数据。</p><h4 id="4-function-P-CabFile-DllFile-msdelta-dll-主解包函数">4. <code>function P($CabFile, $DllFile = 'msdelta.dll')</code> - 主解包函数</h4><p>这是整个工具的入口函数，接收CAB/PSF文件路径，整合所有子函数完成最终解包。</p><p><strong>核心执行流程</strong>：</p><pre class="mermaid">flowchart TD    A[参数初始化] --> B[加载差分压缩DLL]    B --> C[创建解包目录+解析XML元数据]    C --> D[打开PSF文件读取流]    D --> E[遍历每个差分文件]    E --> F[处理长路径问题（UseRobo）]    F --> G[读取PSF中的差分数据]    G --> H{是否PA30/PA31压缩?}    H -- 是 --> I[调用ApplyDelta还原文件]    H -- 否 --> J[直接写入文件]    I --> K[设置文件修改时间]    J --> K    K --> L{是否长路径?}    L -- 是 --> M[调用robocopy移动文件]    L -- 否 --> E    M --> N[关闭流+清理临时目录]</pre><p><strong>关键细节解释</strong>：</p><ol><li><p><strong>DLL选择逻辑</strong>：</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:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$DllFile</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> 'msdelta.dll'</span><span style="color:#56B6C2"> -and</span><span style="color:#ABB2BF"> (</span><span style="color:#56B6C2">Test-Path</span><span style="color:#98C379"> "</span><span style="color:#E06C75">$env:SystemRoot</span><span style="color:#98C379">\System32\UpdateCompression.dll"</span><span style="color:#ABB2BF">)) &#123; </span><span style="color:#E06C75">$DllFile</span><span style="color:#56B6C2"> =</span><span style="color:#98C379"> "UpdateCompression.dll"</span><span style="color:#ABB2BF"> &#125;</span></span></code></pre></td></tr></tbody></table></figure><p>优先使用<code>UpdateCompression.dll</code>（新版Windows的差分压缩库），兼容旧版的<code>msdelta.dll</code>。</p></li><li><p><strong>长路径处理（UseRobo）</strong>：</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">[</span><span style="color:#C678DD">bool</span><span style="color:#ABB2BF">]</span><span style="color:#E06C75">$UseRobo</span><span style="color:#56B6C2"> =</span><span style="color:#ABB2BF"> ((</span><span style="color:#E06C75">$cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $FullFileName</span><span style="color:#ABB2BF">).Length </span><span style="color:#56B6C2">-gt</span><span style="color:#D19A66"> 255</span><span style="color:#ABB2BF">) </span><span style="color:#56B6C2">-or</span><span style="color:#ABB2BF"> ((</span><span style="color:#E06C75">$cwd</span><span style="color:#56B6C2"> +</span><span style="color:#98C379"> '\'</span><span style="color:#56B6C2"> +</span><span style="color:#E06C75"> $ShortFold</span><span style="color:#ABB2BF">).Length </span><span style="color:#56B6C2">-gt</span><span style="color:#D19A66"> 248</span><span style="color:#ABB2BF">)</span></span></code></pre></td></tr></tbody></table></figure><p>Windows默认路径长度限制为255字符，超过时先将文件写入临时目录<code>000</code>，再通过<code>robocopy</code>移动到目标路径（<code>robocopy</code>支持长路径）。</p></li><li><p><strong>差分还原判断</strong>：</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:#C678DD">if</span><span style="color:#ABB2BF"> (</span><span style="color:#E06C75">$DeltaFile.stype</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> "PA30"</span><span style="color:#56B6C2"> -or</span><span style="color:#E06C75"> $DeltaFile.stype</span><span style="color:#56B6C2"> -eq</span><span style="color:#98C379"> "PA31"</span><span style="color:#ABB2BF">) &#123; ApplyDelta </span><span style="color:#E06C75">$Buffer</span><span style="color:#E06C75"> $WhereFile</span><span style="color:#ABB2BF"> &#125;</span></span></code></pre></td></tr></tbody></table></figure><p>仅对PA30/PA31类型的差分数据调用<code>ApplyDelta</code>还原，其他类型直接写入文件。</p></li><li><p><strong>robocopy参数说明</strong>：</p><ul><li><code>/MOV</code>：移动文件（而非复制）</li><li><code>/R:1 /W:1</code>：重试1次，每次等待1秒</li><li><code>/NS /NC /NFL</code>等：关闭冗余输出，只执行操作</li></ul></li></ol><h3 id="总结">总结</h3><p>这段代码的核心关键点如下：</p><ol><li><strong>核心能力</strong>：通过动态P/Invoke封装Windows差分压缩API，实现PSF文件的解包和差分数据还原。</li><li><strong>兼容性</strong>：适配<code>msdelta.dll</code>/<code>UpdateCompression.dll</code>，处理Windows长路径限制（通过<code>robocopy</code>）。</li><li><strong>内存管理</strong>：手动分配/释放非托管内存，避免内存泄漏；解析XML元数据精准定位差分数据位置。</li></ol><p>简单来说，这是一个针对Windows系统PSF差分压缩文件的专业解包工具，核心依赖系统自带的差分压缩库，通过PowerShell动态调用底层API完成文件还原。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;发现 W10UI 中有一段 PowerShell 代码，能够解压 PSF 格式的更新包，支持最新 PA31，通过原生 C# + Win32 方法实现，无需 PSFExtractor.exe。提取出来发现可以直接使用，特此分享。&lt;/p&gt;
&lt;h2 id=&quot;使用方法&quot;&gt;使用方法&lt;</summary>
      
    
    
    
    <category term="技术笔记" scheme="https://www.xrgzs.top/categories/%E6%8A%80%E6%9C%AF%E7%AC%94%E8%AE%B0/"/>
    
    
    <category term="Windows" scheme="https://www.xrgzs.top/tags/windows/"/>
    
    <category term="脚本" scheme="https://www.xrgzs.top/tags/%E8%84%9A%E6%9C%AC/"/>
    
    <category term="PowerShell" scheme="https://www.xrgzs.top/tags/powershell/"/>
    
  </entry>
  
</feed>
