<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>CuriosityNotes</title>
  
  
  <link href="https://curiositynotes.dev/atom.xml" rel="self"/>
  
  <link href="https://curiositynotes.dev/"/>
  <updated>2026-05-23T16:10:56.797Z</updated>
  <id>https://curiositynotes.dev/</id>
  
  <author>
    <name>gsh1209</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>QEMU 启动 Ubuntu 虚拟机</title>
    <link href="https://curiositynotes.dev/posts/a7437913/"/>
    <id>https://curiositynotes.dev/posts/a7437913/</id>
    <published>2026-05-23T15:20:40.000Z</published>
    <updated>2026-05-23T16:10:56.797Z</updated>
    
    <content type="html"><![CDATA[<div class="tag-plugin colorful note" color="cyan"><div class="body"><p><em>Assisted-by: Opencode:MiMo-V2-Pro</em></p></div></div><p>最近需要在 Debian 上临时启动虚拟机做些测试，直接用 QEMU 命令行启动参数略多，（让 AI）写了个脚本封装一下，目的就是用最少的依赖快速跑起一个 Ubuntu 虚拟机。不想使用 libvirt / virsh，轻量使用没必要搞得太复杂。</p><h2 id="1-tldr"><a class="markdownIt-Anchor" href="#1-tldr"></a> 1 TL;DR</h2><p><strong><a href="https://gist.github.com/gsh1209/fff65c0b805c459a2c32d1de2a856d02">start_vm.sh</a></strong></p><p>脚本使用 Ubuntu cloud image 作为基准镜像，在 cloud-init 中配置了一些常用设置。</p><p>使用前需要修改：</p><ul><li><code>QEMU_BIN</code> / <code>QEMU_IMG</code>：QEMU 安装路径</li><li><code>SSH_FWD_PORT</code>：SSH 转发端口，即主机上用来连接虚拟机的端口，改成没被占用的端口</li><li><code>VM_USER</code> / <code>VM_PASSWORD</code>：默认创建 unbutu 用户，密码也是 ubuntu</li><li><code>MEMORY_MB</code> / <code>VCPUS</code>：内存大小和 CPU 数量</li><li>cloud-init 里默认设置了代理环境变量：如不需要代理，把 <code>write_files</code> 里第一段配置删掉</li></ul><p>镜像需要自己下载放到 <code>baseimg/</code> 目录，脚本不会自动下载。</p><h2 id="2-使用方法"><a class="markdownIt-Anchor" href="#2-使用方法"></a> 2 使用方法</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载基础镜像</span></span><br><span class="line"><span class="built_in">mkdir</span> -p baseimg</span><br><span class="line">wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img </span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建虚拟机准备文件</span></span><br><span class="line">./start_vm.sh create</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动，大部分发行版可能需要 root 权限才能使用 kvm</span></span><br><span class="line"><span class="built_in">sudo</span> ./start_vm.sh</span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line"><span class="built_in">sudo</span> ./start_vm.sh start</span><br><span class="line"></span><br><span class="line"><span class="comment"># 其他命令</span></span><br><span class="line"><span class="built_in">sudo</span> ./start_vm.sh stop</span><br><span class="line"><span class="built_in">sudo</span> ./start_vm.sh status</span><br><span class="line"><span class="built_in">sudo</span> ./start_vm.sh restart</span><br></pre></td></tr></table></figure><p>连接到虚拟机：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># SSH</span></span><br><span class="line">ssh -p 60715 ubuntu@localhost</span><br><span class="line"></span><br><span class="line"><span class="comment"># 串口</span></span><br><span class="line">nc -U console.sock</span><br><span class="line"></span><br><span class="line"><span class="comment"># QEMU Monitor</span></span><br><span class="line">nc -U monitor.sock</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;info status&#x27;</span> | nc -U monitor.sock</span><br></pre></td></tr></table></figure><h2 id="3-功能"><a class="markdownIt-Anchor" href="#3-功能"></a> 3 功能</h2><p>脚本的工作流程分两步：先 <code>create</code> 初始化环境，然后可以 <code>start</code>/<code>stop</code>。</p><p>创建阶段主要是基于 cloud image 创建一个差分磁盘；生成 cloud-init 配置、打包 cloud-init 配置到 ISO，启动时 QEMU 会读取这个 ISO，完成用户创建、SSH 密钥注入等初始化工作。</p><h3 id="31-cloud-init-配置"><a class="markdownIt-Anchor" href="#31-cloud-init-配置"></a> 3.1 Cloud-init 配置</h3><p>主要做了这几件事：</p><ul><li>创建用户并设置密码，配置 sudo 免密</li><li>自动检测 <code>~/.ssh/</code> 下的公钥并注入，支持 ed25519、rsa、ecdsa</li><li>配置代理环境变量</li><li>禁用 IPv6，我的测试环境不需要使用 IPv6</li><li>自定义 MOTD 并禁用系统默认的 MOTD，自定义内容为显示系统资源占用</li><li>最后 touch 一个 <code>cloud-init.disabled</code> 文件，下次启动时即便加载 ISO 文件也不会再重新运行 cloud-init 流程</li></ul><h3 id="32-qemu-主要参数说明"><a class="markdownIt-Anchor" href="#32-qemu-主要参数说明"></a> 3.2 QEMU 主要参数说明</h3><p>启动命令里用到的几个主要参数：</p><p><code>-machine q35,accel=kvm</code> 使用 Q35 芯片组配合 KVM 加速。如果不支持 KVM 会退化成纯 TCG 软件模拟，速度会很慢。</p><p><code>-netdev user</code> 用户态网络，使用简单。虚拟机和主机可以互访，虚拟机也可以访问互联网。<code>hostfwd</code> 参数把宿主机端口转发到虚拟机的 22 端口。</p><p><code>-daemonize</code> 让 QEMU 后台运行，与参数 <code>--nographic</code> 是互斥的。</p><h3 id="33-生成文件说明"><a class="markdownIt-Anchor" href="#33-生成文件说明"></a> 3.3 生成文件说明</h3><p>脚本运行后会生成这些文件：</p><ul><li><code>vm-disk.qcow2</code> - 差分虚拟磁盘</li><li><code>.cloud-init.iso</code> - cloud-init 配置打包成的 ISO</li><li><code>.cloud-init/</code> - cloud-init 源文件，可以检查生成的配置是否正确</li><li><code>.vm-mac</code> - MAC 地址</li><li><code>ubuntu-vm.pid</code> - QEMU 进程 PID</li><li><code>ubuntu-vm.monitor.sock</code> / <code>ubuntu-vm.console.sock</code> - 两个 socket，用于控制和调试</li></ul><p>其中 <code>.cloud-init.iso</code> 和 <code>.cloud-init/</code> 只在 create 阶段生成，之后如果修改了 cloud-init 配置需要删掉重新 create。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;tag-plugin colorful note&quot; color=&quot;cyan&quot;&gt;&lt;div class=&quot;body&quot;&gt;&lt;p&gt;&lt;em&gt;Assisted-by: Opencode:MiMo-V2-Pro&lt;/em&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;最近需要在 </summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="QEMU" scheme="https://curiositynotes.dev/tags/QEMU/"/>
    
  </entry>
  
  <entry>
    <title>编译安装指定 GCC 版本的 RISC-V GNU 工具链</title>
    <link href="https://curiositynotes.dev/posts/5a93a7db/"/>
    <id>https://curiositynotes.dev/posts/5a93a7db/</id>
    <published>2026-04-26T12:17:35.000Z</published>
    <updated>2026-05-20T14:08:16.436Z</updated>
    
    <content type="html"><![CDATA[<div class="tag-plugin colorful note" color="cyan"><div class="body"><p>RISC-V 工具链的官方仓库 README 文档中的操作说明已经比较详细了，本文主要是记录指定的 GCC 版本来构建工具链的方法。</p><p>如果出于某些原因，需要构建基于旧版 GCC 的工具链，可以按照本文步骤操作。</p><p>实际上，这是仓库 README 中已有过说明的方法，但是我之前一直没注意到，长期以来笨拙地根据 GCC 发布日志寻找对应的日期 tag 来编译对应版本的工具链……  <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/blobcat0_0.png"/></span></p></div></div><h2 id="1-系统环境"><a class="markdownIt-Anchor" href="#1-系统环境"></a> 1 系统环境</h2><p>本文在 Debian 13 上构建 GCC 版本 15.2.0 的 RISC-V GNU 工具链，主机上的 GCC 版本为 apt 包管理器直接安装的 14.2.0 版。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ <span class="built_in">uname</span> -a</span><br><span class="line">Linux minisforum 6.12.74+deb13+1-amd64 <span class="comment">#1 SMP PREEMPT_DYNAMIC Debian 6.12.74-2 (2026-03-08) x86_64 GNU/Linux</span></span><br><span class="line">➜  ~ gcc --version</span><br><span class="line">gcc (Debian 14.2.0-19) 14.2.0</span><br><span class="line">Copyright (C) 2024 Free Software Foundation, Inc.</span><br><span class="line">This is free software; see the <span class="built_in">source</span> <span class="keyword">for</span> copying conditions.  There is NO</span><br><span class="line">warranty; not even <span class="keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</span><br></pre></td></tr></table></figure><h2 id="2-准备"><a class="markdownIt-Anchor" href="#2-准备"></a> 2 准备</h2><h3 id="21-获取工具链代码"><a class="markdownIt-Anchor" href="#21-获取工具链代码"></a> 2.1 获取工具链代码</h3><p>注意这里不需要手动获取（<code>--recursive</code>）所有 submodules，在编译时会自动按需获取。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/riscv/riscv-gnu-toolchain</span><br></pre></td></tr></table></figure><h3 id="22-安装依赖"><a class="markdownIt-Anchor" href="#22-安装依赖"></a> 2.2 安装依赖</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install autoconf automake autotools-dev curl python3 python3-pip \</span><br><span class="line">python3-tomli libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison \</span><br><span class="line">flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build \</span><br><span class="line">git cmake libglib2.0-dev libslirp-dev libncurses-dev</span><br></pre></td></tr></table></figure><h2 id="23-获取-gcc-代码"><a class="markdownIt-Anchor" href="#23-获取-gcc-代码"></a> 2.3 获取 GCC 代码</h2><p>当前的 riscv-gnu-toolchain master 分支已经把 GCC 版本更新至 15.2.0，这里主要是展示指定的 GCC 版本构建的方法。手动下载 GCC 源码，不依赖 riscv-gnu-toolchain 自动获取。</p><p>下面步骤通过 GCC 的 git 仓库获取代码，直接在 GNU 网站上下载 tar 包后解压也是可以的。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 riscv-gnu-toolchain 目录外</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/gcc-mirror/gcc.git</span><br><span class="line"><span class="built_in">cd</span> gcc</span><br><span class="line">git checkout -b branch-15.2.0</span><br><span class="line">git reset --hard releases/gcc-15.2.0</span><br></pre></td></tr></table></figure><p>此时的目录结构如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ <span class="built_in">pwd</span></span><br><span class="line">/home/ubuntu</span><br><span class="line">➜  ~ <span class="built_in">ls</span> -l</span><br><span class="line">total 8</span><br><span class="line">drwxrwxr-x 41 ubuntu ubuntu 4096 Apr 26 19:36 gcc</span><br><span class="line">drwxrwxr-x 21 ubuntu ubuntu 4096 Apr 26 18:58 riscv-gnu-toolchain</span><br></pre></td></tr></table></figure><h2 id="3-编译安装"><a class="markdownIt-Anchor" href="#3-编译安装"></a> 3 编译安装</h2><h3 id="31-配置"><a class="markdownIt-Anchor" href="#31-配置"></a> 3.1 配置</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> riscv-gnu-toolchain</span><br><span class="line"><span class="built_in">mkdir</span> build &amp;&amp; <span class="built_in">cd</span> build</span><br><span class="line"></span><br><span class="line">../configure \</span><br><span class="line">--with-arch=rv64imafdcv_zicbom_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zfh_zba_zbb_zbc_zbs_zkt \</span><br><span class="line">--with-abi=lp64d \</span><br><span class="line">--disable-multilib \</span><br><span class="line">--with-gcc-src=<span class="variable">$HOME</span>/gcc \</span><br><span class="line">--prefix=<span class="variable">$HOME</span>/.local/riscv-15.2.0</span><br></pre></td></tr></table></figure><p>其中关键的参数就是 <code>--with-gcc-src</code>，该参数指定了 GCC 代码目录，配置了该参数后编译时就会直接使用其中代码，而不会再自动获取。</p><ul><li><strong><code>--with-arch</code></strong> 参数指定了一些架构特性，应当根据目标机器支持的特性来设置。删除高级特性可以保持较高的兼容性，可以直接使用 <code>rv64imafdcv</code> 或者 <code>rv64i</code>；</li><li><strong><code>--enable-multilib/--disable-multilib</code></strong> 参数同时编译会 32-bit 支持，一般不会用到，这里直接禁用；</li><li><strong><code>--prefix</code></strong> 参数指定工具链安装路径。</li></ul><h3 id="32-编译"><a class="markdownIt-Anchor" href="#32-编译"></a> 3.2 编译</h3><blockquote><mark class="tag-plugin colorful mark" color="yellow">正如前面提到，编译时 riscv-gnu-toolchain 会自动获取所需的软件包，此时需要良好的网络连接。</mark></blockquote><p>编译后可执行文件会自动安装至之前配置的安装目录中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用 newlib 作为 C 库编译</span></span><br><span class="line">make -j$(<span class="built_in">nproc</span>)</span><br><span class="line"><span class="comment"># 使用 glibc 作为 C 库编译</span></span><br><span class="line">make linux -j$(<span class="built_in">nproc</span>)</span><br></pre></td></tr></table></figure><p>关于选择 newlib 还是 glibc 这里不做展开，简单来说就是 newlib 一般用来编译裸机程序、嵌入式程序（固件、bootloader等），其工具链的前缀是 <code>riscv64-unknown-elf-</code>；glibc 有完整 POSIX 支持，一般用来编译 Linux 用户空间程序和 Kernel（Kernel 虽然是“裸机程序”，但也包含一些共享库组件），工具链前缀为 <code>riscv64-unknown-linux-gnu-</code>。</p><p>编译中如果出现错误，可以删除 build 目录后重新配置、编译。</p><h3 id="33-设置环境变量"><a class="markdownIt-Anchor" href="#33-设置环境变量"></a> 3.3 设置环境变量</h3><p>把安装目录加入到 <code>PATH</code> 环境变量中，可以在任意目录中使用编译的工具链。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;<span class="variable">$HOME</span>/.local/riscv-15.2.0/bin:<span class="variable">$PATH</span>&quot;</span></span><br></pre></td></tr></table></figure><p>查看编译安装结果：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">➜  ~ riscv64-unknown-elf-gcc --version</span><br><span class="line">riscv64-unknown-elf-gcc (g5115c7e447f) 15.2.0</span><br><span class="line">Copyright (C) 2025 Free Software Foundation, Inc.</span><br><span class="line">This is free software; see the <span class="built_in">source</span> <span class="keyword">for</span> copying conditions.  There is NO</span><br><span class="line">warranty; not even <span class="keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</span><br><span class="line"></span><br><span class="line">➜  ~ riscv64-unknown-linux-gnu-gcc --version</span><br><span class="line">riscv64-unknown-linux-gnu-gcc (g5115c7e447f) 15.2.0</span><br><span class="line">Copyright (C) 2025 Free Software Foundation, Inc.</span><br><span class="line">This is free software; see the <span class="built_in">source</span> <span class="keyword">for</span> copying conditions.  There is NO</span><br><span class="line">warranty; not even <span class="keyword">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;tag-plugin colorful note&quot; color=&quot;cyan&quot;&gt;&lt;div class=&quot;body&quot;&gt;&lt;p&gt;RISC-V 工具链的官方仓库 README 文档中的操作说明已经比较详细了，本文主要是记录指定的 GCC 版本来构建工具链的方法。&lt;/</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Debian" scheme="https://curiositynotes.dev/tags/Debian/"/>
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="RISC-V" scheme="https://curiositynotes.dev/tags/RISC-V/"/>
    
  </entry>
  
  <entry>
    <title>Milk-V Duo S 点灯</title>
    <link href="https://curiositynotes.dev/posts/40a001bb/"/>
    <id>https://curiositynotes.dev/posts/40a001bb/</id>
    <published>2026-03-22T13:47:20.000Z</published>
    <updated>2026-05-20T14:08:16.436Z</updated>
    
    <content type="html"><![CDATA[<p>最近想找个 RISC-V 小板子玩一玩，搜寻一番发现大多都是搭载了多核异构 SoC 的嵌入式开发板，入手了一块 Milk-V Duo S 开发板。</p><blockquote><p>其实 SpacemiT K1 的开发板更具吸引力，但是价格小贵 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/blobcat0_0.png"/></span></p></blockquote><p>板子的基本使用方法<a href="https://milkv.io/zh/docs/duo/getting-started/duos">官方文档</a>中写的比较详细，我这里主要记录一下使用 SDK 自行构建 SD 卡镜像遇到的一些问题；以及 Linux 启动后，如何扩展根分区和增加可用内存。</p><h2 id="1-sdkv2编译"><a class="markdownIt-Anchor" href="#1-sdkv2编译"></a> 1 SDK（V2）编译</h2><p>官方 SDK 基于 buildroot 构建，Dou S 推荐使用 V2 版本，<a href="https://github.com/milkv-duo/duo-buildroot-sdk-v2">链接</a>。</p><p>整个 SDK 工程十分复杂，塞进了 OpenSBI，U-Boot，Linux kernel，Buildroot 以及一些定制化硬件的驱动模块。同时给这个复杂的工程设计了一套复杂的构建流程，由各种 shell 脚本、python 脚本完成最终镜像的构建。</p><p>启动流程看起来是典型 FW_DYNAMIC 流程，ROM 固件跳转 OpenSBI，OpenSBI 引导 U-Boot（进入 S-mode），U-Boot 引导 Linux kernel，Buildroot 作为根文件系统。代码中也用了一些 ARM ATF 的术语，还没有细看。</p><p>总之，初步感觉以这套 SDK 的复杂（混乱）程度，对希望搞定制化开发的朋友算不上友好，初看起来有些“牵一发而动全身”的样子。</p><p>以下记录构建 Milk-V Duo S 无 WiFi/BT 版 SD 卡镜像遇到的一些小问题和解决方法。</p><h3 id="11-编译过程记录"><a class="markdownIt-Anchor" href="#11-编译过程记录"></a> 1.1 编译过程记录</h3><h3 id="111-sdk-版本信息"><a class="markdownIt-Anchor" href="#111-sdk-版本信息"></a> 1.1.1 SDK 版本信息</h3><p>SDK链接: <a href="https://github.com/milkv-duo/duo-buildroot-sdk-v2">https://github.com/milkv-duo/duo-buildroot-sdk-v2</a></p><p>main 分支：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">commit 6f8962c394dd0a05729abb089f0feb7d5cc4aa5e (HEAD -&gt; main, origin/main, origin/HEAD)</span><br><span class="line">Merge: 0545e9dc6 e7b0c7933</span><br><span class="line">Author: carbon &lt;carbon@milkv.io&gt;</span><br><span class="line">Date:   Tue Nov 4 13:38:24 2025 +0800</span><br><span class="line"></span><br><span class="line">    Merge branch <span class="string">&#x27;develop&#x27;</span></span><br></pre></td></tr></table></figure><p>develop 分支：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">commit ad920f839277e566d9068989ca8d737483a4677d (HEAD -&gt; develop, origin/develop)</span><br><span class="line">Author: carbon &lt;carbon@milkv.io&gt;</span><br><span class="line">Date:   Tue Dec 30 17:35:00 2025 +0800</span><br><span class="line"></span><br><span class="line">    cvi_mpi: support st7701sn 2 lane lcd</span><br><span class="line"></span><br><span class="line">    <span class="built_in">test</span> <span class="built_in">command</span>:</span><br><span class="line">    sample_panel --panel=ST7701SN</span><br><span class="line">    devmem 0x0a088094 32 0x0701000a</span><br><span class="line"></span><br><span class="line">    Signed-off-by: carbon &lt;carbon@milkv.io&gt;</span><br></pre></td></tr></table></figure><p>develop 分支看起来更新一点，也测试了一下，以下编译问题同时存在于 main 和 develop 分支。</p><p>其他问题参考文档: <a href="https://milkv.io/zh/docs/duo/getting-started/buildroot-sdk">https://milkv.io/zh/docs/duo/getting-started/buildroot-sdk</a></p><p>值得一提的是 Milk-V 开发板的文档比较详细，论坛社区讨论也比较活跃。能在里面找到不少有参考价值的讨论。</p><h3 id="112-主机环境"><a class="markdownIt-Anchor" href="#112-主机环境"></a> 1.1.2 主机环境</h3><p>我使用 Debian 13 作为 Host 编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@debian ~ $ <span class="built_in">uname</span> -a</span><br><span class="line">Linux debian 6.12.57+deb13-amd64 <span class="comment">#1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1 (2025-11-05) x86_64 GNU/Linux</span></span><br><span class="line">ubuntu@debian ~ $ <span class="built_in">cat</span> /etc/os-release</span><br><span class="line">PRETTY_NAME=<span class="string">&quot;Debian GNU/Linux 13 (trixie)&quot;</span></span><br><span class="line">NAME=<span class="string">&quot;Debian GNU/Linux&quot;</span></span><br><span class="line">VERSION_ID=<span class="string">&quot;13&quot;</span></span><br><span class="line">VERSION=<span class="string">&quot;13 (trixie)&quot;</span></span><br><span class="line">VERSION_CODENAME=trixie</span><br><span class="line">DEBIAN_VERSION_FULL=13.2</span><br><span class="line">ID=debian</span><br><span class="line">HOME_URL=<span class="string">&quot;https://www.debian.org/&quot;</span></span><br><span class="line">SUPPORT_URL=<span class="string">&quot;https://www.debian.org/support&quot;</span></span><br><span class="line">BUG_REPORT_URL=<span class="string">&quot;https://bugs.debian.org/&quot;</span></span><br></pre></td></tr></table></figure><p>初次编译时，<code>./build.sh</code> 编译脚本会自动下载工具链和 Buildroot 软件包，网络要顺畅。</p><p>工具链：<a href="https://github.com/milkv-duo/host-tools.git">https://github.com/milkv-duo/host-tools.git</a></p><p>若工具链自动下载失败可以手动 git clone 至 SDK 根目录。Buildroot 软件包官方文档中也有离线下载方式可供参考。</p><p>官方文档中给出的安装命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y pkg-config build-essential ninja-build automake autoconf libtool \</span><br><span class="line">wget curl git gcc libssl-dev bc slib squashfs-tools android-sdk-libsparse-utils \</span><br><span class="line">jq python3-distutils scons parallel tree python3-dev python3-pip device-tree-compiler \</span><br><span class="line">ssh cpio fakeroot libncurses5 flex bison libncurses5-dev genext2fs rsync unzip \</span><br><span class="line">dosfstools mtools tcl openssh-client cmake expect python-is-python3</span><br><span class="line"><span class="built_in">sudo</span> pip install jinja2</span><br></pre></td></tr></table></figure><p>在我的环境上需要略作调整，删去 <code>libncurses5</code> 和 <code>python3-distutils</code>，<code>jinja2</code> 使用 apt 而不是 pip 安装，还需要额外安装工具 <code>xxd</code>。修改过后的安装命令为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install -y pkg-config build-essential ninja-build automake autoconf libtool \</span><br><span class="line">wget curl git gcc libssl-dev bc slib squashfs-tools android-sdk-libsparse-utils jq \</span><br><span class="line">scons parallel tree python3-dev python3-pip device-tree-compiler ssh cpio fakeroot \</span><br><span class="line">flex bison libncurses5-dev genext2fs rsync unzip dosfstools mtools tcl openssh-client \</span><br><span class="line">cmake expect python-is-python3 xxd python3-jinja2</span><br></pre></td></tr></table></figure><p>至此准备工作结束，可以在 SDK 目录下直接使用命令 <code>./build.sh milkv-duos-musl-riscv64-sd</code> 构建 SD 卡镜像，也可以用命令 <code>./build lunch</code> 交互式地选择要使用的工具链和目标开发板。一切顺利的话 SD 镜像将会生成在 <code>out/</code> 目录中。</p><h3 id="113-错误解决"><a class="markdownIt-Anchor" href="#113-错误解决"></a> 1.1.3 错误解决</h3><blockquote><p>目前大部分编译报错都可以在 SDK github issues 中找到线索。</p><p>实在无法解决就使用 Docker 方式编译。</p><mark class="tag-plugin colorful mark" color="yellow">本节最后附有解决所有问题的 patch。</mark></blockquote><h4 id="问题-1"><a class="markdownIt-Anchor" href="#问题-1"></a> 问题 1</h4><p>报错信息（linux/cvi_type.h: No such file or directory）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">ake[2]: Entering directory <span class="string">&#x27;/home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/modules/sys&#x27;</span></span><br><span class="line">ov_ov5647/ov5647_cmos.c:8:10: fatal error: linux/cvi_type.h: No such file or directory</span><br><span class="line">    8 | <span class="comment">#include &lt;linux/cvi_type.h&gt;</span></span><br><span class="line">      |          ^~~~~~~~~~~~~~~~~~</span><br><span class="line">compilation terminated.</span><br><span class="line">gcore_gc2083/gc2083_cmos.c:7:10: fatal error: linux/cvi_type.h: No such file or directory</span><br><span class="line">    7 | <span class="comment">#include &lt;linux/cvi_type.h&gt;</span></span><br><span class="line">      |          ^~~~~~~~~~~~~~~~~~</span><br><span class="line">compilation terminated.</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1499/964;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/260331011044_284f2dd3dc04ad52.webp" data-src="https://cos.curiositynotes.dev/imgs/260331011044_284f2dd3dc04ad52.webp" alt="编译报错1" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">编译报错1</span></div></div><p>这个问题有些奇怪，原本想改为单线程编译看下到底哪里出了问题，结果单线程编译反而能正常生成镜像，猜测应该是脚本编排的流程存在一些问题，导致并行编译时某些依赖文件尚未就绪。后来发现 <a href="https://github.com/milkv-duo/duo-buildroot-sdk-v2/issues/20">issues#20</a> 中也有人提到了这个现象。具体的修改位置在 <code>build/envsetup_milkv.sh</code> 文件中的 <code>make all -j$(nproc)</code>，改为 <code>make all -j1</code> 即可。</p><h4 id="问题-2"><a class="markdownIt-Anchor" href="#问题-2"></a> 问题 2</h4><p>报错信息（undefined reference to `pcm_read’）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"> Run build_pqtool_server() <span class="keyword">function</span></span><br><span class="line">[riscv64-unknown-linux-musl-g++] cvi_streamer.o</span><br><span class="line">[riscv64-unknown-linux-musl-g++] main.o</span><br><span class="line">[riscv64-unknown-linux-musl-gcc] stream_porting.o</span><br><span class="line">/home/ronnie/code/duo-buildroot-sdk-v2/host-tools/gcc/riscv64-linux-musl-x86_64/bin/../lib/gcc/riscv64-unknown-linux-musl/10.2.0/../../../../riscv64-unknown-linux-musl/bin/ld: /home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/lib/libcvi_audio.so: undefined reference to `pcm_read<span class="string">&#x27;</span></span><br><span class="line"><span class="string">/home/ronnie/code/duo-buildroot-sdk-v2/host-tools/gcc/riscv64-linux-musl-x86_64/bin/../lib/gcc/riscv64-unknown-linux-musl/10.2.0/../../../../riscv64-unknown-linux-musl/bin/ld: /home/ronnie/code/duo-buildroot-sdk-v2/cvi_mpi/lib/libcvi_audio.so: undefined reference to `pcm_open&#x27;</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1585/705;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/260331011225_d642fd03c1cde8dc.webp" data-src="https://cos.curiositynotes.dev/imgs/260331011225_d642fd03c1cde8dc.webp" alt="编译报错2" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">编译报错2</span></div></div><p>参考 <a href="https://github.com/milkv-duo/duo-buildroot-sdk-v2/issues/36#issuecomment-3009124606">issues#36</a>，添加一个库解决。或者，参考 <a href="https://github.com/milkv-duo/duo-buildroot-sdk-v2/issues/6#issuecomment-2903872850">issues#6</a>，直接在 <code>build/envsetup_milkv.sh</code> 中注释这个部分的编译，即注释这一行 <code>build_pqtool_server || return $?</code></p><p>两种方法均测试可用，目前使用添加库的方法解决。</p><h3 id="114-关闭-wifibt-驱动加载"><a class="markdownIt-Anchor" href="#114-关闭-wifibt-驱动加载"></a> 1.1.4 关闭 WiFi/BT 驱动加载</h3><p>我买的是不带 WiFi 功能的板子，WiFi 芯片焊盘是空焊的。SDK 中的启动脚本默认会加载驱动，内核日志中会有一条找不到设备的报错，此处修改启动脚本直接不加载驱动。</p><p>修改 <code>device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh</code> 文件，注释掉文件末尾处的加载<code>aic8800_*.ko</code>模块的命令。</p><blockquote><p>其实也尝试过修改内核 defconfig 不编译对应的驱动，没有生效，原因待查。</p></blockquote><h3 id="115-小结"><a class="markdownIt-Anchor" href="#115-小结"></a> 1.1.5 小结</h3><p>以上修改总结</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">diff --git a/build/envsetup_milkv.sh b/build/envsetup_milkv.sh</span><br><span class="line">index d9a2e104f..b774c2f88 100644</span><br><span class="line">--- a/build/envsetup_milkv.sh</span><br><span class="line">+++ b/build/envsetup_milkv.sh</span><br><span class="line">@@ -239,7 +239,7 @@ function build_middleware()</span><br><span class="line">   make &quot;$ROOTFS_DIR&quot; || return &quot;$?&quot;</span><br><span class="line"></span><br><span class="line">   pushd &quot;$MW_PATH&quot;</span><br><span class="line">-  make all -j$(nproc)</span><br><span class="line">+  make all -j1</span><br><span class="line">   test $? -ne 0 &amp;&amp; print_notice &quot;build middleware failed !!&quot; &amp;&amp; popd &amp;&amp; return 1</span><br><span class="line">   make install DESTDIR=&quot;$SYSTEM_OUT_DIR&quot; || return &quot;$?&quot;</span><br><span class="line">   popd</span><br><span class="line">diff --git a/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile b/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile</span><br><span class="line">index 62e95b178..528d9414a 100644</span><br><span class="line">--- a/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile</span><br><span class="line">+++ b/cvi_mpi/modules/isp/cv181x/isp-tool-daemon/isp_daemon_tool/Makefile</span><br><span class="line">@@ -64,6 +64,7 @@ LIBS += -lcvi_ispd2</span><br><span class="line"> LIBS += -lraw_dump</span><br><span class="line"> LIBS += -lcvi_dnvqe -lcvi_ssp2 -lteaisp</span><br><span class="line"> LIBS += -lcviruntime -lcvikernel</span><br><span class="line">+LIBS += -ltinyalsa</span><br><span class="line"></span><br><span class="line"> LOCAL_CFLAGS = $(DEFS) $(INCS)</span><br><span class="line"> LOCAL_CPPFLAGS = -fno-use-cxa-atexit</span><br><span class="line">diff --git a/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh b/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh</span><br><span class="line">index e155c6e37..e19465950 100755</span><br><span class="line">--- a/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh</span><br><span class="line">+++ b/device/generic/rootfs_overlay/duos/mnt/system/duo-init.sh</span><br><span class="line">@@ -22,14 +22,13 @@ gpio_b17=465</span><br><span class="line"> set_gpio $&#123;gpio_b17&#125; 0</span><br><span class="line"></span><br><span class="line"> # Host Wake BT</span><br><span class="line">-host_wake_bt=362</span><br><span class="line">-set_gpio $&#123;host_wake_bt&#125; 1</span><br><span class="line">+# host_wake_bt=362</span><br><span class="line">+# set_gpio $&#123;host_wake_bt&#125; 1</span><br><span class="line"></span><br><span class="line"> # WIFI/BT Module</span><br><span class="line">-insmod /mnt/system/ko/aic8800_bsp.ko</span><br><span class="line">-sleep 0.5</span><br><span class="line">-insmod /mnt/system/ko/aic8800_fdrv.ko</span><br><span class="line">+# insmod /mnt/system/ko/aic8800_bsp.ko</span><br><span class="line">+# sleep 0.5</span><br><span class="line">+# insmod /mnt/system/ko/aic8800_fdrv.ko</span><br><span class="line"></span><br><span class="line"> # Insmod PWM Module</span><br><span class="line"> insmod /mnt/system/ko/cv181x_pwm.ko</span><br></pre></td></tr></table></figure><h2 id="2-根分区扩容"><a class="markdownIt-Anchor" href="#2-根分区扩容"></a> 2 根分区扩容</h2><p>Duo S 镜像文件写入到 SD 卡后，根分区 <code>/</code> 只使用了 768M 空间，这与 SD 卡的容量大小无关，是 SDK 中生成镜像文件时设置的（<code>device/milkv-duos-musl-riscv64-sd/genimage.cfg</code>）。将根分区扩容至占满 SD 卡容量后，板上系统能使用更大的容量，SD 卡的空间也不至于白白浪费。</p><p>以下方法选择其一即可。</p><h3 id="21-使用-parted-命令扩容"><a class="markdownIt-Anchor" href="#21-使用-parted-命令扩容"></a> 2.1 使用 parted 命令扩容</h3><p>官方 SDK （duo-buildroot-sdk-v2）中默认编译了 parted 工具，扩容操作方便得多，按以下命令操作：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看磁盘信息</span></span><br><span class="line">lsblk</span><br><span class="line"><span class="built_in">df</span> -hT</span><br><span class="line"><span class="comment"># 对指定分区扩容</span></span><br><span class="line">parted /dev/mmcblk0 resizepart 3 100%</span><br><span class="line"><span class="comment"># 扩展文件系统</span></span><br><span class="line">resize2fs /dev/mmcblk0p3</span><br><span class="line"><span class="comment"># 查看扩展结果</span></span><br><span class="line"><span class="built_in">df</span> -hT</span><br></pre></td></tr></table></figure><p>设置过程图示：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1319/972;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/260331011310_2dabcf27e6b76081.webp" data-src="https://cos.curiositynotes.dev/imgs/260331011310_2dabcf27e6b76081.webp" alt="用parted扩容根分区" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">用parted扩容根分区</span></div></div><h3 id="22-使用-fdisk-命令扩容"><a class="markdownIt-Anchor" href="#22-使用-fdisk-命令扩容"></a> 2.2 使用 fdisk 命令扩容</h3><p>如果 parted 工具不可用，还可选择 fdisk 工具“古法”扩容。扩容的原理是删除根分区，在<strong>原位置</strong>上新建一个占满 SD 卡容量的新分区。操作的关键在于一定要原地新建，删除的分区和新建的分区的<mark class="tag-plugin colorful mark" color="yellow">起始扇区号必须一致</mark>。这样只修改了分区的信息，磁盘上的内容并没有改动。按以下命令操作：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看磁盘信息</span></span><br><span class="line">lsblk</span><br><span class="line"><span class="built_in">df</span> -hT</span><br><span class="line"><span class="comment"># 对指定分区扩容</span></span><br><span class="line">fdisk /dev/mmcblk0</span><br><span class="line"></span><br><span class="line"><span class="comment">### 以下命令在 fdisk 交互界面中输入</span></span><br><span class="line">p              <span class="comment"># 打印分区表，确认根分区的 Start 值</span></span><br><span class="line">d              <span class="comment"># 删除分区</span></span><br><span class="line">3              <span class="comment"># 选择删除第 3 个分区 (根分区)</span></span><br><span class="line"></span><br><span class="line">n              <span class="comment"># 新建分区</span></span><br><span class="line">p              <span class="comment"># 主分区 (primary)</span></span><br><span class="line">3              <span class="comment"># 分区号 3</span></span><br><span class="line">266241         <span class="comment"># 起始扇区：必须与原来一致</span></span><br><span class="line">&lt;回车&gt;         <span class="comment"># 结束扇区：直接回车使用默认最大值（占用所有剩余空间）</span></span><br><span class="line"></span><br><span class="line">n              <span class="comment"># 保持分区类型</span></span><br><span class="line">&lt;回车&gt;         <span class="comment"># 默认 ext4</span></span><br><span class="line"></span><br><span class="line">w              <span class="comment"># 写入分区表并退出</span></span><br><span class="line"><span class="comment">### fdisk 交互界面退出，回到 shell</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 扩展文件系统</span></span><br><span class="line">resize2fs /dev/mmcblk0p3</span><br><span class="line"><span class="comment"># 查看扩展结果</span></span><br><span class="line"><span class="built_in">df</span> -hT</span><br></pre></td></tr></table></figure><p>设置过程图示：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1588/1330;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/260331011347_ea70d4ddc549da5c.webp" data-src="https://cos.curiositynotes.dev/imgs/260331011347_ea70d4ddc549da5c.webp" alt="用fdisk扩容根分区" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">用fdisk扩容根分区</span></div></div><p>注意图中只展示了扩容分区的步骤，后续仍需要使用 <code>resize2fs</code> 命令扩展文件系统。</p><h2 id="3-释放-ion-内存"><a class="markdownIt-Anchor" href="#3-释放-ion-内存"></a> 3 释放 ION 内存</h2><p>Duo S 开发板上 Linux 成功启动后，发现内存只有 316M 可用（板载内存 512M），搜索了一下发现 SDK 中预留了一部分内存（170M）用作 ION 内存，用于 Multimedia buffer，应该是给 SoC 内部的多媒体处理模块用的。我对这部分功能不感兴趣，尝试修改配置将这部分内存释放出来。</p><p>在 SDK 中搜索到 <code>build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</code> 文件中定义了各部分内存的分配，直接将 ION 部分设置为 0，修改如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line">diff --git a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">index d18d314ca..10c1b4cfc 100644</span><br><span class="line">--- a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">+++ b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">@@ -40,10 +40,10 @@ class MemoryMap:</span><br><span class="line">     <span class="comment"># =================</span></span><br><span class="line">     <span class="comment"># Multimedia buffer. Used by u-boot/kernel/FreeRTOS</span></span><br><span class="line">     <span class="comment"># =================</span></span><br><span class="line">-    ION_SIZE = 170 * SIZE_1M</span><br><span class="line">-    H26X_BITSTREAM_SIZE = 2 * SIZE_1M</span><br><span class="line">+    ION_SIZE = 0 * SIZE_1M</span><br><span class="line">+    H26X_BITSTREAM_SIZE = 0 * SIZE_1M</span><br><span class="line">     H26X_ENC_BUFF_SIZE = 0</span><br><span class="line">-    ISP_MEM_BASE_SIZE = 20 * SIZE_1M</span><br><span class="line">+    ISP_MEM_BASE_SIZE = 0 * SIZE_1M</span><br><span class="line">     FREERTOS_RESERVED_ION_SIZE = H26X_BITSTREAM_SIZE + H26X_ENC_BUFF_SIZE + ISP_MEM_BASE_SIZE</span><br><span class="line"></span><br><span class="line">     <span class="comment"># ION after FreeRTOS</span></span><br></pre></td></tr></table></figure><p>但是这样修改会导致启动时出现 OOPS：</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><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></pre></td><td class="code"><pre><span class="line">[    3.438039] cv181x-cooling cv181x_cooling: Cooling device registered: cv181x_cooling</span><br><span class="line">[    3.468018] [INFO] Register SBM IRQ ###################################</span><br><span class="line">[    3.468045] [INFO] pvctx-&gt;s_sbm_irq = 37</span><br><span class="line">[    3.483018] jpu ctrl reg pa = 0xb030000, va = (____ptrval____), size = 256</span><br><span class="line">[    3.494692] end jpu_init result = 0x0</span><br><span class="line">[    3.616765] cvi_vc_drv_init result = 0x0</span><br><span class="line">[    3.651499] Summary:</span><br><span class="line">[    3.653825] [0] carveout heap size:0 bytes, used:0 bytes</span><br><span class="line">[    3.659700] usage rate:-1%, memory usage peak 0 bytes</span><br><span class="line">[    3.665266]</span><br><span class="line">[    3.665266] Details:</span><br><span class="line">[    3.665266]          heap_id   alloc_buf_size         phy_addr         kmap_cnt      buffer name</span><br><span class="line">[    3.678606]</span><br><span class="line">[    3.680166] ion allocated len=0x1000 failed</span><br><span class="line">[    3.684911] Summary:</span><br><span class="line">[    3.687184] [0] carveout heap size:0 bytes, used:0 bytes</span><br><span class="line">[    3.693033] usage rate:-1%, memory usage peak 0 bytes</span><br><span class="line">[    3.698511]</span><br><span class="line">[    3.698511] Details:</span><br><span class="line">[    3.698511]          heap_id   alloc_buf_size         phy_addr         kmap_cnt      buffer name</span><br><span class="line">[    3.711787]</span><br><span class="line">[    3.713588] ion allocated len=0x1000 failed</span><br><span class="line">[    3.718357] Summary:</span><br><span class="line">[    3.720906] [0] carveout heap size:0 bytes, used:0 bytes</span><br><span class="line">[    3.726650] usage rate:-1%, memory usage peak 0 bytes</span><br><span class="line">[    3.732127]</span><br><span class="line">[    3.732127] Details:</span><br><span class="line">[    3.732127]          heap_id   alloc_buf_size         phy_addr         kmap_cnt      buffer name</span><br><span class="line">[    3.745401]</span><br><span class="line">[    3.746960] ion allocated len=0x4010 failed</span><br><span class="line">[    3.751691] pstStCandiCornerCtrl-&gt;stMem.u64PhyAddr can&#x27;t be 0!</span><br><span class="line">[    3.757986] sys_ctx_mem_get() can&#x27;t find it</span><br><span class="line">[    3.762567] dmabuf_fd get failed, addr:0x14</span><br><span class="line">[    3.767141] sys_ctx_mem_get() can&#x27;t find it</span><br><span class="line">[    3.771719] dmabuf_fd get failed, addr:0xffffffe002a28294</span><br><span class="line">[    3.777584] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000078</span><br><span class="line">[    3.786925] Oops [#1]</span><br><span class="line">[    3.789281] Modules linked in: cv181x_ive(FO+) cvi_vc_driver(FO) cv181x_jpeg(FO) cv181x_vcodec(FO) cv181x_tpu(FO) cv181x_clock_cooling(FO) cv181x_rgn(FO) cv181x_mipi_tx(FO) cv181x_vo(FO) cv181x_dwa(FO) cv181x_vpss(FO) cv181x_vi(FO) snsr_i2c(FO) cvi_mipi_rx(FO) cv181x_fast_image(FO) cv181x_rtos_cmdqu(FO) cv181x_base(FO) cv181x_sys(FO)</span><br><span class="line">[    3.819888] CPU: 0 PID: 219 Comm: insmod Tainted: GF          O      5.10.4-tag- #1</span><br><span class="line">[    3.827798] epc: ffffffdf809c909a ra : ffffffdf809c9066 sp : ffffffe0027e3a80</span><br><span class="line">[    3.835167]  gp : ffffffe00098f2d8 tp : ffffffe0015bdc40 t0 : 0000000000000000</span><br><span class="line">[    3.842625]  t1 : ffffffdf809d4600 t2 : 0000000000000000 s0 : 0000000000000000</span><br><span class="line">[    3.850085]  s1 : 0000000000000000 a0 : 0000000000000000 a1 : 0000000000000000</span><br><span class="line">[    3.857543]  a2 : ffffffdf809d4608 a3 : 0000000000000000 a4 : ffffffffffffffd0</span><br><span class="line">[    3.865003]  a5 : 0000000000000000 a6 : 0000000000000007 a7 : ffffffdf809d4608</span><br><span class="line">[    3.872462]  s2 : ffffffe000b68400 s3 : ffffffe000990088 s4 : ffffffdf809cc800</span><br><span class="line">[    3.879921]  s5 : 0000000000000015 s6 : ffffffe000991068 s7 : ffffffe00096fe08</span><br><span class="line">[    3.887381]  s8 : 0000003fe43439e0 s9 : 0000003fffa38da0 s10: 0000000000000014</span><br><span class="line">[    3.894839]  s11: 0000000000000001 t3 : 0000000000000000 t4 : 0000000038000000</span><br><span class="line">[    3.902297]  t5 : 0000000030000000 t6 : 0000000066000000</span><br><span class="line">[    3.907785] status: 0000000200000120 badaddr: 0000000000000078 cause: 000000000000000d</span><br><span class="line">[    3.915959] Call Trace:</span><br><span class="line">[    3.918519] [&lt;ffffffdf809c909a&gt;] _sys_ion_free+0x68/0x196 [cv181x_sys]</span><br><span class="line">[    3.925398] [&lt;ffffffdf80d89b16&gt;] stcandicorner_workaround+0xb0/0xce [cv181x_ive]</span><br><span class="line">[    3.933060] [&lt;ffffffe0002f5752&gt;] proc_alloc_inum+0x1e/0x38</span><br><span class="line">[    3.938732] [&lt;ffffffe0002f5854&gt;] proc_register+0x14/0xe0</span><br><span class="line">[    3.944347] [&lt;ffffffdf80d7b6de&gt;] cvi_ive_probe+0x1e4/0x1ea [cv181x_ive]</span><br><span class="line">[    3.951195] [&lt;ffffffe000408dee&gt;] platform_drv_probe+0x24/0x54</span><br><span class="line">[    3.957143] [&lt;ffffffe000407a44&gt;] really_probe+0x210/0x33e</span><br><span class="line">[    3.962728] [&lt;ffffffe000407f22&gt;] device_driver_attach+0x2c/0x48</span><br><span class="line">[    3.968848] [&lt;ffffffe000408036&gt;] __driver_attach+0xf8/0xfe</span><br><span class="line">[    3.974519] [&lt;ffffffe000407f3a&gt;] device_driver_attach+0x44/0x48</span><br><span class="line">[    3.980639] [&lt;ffffffe00040603a&gt;] bus_for_each_dev+0x46/0x76</span><br><span class="line">[    3.986402] [&lt;ffffffe000406e70&gt;] bus_add_driver+0x15a/0x186</span><br><span class="line">[    3.992170] [&lt;ffffffe000408494&gt;] driver_register+0x7c/0xa2</span><br><span class="line">[    3.997852] [&lt;ffffffe000227104&gt;] do_one_initcall+0x58/0xf2</span><br><span class="line">[    4.003525] [&lt;ffffffe000269734&gt;] do_init_module+0x3e/0x174</span><br><span class="line">[    4.009195] [&lt;ffffffe00026b1da&gt;] __do_sys_finit_module+0x92/0xb6</span><br><span class="line">[    4.015407] [&lt;ffffffe00026b1fc&gt;] __do_sys_finit_module+0xb4/0xb6</span><br><span class="line">[    4.021613] [&lt;ffffffe00022805e&gt;] check_syscall_nr+0x1e/0x22</span><br><span class="line">[    4.031228] ---[ end trace b61bbf8cb217d7b4 ]---</span><br><span class="line">Segmentation fault</span><br><span class="line">[    4.049898] sh (194): drop_caches: 3</span><br><span class="line">Starting app...</span><br><span class="line"></span><br><span class="line">[root@milkv-duo]~# </span><br></pre></td></tr></table></figure><p>看起来应该是在初始化这些 SoC 内部模块时要分配 ION 内存，但已经没有 ION 内存可用，分配失败导致的错误。</p><p>按道理应当把这些模块的初始化代码删除或禁用，不过我暂时不清楚这些模块具体是做什么用的，甚至不知道名字，暂时还是保留一部分 ION 内存作为 workaround 消除这个错误吧。</p><p>从错误日志中可以看到，类似 <code>ion allocated len=0x1000 failed</code> 的日志打印了三次，猜测共做了三次分配，总计需要内存约 24K（<code>0x1000 + 0x1000 + 0x4010 = 0x6010 = 24592B</code>），预留 32K 内存给 ION，应当可以满足初始化时的分配。</p><p>做如下修改：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line">diff --git a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">index d18d314ca..19fb26370 100644</span><br><span class="line">--- a/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">+++ b/build/boards/cv181x/sg2000_milkv_duos_musl_riscv64_sd/memmap.py</span><br><span class="line">@@ -40,10 +40,10 @@ class MemoryMap:</span><br><span class="line">     <span class="comment"># =================</span></span><br><span class="line">     <span class="comment"># Multimedia buffer. Used by u-boot/kernel/FreeRTOS</span></span><br><span class="line">     <span class="comment"># =================</span></span><br><span class="line">-    ION_SIZE = 170 * SIZE_1M</span><br><span class="line">-    H26X_BITSTREAM_SIZE = 2 * SIZE_1M</span><br><span class="line">+    ION_SIZE = 32 * SIZE_1K</span><br><span class="line">+    H26X_BITSTREAM_SIZE = 0 * SIZE_1M</span><br><span class="line">     H26X_ENC_BUFF_SIZE = 0</span><br><span class="line">-    ISP_MEM_BASE_SIZE = 20 * SIZE_1M</span><br><span class="line">+    ISP_MEM_BASE_SIZE = 0 * SIZE_1M</span><br><span class="line">     FREERTOS_RESERVED_ION_SIZE = H26X_BITSTREAM_SIZE + H26X_ENC_BUFF_SIZE + ISP_MEM_BASE_SIZE</span><br><span class="line"></span><br><span class="line">     <span class="comment"># ION after FreeRTOS</span></span><br></pre></td></tr></table></figure><p>修改后，启动时不再有 OOPS 错误日志。</p><p>编译镜像过程中会生成各阶段内存分配表：<code>build/output/sg2000_milkv_duos_musl_riscv64_sd/cvi_board_memmap.txt</code>，查看该表可以看到各部分内存分配起始地址、结束地址以及大小等明细。</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">kernel stage:</span><br><span class="line">| Name          | KERNEL_MEMORY | MONITOR    | OPENSBI_FDT | FRAMEBUFFER | H26X_BITSTREAM | H26X_ENC_BUFF | ION        | ISP_MEM_BASE | FREERTOS   |</span><br><span class="line">| Start Address | 0x80000000    | 0x80000000 | 0x80080000  | 0x9f628000  | 0x9fdf8000     | 0x9fdf8000    | 0x9fdf8000 | 0x9fdf8000   | 0x9fe00000 |</span><br><span class="line">| End Address   | 0x9fe00000    | 0x80000000 | 0x80080000  | 0x9fdf8000  | 0x9fdf8000     | 0x9fdf8000    | 0x9fe00000 | 0x9fdf8000   | 0xa0000000 |</span><br><span class="line">| Size          | 0x1fe00000    | 0x0        | 0x0         | 0x7d0000    | 0x0            | 0x0           | 0x8000     | 0x0          | 0x200000   |</span><br><span class="line">| Size(M/K/B)   | 510M          | 0M         | 0M          | 8000K       | 0M             | 0M            | 32K        | 0M           | 2M         |</span><br><span class="line">----------------------------------------------------------------------------------------------------------------------------------------------------</span><br></pre></td></tr></table></figure><p>释放 ION 内存后，Linux 下使用 <code>free -mh</code> 命令查看可用内存达到 485M，根据分配表内核拥有的内存应该是 510M，有 2M 内存划给了 FreeRTOS。这中间约 25M 的差值应该都是由内核使用，<a href="https://community.milkv.io/t/55mb-instead-of-64mb-ram/1529/10">这篇帖子</a>里有一些讨论可供参考。</p><p>后续或许可以精细的分析一下，内核到底把内存用在哪里了。 <a class="tag-plugin colorful hashtag" color="red"><svg t="1701408144765" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4228" width="200" height="200"><path d="M426.6 64.8c34.8 5.8 58.4 38.8 52.6 73.6l-19.6 117.6h190.2l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6s58.4 38.8 52.6 73.6l-19.4 117.6H896c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-42.6 256H832c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.6-117.4h-190.4l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.4-117.8H128c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l42.6-256H192c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6z m11.6 319.2l-42.6 256h190.2l42.6-256h-190.2z" p-id="4229"></path></svg><span>TODO</span></a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;最近想找个 RISC-V 小板子玩一玩，搜寻一番发现大多都是搭载了多核异构 SoC 的嵌入式开发板，入手了一块 Milk-V Duo S 开发板。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实 SpacemiT K1 的开发板更具吸引力，但是价格小贵 &lt;span class</summary>
      
    
    
    
    <category term="Loaf" scheme="https://curiositynotes.dev/categories/Loaf/"/>
    
    
    <category term="RISC-V" scheme="https://curiositynotes.dev/tags/RISC-V/"/>
    
  </entry>
  
  <entry>
    <title>在 Ubuntu 中编译安装 QEMU for RISC-V</title>
    <link href="https://curiositynotes.dev/posts/a241aa26/"/>
    <id>https://curiositynotes.dev/posts/a241aa26/</id>
    <published>2025-10-20T15:09:44.000Z</published>
    <updated>2026-05-20T14:08:16.435Z</updated>
    
    <content type="html"><![CDATA[<p>RISC-V 作为新兴架构，在 QEMU 新版本中常有新特性和 bug 修复，而系统包管理器中的版本选择较为保守，在我目前使用的系统 Ubuntu 24.04.3 LTS 中，apt 包管理器安装的 QEMU 版本为 8.2.2，而 QEMU 官网中已经以源代码形式发布了最新版 10.1.1（2025-10-08 发布），所以需要使用源代码编译安装。</p><p>QEMU 编译安装非常简单，也有较为详细的官方文档（<a href="https://wiki.qemu.org/Hosts/Linux">QEMU Building Guide</a>）可供参考，本文仅作为备忘供我需要时直接复制命令。</p><h2 id="1-编译前准备"><a class="markdownIt-Anchor" href="#1-编译前准备"></a> 1 编译前准备</h2><p>参考：</p><ul><li><code>docs/devel/build-system.txt</code></li><li><a href="https://wiki.qemu.org/Hosts/Linux">QEMU Building Guide</a></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"></span><br><span class="line"><span class="comment"># 必须软件包</span></span><br><span class="line"><span class="built_in">sudo</span> apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 推荐软件包</span></span><br><span class="line"><span class="built_in">sudo</span> apt install -y libaio-dev libbluetooth-dev libcapstone-dev libbrlapi-dev libbz2-dev \</span><br><span class="line">libcap-ng-dev libcurl4-gnutls-dev libgtk-3-dev libxen-dev liblzo2-dev libssh-dev librbd-dev \</span><br><span class="line">libibverbs-dev libjpeg8-dev libncurses5-dev libnuma-dev libvdeplug-dev libvte-2.91-dev \</span><br><span class="line">librdmacm-dev valgrind xfslibs-dev git-email libsasl2-dev libsdl2-dev libseccomp-dev \</span><br><span class="line">libvde-dev libsnappy-dev libnfs-dev libiscsi-dev</span><br></pre></td></tr></table></figure><h2 id="2-编译安装命令"><a class="markdownIt-Anchor" href="#2-编译安装命令"></a> 2 编译安装命令</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载解压源码包</span></span><br><span class="line">wget https://download.qemu.org/qemu-10.1.1.tar.xz</span><br><span class="line">tar xf qemu-10.1.1.tar.xz</span><br><span class="line"><span class="built_in">cd</span> qemu-10.1.1</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建存放编译临时文件的目录</span></span><br><span class="line"><span class="built_in">mkdir</span> build &amp;&amp; <span class="built_in">cd</span> build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置编译选项</span></span><br><span class="line">../configure --target-list=riscv64-softmmu,riscv64-linux-user,riscv32-softmmu,riscv32-linux-user --prefix=/home/ubuntu/.local/qemu-10.1.1 --enable-slirp</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编译</span></span><br><span class="line">make -j$(<span class="built_in">nproc</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将可执行文件安装到 --prefix 指定的路径</span></span><br><span class="line">make install</span><br></pre></td></tr></table></figure><p>安装后可以设置用户环境变量便于使用，在 <code>~/.bashrc</code> 活 <code>~/.zshrc</code> 文件中添加：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;<span class="variable">$HOME</span>/.local/qemu-10.1.1/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="comment"># check</span></span><br><span class="line"><span class="built_in">which</span> qemu-system-riscv64</span><br></pre></td></tr></table></figure><p>编译后可以删除 <code>build</code> 目录，如不需要再次编译，可以直接删除整个源代码目录</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf qemu-10.1.1</span><br></pre></td></tr></table></figure><h2 id="3-参数简要说明"><a class="markdownIt-Anchor" href="#3-参数简要说明"></a> 3 参数简要说明</h2><ul><li><strong><code>--target-list</code></strong>：指定要编译的 QEMU 架构和模式，只编译需要的目标平台可以减少编译时间。<ul><li><strong><code>riscv64-softmmu</code></strong>：指定编译支持 RISC-V 64 位架构的系统级仿真（softmmu，模拟整个硬件和操作系统）。</li><li><strong><code>riscv64-linux-user</code></strong>：指定编译支持 RISC-V 64 位架构的 Linux 用户态仿真（linux-user，只模拟用户态程序）。</li><li><strong><code>riscv32-*</code></strong>：（可选）一些开源内容使用 RISC-V 32 位架构以简化代码，如果想不做修改就运行这些项目需要使用支持 32 位架构的 QEMU。</li></ul></li><li><strong><code>--prefix</code></strong>：设定安装的目标路径。编译安装（<code>make install</code>）后，所有的二进制、库文件、配置文件都会安装到这个目录下，便于多版本管理。</li><li><strong><code>--enable-slirp</code></strong>：启用 slirp 用户态网络后端。slirp 是 QEMU 用于仿真网络的一种方法，允许虚拟机通过宿主机访问外部网络，以后如果需要在 QEMU 中运行 openEuler 这种大型 Linux 系统，可能会用到。</li></ul><blockquote><p>slirp 暂时未用到，相关内容在需要时补充，参考：<a href="https://wiki.qemu.org/Documentation/Networking">QEMU Doc Networking</a></p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;RISC-V 作为新兴架构，在 QEMU 新版本中常有新特性和 bug 修复，而系统包管理器中的版本选择较为保守，在我目前使用的系统 Ubuntu 24.04.3 LTS 中，apt 包管理器安装的 QEMU 版本为 8.2.2，而 QEMU 官网中已经以源代码形式发布了最</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="QEMU" scheme="https://curiositynotes.dev/tags/QEMU/"/>
    
    <category term="RISC-V" scheme="https://curiositynotes.dev/tags/RISC-V/"/>
    
  </entry>
  
  <entry>
    <title>不要使用 qBittorrent WebUI 的跳过身份验证功能</title>
    <link href="https://curiositynotes.dev/posts/61024c29/"/>
    <id>https://curiositynotes.dev/posts/61024c29/</id>
    <published>2024-12-12T10:44:51.000Z</published>
    <updated>2026-05-20T14:08:16.434Z</updated>
    
    <content type="html"><![CDATA[<div class="tag-plugin colorful note" color="red"><div class="body"><p>再重复一下标题：</p><p><emp><strong>不要使用 qBittorrent WebUI 的跳过身份验证功能！</strong></emp></p></div></div><p>“跳过身份验证功能”指的是这两个选项：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:414/212;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241212230232_dffbaabb0d509686.webp" data-src="https://cos.curiositynotes.dev/imgs/241212230232_dffbaabb0d509686.webp" alt="两个跳过身份验证选项" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">两个跳过身份验证选项</span></div></div><ul><li>勾选 <u>对本地主机上的客户端跳过身份验证</u> 选项，允许本机（localhost/127.0.0.1）访问 WebUI 时，跳过用户名和密码验证；</li><li>勾选 <u>对 IP 子网白名单中的客户端跳过身份验证</u> 选项，允许白名单列表中的 IP 或 IP 段设备访问 WebUI 时不进行用户名和密码验证，一般可能会填写一些局域网 IP。</li></ul><p>看似是两个方便的功能，但是两个功能可能会让你的 qBittorrent WebUI 在互联网上<strong>裸奔</strong>。任何人都可以“畅通无阻”地访问你的 WebUI，无需用户名和密码认证。任何人都能添加、删除、修改你的种子，而且能直接看到你的私有 tracker 和 passkey。</p><p>而且开启公网访问的 WebUI 页面可能会被搜索引擎收录，如果你还有其他内网服务使用了形同虚设的弱密码，或者根本没有登录认证。那这些服务无一例外的在“裸奔”。</p><h2 id="问题原因"><a class="markdownIt-Anchor" href="#问题原因"></a> 问题原因</h2><p>如果开启了 WebUI 的跳过身份验证功能，并且使用了这些方法做“内网穿透”：</p><ul><li>公网 IPv4/单播 IPv6 + 端口转发（端口映射）；</li><li>DDNS（公网 IPv4/单播 IPv6） + 端口转发，和直接用 IP 区别不大；</li><li>DDNS + 反向代理，通常会用于同二级域名访问不同的局域网服务；</li><li>Cloudflare Tunnel，相当于 Cloudflare 提供了用于转发的公网服务器；</li><li>FRP/NPS 等反向代理工具，内网的客户端与外网的服务端建立一个持久的连接（隧道）实现内网穿透；</li><li>其他工作过程类似反向代理的内网穿透方法。</li></ul><p>这些方法用到的工具，几乎都是扮演了反向代理服务器的角色，将互联网客户端的请求转发到提供服务的局域网服务端。<u>这个转发过程很有可能会导致请求的源 IP 地址变成来自本地（localhost/127.0.0.1）、来自 docker 的默认网桥 docker0、或者来自于属于白名单的某个局域网 IP</u>。</p><p>从局域网服务（qBittorrent WebUI）的角度来看，经过反向代理服务器的请求通常是来自于这些应当跳过验证的 IP。</p><span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/blobcatfearful.png"/></span><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>反向代理工具（如 Nginx）、FRP、NPS 及 Cloudflare Tunnel 等都可以通过额外的设置，向被代理的服务端传递真实的客户端 IP，但我个人认为这不是解决问题的最佳办法。也没有测试这样做是否真的有效。</p></div></div><div class="tag-plugin colorful note" color="cyan"><div class="body"><p><strong>1. 为什么主动暴露 IP 是一件很糟糕的事？</strong></p><p>主动暴露的 IP 更容易受到攻击，如 DDoS 攻击、端口扫描或漏洞利用。</p><p>公网 IP 地址无时无刻都在被来自全球各地的自动化攻击工具探测 IP 可用性以及开放的端口，如果有云服务器可以看一下 ssh 登录失败的记录（<code>sudo lastb</code>），不出意外的话会看到大量尝试使用<code>root</code>、<code>admin</code>、<code>test</code>等用户名登录的记录。IPv6 缓解了这个问题，因为 IPv6 有庞大的地址空间，很难被全面扫描。</p><p>主动暴露 IP 直接省去了攻击者扫描可用 IP 的步骤，IPv6 带来的优势也直接不存在了。改成不常用的端口只能挡住自动化工具，但是挡不住专门针对你这个 IP 的端口扫描。确定了一个 IP 是存在服务的，大不了逐个扫遍所有可用的端口。</p><p>另外，暴露 IP 还会造成隐私泄漏，一些免费的公开的 IP 数据库可以精确追踪 IP 地址到区县级精度。</p><p><strong>2. 可是 PT 下载本来就会暴露 IP</strong></p><p>是的，活动的 PT 种子在用户页面看到的 peer 都是公网 IP，确实会暴露 IP 地址。只能通过设置防火墙，只开放 PT 的端口，不开放其他内网服务，使用强密码等方法来防范攻击。</p><p>但是，暴露给其他 PT 用户总是好过于在搜索引擎中暴露给所有人。直接使用公网 IPv4 地址的 Web 服务（不论是不是用了 80/443 端口）很有可能被搜索引擎收录；IPv6 地址的 Web 服务倒是没见过，是否会收录我没有查证；但如果解析了域名，不论是 IPv4 还是 IPv6，都更容易被搜索引擎收录。</p></div></div><h2 id="应对方法"><a class="markdownIt-Anchor" href="#应对方法"></a> 应对方法</h2><p>最直接解决这个问题的办法，关闭 WebUI 中这两个跳过身份认证的选项，WebUI 认证使用复杂的用户名和强密码。使用 WireGuard 这类虚拟专用网络隧道（Virtual Private Network）工具做异地（外网）访问。</p><p>更进一步，不要将任何局域网内的个人服务映射到互联网上。FRP、NPS 和 Cloudflare Tunnel 这类内网穿透工具，原本的目的是将处于 NAT 后（如家庭网络）的内网服务映射到互联网上，供<mark class="tag-plugin colorful mark" color="red">任何人</mark>访问。</p><p>但是，个人的服务本就不应该被任何人访问。即便可能还有一层服务自身提供的登录认证，也不应当暴露在互联网中，这些服务可能因更新不及时存在已知漏洞或者存在 0day 漏洞被攻击者利用。</p><p>使用强密码会好些吗？会，但仍然不推荐，借用 B 站 up <a href="https://space.bilibili.com/1292029">@AlphaArea_</a>的话：</p><blockquote><p>即便设置了强密码，也不能把带锁的卫生间门当防盗门用。</p></blockquote><p>此外，将家庭宽带中的内网服务映射至互联网提供 Web 服务，可能会有法律风险（引自阿里云文档）：</p><blockquote><p>根据《互联网信息服务管理办法》以及《非经营性互联网信息服务备案管理办法》，国家对非经营性互联网信息服务实行备案制度，对经营性互联网信息服务实行许可制度。未取得许可或者未履行互联网内容提供商 ICP（Internet Content Provider）备案手续的，不得从事互联网信息服务。</p><p>解析至中国内地服务器的网站、App 等服务，必须完成 ICP 备案才可对外提供服务。</p></blockquote><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/">Cloudflare Tunnel</a></li><li><a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/configure-tunnels/origin-configuration/#proxyaddress">Cloudflare Tunnel - Origin configuration</a></li><li><a href="https://developers.cloudflare.com/cloudflare-one/faq/cloudflare-tunnels-faq/#does-cloudflare-tunnel-send-visitor-ips-to-my-origin">Does Cloudflare Tunnel send visitor IPs to my origin?</a></li><li><a href="https://ehang-io.github.io/nps/#/description">nps 文档</a></li><li><a href="https://blog.engine.wang/posts/frp-notes/#%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%8E%9F%E7%90%86%E5%8F%8A%E5%B7%A5%E5%85%B7">frp 内网穿透原理及配置记录</a></li><li><a href="https://www.ikunl.com/53.html">新版 frp 进行内网穿透/反向代理及原理解析</a></li><li><a href="https://help.aliyun.com/zh/icp-filing/basic-icp-service/product-overview/what-is-an-icp-filing?spm=5176.27804673.J_9865808500.15.20a61f589mUQKV">阿里云 - 什么是 ICP 备案</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;div class=&quot;tag-plugin colorful note&quot; color=&quot;red&quot;&gt;&lt;div class=&quot;body&quot;&gt;&lt;p&gt;再重复一下标题：&lt;/p&gt;&lt;p&gt;&lt;emp&gt;&lt;strong&gt;不要使用 qBittorrent WebUI 的跳过身份验证功能！&lt;/strong</summary>
      
    
    
    
    <category term="PT" scheme="https://curiositynotes.dev/categories/PT/"/>
    
    
    <category term="网络" scheme="https://curiositynotes.dev/tags/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="内网穿透" scheme="https://curiositynotes.dev/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"/>
    
    <category term="qBittorrent" scheme="https://curiositynotes.dev/tags/qBittorrent/"/>
    
  </entry>
  
  <entry>
    <title>修复因绑定 WireGuard 接口导致 SSH 启动失败的问题</title>
    <link href="https://curiositynotes.dev/posts/2e972d60/"/>
    <id>https://curiositynotes.dev/posts/2e972d60/</id>
    <published>2024-11-30T02:07:38.000Z</published>
    <updated>2026-05-20T14:08:16.433Z</updated>
    
    <content type="html"><![CDATA[<h2 id="问题"><a class="markdownIt-Anchor" href="#问题"></a> 问题</h2><p><a href="https://curiositynotes.dev/posts/2375099b.html">PT 下载机</a>今天重启之后突然连不上 SSH 了，我以为是之前的<a href="https://curiositynotes.dev/posts/ce06158c.html">防火墙或者 Fail2Ban 设置</a>有问题把我自己拦住了。<span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/blobcat0_0.png"/></span></p><p>更换 IP 后还是连不上，不得不连上显示器键盘鼠标查看问题。</p><p>发现是 SSH 服务没有启动，错误信息：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">error: Bind to port 53156 on 192.168.50.219 faild: Cannot assign requested address.</span><br><span class="line">error: Bind to port 53156 on 192.168.70.219 faild: Cannot assign requested address.</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2462/754;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100037_9c35fb406fac8f18.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100037_9c35fb406fac8f18.webp" alt="SSH 启动失败" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">SSH 启动失败</span></div></div><p>不过开机之后手动启动 SSH 是没有任何报错的：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1755/820;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100110_00583616cd5a3d59.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100110_00583616cd5a3d59.webp" alt="手动启动 SSH 正常" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">手动启动 SSH 正常</span></div></div><p>经过排查启动失败的原因应该是 sshd 服务启动时间在 wg0 接口（WireGuard）启动之前，而我之前修改了 sshd 设置，从默认绑定的任意 IP（0.0.0.0）到具体的 IP（192.168.50.219，192.168.70.219），而此时还没有<code>192.168.70.219</code>这个 IP，导致了 sshd 启动失败。并且这种情况 sshd 退出的状态码是<code>255</code>，而<code>sshd.service</code>中使用参数<code>RestartPreventExitStatus=255</code>禁止在遇到这种状态码时自动重启。最终就导致了 sshd 绑定 IP 时遇到了未知的 IP 而异常退出，并且不再尝试重启。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>其实报错信息中可以看到，绑定的物理网卡 IP（192.168.50.219）也出错了，原因应该相同。在<code>sshd.service</code>中使用参数<code>After=network.target auditd.service</code>确保 sshd 服务在<code>network.target</code>之后启动。但是<code>network.target</code>并不代表网络接口已经被配置。</p><p>在<a href="https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/">systemd-NetworkTarget</a>及<a href="https://systemd.io/NETWORK_ONLINE/">Network Configuration Synchronization Points</a>中提到：</p><p>network.target indicates that the network management stack has been started. <strong>Ordering after it has little meaning during start-up: whether any network interfaces are already configured when it is reached is not defined.</strong></p><p>正确的等待“网络就绪”后再启动某个服务的方法参考<a href="https://systemd.io/NETWORK_ONLINE/">Network Configuration Synchronization Points</a>中的 Discussion 和 FAQ 部分。</p><p>我并没有单独处理这个问题，下面使用的两个方法都能够“顺便”解决物理网卡接口没来得及配置的问题。</p></div></div><h2 id="解决方法-1"><a class="markdownIt-Anchor" href="#解决方法-1"></a> 解决方法 1</h2><p>第一个方法就是修改<code>sshd.service</code>，移除参数<code>RestartPreventExitStatus=255</code>，让 sshd 不论什么原因启动失败后都尝试重启；并且增加参数<code>RestartSec=10s</code>推迟 sshd 重启时间，即第一次失败 10 秒后再启动，这期间 wg0 接口已经启动完成。10s 的时间是一个估计值，可以加长确保 wg0 接口启动完成，但不建议设置更短。</p><p>不要直接编辑<code>/lib/systemd/system/ssh.service</code>或者<code>/etc/systemd/system/ssh.service</code>，<code>systemctrl</code>提供了<code>edit</code>方式覆盖已有的设置。使用命令<code>sudo systemctl edit sshd</code>，编辑需要覆盖<code>sshd.service</code>中的设置。</p><p>在两条注释中添加要覆盖的配置项，<code>RestartPreventExitStatus=</code>表示将该参数清空：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[Service]</span><br><span class="line">RestartSec=10s</span><br><span class="line">RestartPreventExitStatus=</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1795/1405;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100129_6c2ebc28ed3fb8e2.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100129_6c2ebc28ed3fb8e2.webp" alt="解决方法 1-10 秒后重启" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">解决方法 1-10 秒后重启</span></div></div><p>设置后重启系统查看日志，结果符合预期，第一次启动失败 10 秒之后重启正常：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2574/826;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100139_cf6a8fcf117e8d6a.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100139_cf6a8fcf117e8d6a.webp" alt="10 秒后重启正常" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">10 秒后重启正常</span></div></div><blockquote><p><a href="https://serverfault.com/a/640065">参考</a></p></blockquote><h2 id="解决方法-2"><a class="markdownIt-Anchor" href="#解决方法-2"></a> 解决方法 2</h2><p>另一个方法也需要修改<code>sshd.service</code>，使用参数<code>Requires</code>和<code>After</code>指定 sshd 服务依赖 wg0，并且让 sshd 在 wg0 启动之后再启动，修改的内容为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">After=network.target auditd.service wg-quick@wg0.service</span><br><span class="line">Requires=sys-devices-virtual-net-wg0.device</span><br></pre></td></tr></table></figure><p>图中红框是新增的设置，<code>After=network.target auditd.service</code>这部分是原配置文件存在的：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1787/528;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100230_7ecee89862b11424.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100230_7ecee89862b11424.webp" alt="解决方法 2-设置依赖" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">解决方法 2-设置依赖</span></div></div><p>重启后检查日志，符合预期，直接启动成功，没有重启：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1827/377;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241130100253_9549328f7e80f70c.webp" data-src="https://cos.curiositynotes.dev/imgs/241130100253_9549328f7e80f70c.webp" alt="设置依赖后启动正常" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">设置依赖后启动正常</span></div></div><blockquote><p><a href="https://unix.stackexchange.com/a/624988">参考</a></p></blockquote><h2 id="解决方法-3"><a class="markdownIt-Anchor" href="#解决方法-3"></a> 解决方法 3</h2><p>还找到了这样一种方法，修改内核参数<code>net.ipv4.ip_nonlocal_bind=1</code>，允许绑定非本地 IP 地址，这样可以在网卡 IP 还没有确定的时候让 sshd 能够绑定配置文件中预设的 IP。原理上可行，但我没有验证，这里仅作记录。</p><blockquote><p><a href="https://www.cyberciti.biz/tips/howto-openssh-sshd-listen-multiple-ip-address.html">参考</a></p></blockquote><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://manpages.ubuntu.com/manpages/jammy/en/man1/systemctl.1.html">manpages-systemctl.1</a></li><li><a href="https://manpages.ubuntu.com/manpages/jammy/en/man5/systemd.service.5.html">manpages-systemd.service.5</a></li><li><a href="https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/">systemd-NetworkTarget</a></li><li><a href="https://systemd.io/NETWORK_ONLINE/">Network Configuration Synchronization Points</a></li><li><a href="https://serverfault.com/a/640065">sshd service fails to start</a></li><li><a href="https://unix.stackexchange.com/a/624988">SSH fails to start when ListenAddress is set to Wireguard VPN IP</a></li><li><a href="https://www.cyberciti.biz/tips/howto-openssh-sshd-listen-multiple-ip-address.html">A note about dealing with OpenVPN or Wireguard VPN binding on Linux</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;问题&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#问题&quot;&gt;&lt;/a&gt; 问题&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://curiositynotes.dev/posts/2375099b.html&quot;&gt;PT 下载机&lt;/a&gt;今天重启之</summary>
      
    
    
    
    <category term="工具" scheme="https://curiositynotes.dev/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="SSH" scheme="https://curiositynotes.dev/tags/SSH/"/>
    
    <category term="网络" scheme="https://curiositynotes.dev/tags/%E7%BD%91%E7%BB%9C/"/>
    
  </entry>
  
  <entry>
    <title>提高 PT 下载机安全性的配置</title>
    <link href="https://curiositynotes.dev/posts/ce06158c/"/>
    <id>https://curiositynotes.dev/posts/ce06158c/</id>
    <published>2024-11-28T04:05:29.000Z</published>
    <updated>2026-05-20T14:08:16.432Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2024-11-28：初始版本</li><li>2024-11-30：修复因绑定 WireGuard 接口 IP 在系统重启时 SSH 启动失败的问题 <a href="#3-2-1-%E4%BF%AE%E6%94%B9%E7%9B%91%E5%90%AC%E5%9C%B0%E5%9D%80%E5%92%8C%E7%AB%AF%E5%8F%A3">3.2.1 修改监听地址和端口</a></li><li>2025-05-09：修复 cups 服务和 avahi 服务设置 disable 后仍然能够自动重启的问题 <a href="#4-%E5%8F%AA%E5%BC%80%E5%90%AF%E5%BF%85%E8%A6%81%E7%9A%84%E6%9C%8D%E5%8A%A1">4 只开启必要的服务</a></li></ul><p>上篇文章<a href="https://curiositynotes.dev/posts/2375099b.html">搭建一台 PT 下载主机</a>的最后提到计划写一些提高安全性的内容，原本是打算鸽一阵的。毕竟这台机器位于校园网内，本身没有公网 IPv4 地址，校园网的 IPv6 也是关闭入站连接的，我一度认为还是比较安全的。</p><p>然而，今天偶然看了一下登录失败日志，发现居然有内网设备在暴力破解 SSH。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:805/488;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129183159_21e963ba824800f4.webp" data-src="https://cos.curiositynotes.dev/imgs/241129183159_21e963ba824800f4.webp" alt="登录失败日志" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">登录失败日志</span></div></div><p>看来校园网内也并不十分安全，还是需要做一些必要的措施增加一些防护能力。</p><p>实际上关于 Linux 主机安全性方面有一些非常全面的文章，这些文章从各种角度介绍了如何加强 Linux 系统安全性。如 <a href="https://wiki.archlinux.org/title/Security">ArchLinux-Security</a> 和 <a href="https://www.debian.org/doc/manuals/securing-debian-manual/index.en.html">Securing Debian Manual</a> 等。其中 <a href="https://wiki.archlinux.org/title/Security">ArchLinux-Security</a> 建议完全读一遍，篇幅不长，语言也可以切换到中文。而 <a href="https://www.debian.org/doc/manuals/securing-debian-manual/index.en.html">Securing Debian Manual</a> 则更为详细，但是篇幅较长，适合对具体章节针对性的深入阅读。</p><p>实际选择的安全策略总是安全与便利的平衡，本文主要从实际操作的角度来记录我对这台 PT 下载机的配置。</p><div class="tag-plugin colorful note" color="warning"><div class="title"><strong><p>注意时效性！</p></strong></div><div class="body"><p>文章更新于 2024 年 11 月，以下内容基于当时的知识和技术状况。</p><p>随着时间的推移，新的安全威胁和解决方案可能会出现，一些配置项可能会被移除（如 SSH 配置中 <code>Protocol</code> 项在当前版本已经被移除）或者替换，注意文章内容的时效性！</p></div></div><h2 id="1-概述"><a class="markdownIt-Anchor" href="#1-概述"></a> 1 概述</h2><p>计划从我现在能想到的如下几个方面来提高下载机的安全性，当前主要是防御来自于网络的攻击，不涉及数据安全、备份、系统及软件更新已经硬件层面的安全防护（例如硬盘被偷）等方面的内容。</p><ul><li>保护密码</li><li>保护 SSH</li><li>只开启必要的服务</li><li>设置防火墙</li><li>封禁可疑 IP</li></ul><h2 id="2-保护密码"><a class="markdownIt-Anchor" href="#2-保护密码"></a> 2 保护密码</h2><h3 id="21-使用强密码"><a class="markdownIt-Anchor" href="#21-使用强密码"></a> 2.1 使用强密码</h3><p>下载机这一使用场景中，涉及到的密码主要有：</p><ul><li>Ubuntu root 用户密码；</li><li>Ubuntu ubuntu 普通用户密码；</li><li>Ubuntu 系统用户密码；</li><li>qBittorrent WebUI 用户密码；</li><li>Samba 用户密码。</li></ul><p>root 用户的密码在安装系统后设定，建议使用密码生成工具生成随机密码，尽可能同时包含大小写字母、数字和符号以提升密码强度。root 密码设置好后很少使用，妥善保存即可，并不需要考虑是不是容易用大脑记住。</p><p>普通用户 ubuntu 的密码在登录 SSH、使用 <code>sudo</code> 运行命令等场景会频繁用到，建议在能记住的情况下尽量提高复杂性，即尽可能同时包含大小写字母、数字和符号，并且足够长（&gt;=16 位）。</p><div class="tag-plugin colorful note" color="cyan"><div class="title">其实</div><div class="body"><p>Ubuntu 用户名使用 <code>ubuntu</code> 是一个反面案例，用户名也不应是容易猜到的字符串。</p></div></div><p>Ubuntu 的系统用户在<a href="https://curiositynotes.dev/posts/2375099b.html#%E4%BD%BF%E7%94%A8%E7%8B%AC%E7%AB%8B%E7%9A%84%E7%94%A8%E6%88%B7%E8%BF%90%E8%A1%8C">搭建一台 PT 下载主机 - 使用独立的用户运行</a>部分简单介绍过，在新建系统用户（或者低权限的普通用户）时，要确保这个用户不能通过 Shell 和 SSH 登录。</p><p>qBittorrent WebUI 和 Samba 的用户密码与 Ubuntu 普通用户的规则相同，用户名不容易被猜到，密码复杂。</p><h3 id="22-妥善保管密码"><a class="markdownIt-Anchor" href="#22-妥善保管密码"></a> 2.2 妥善保管密码</h3><p>密码应当被妥善保管，既要确保不会被别人获取，也要保证不会丢失。有时候不丢失密码可能会更重要，否则复杂的密码最终只能挡住你自己……</p><p>另外密码不要用明文写在命令中，有选择的情况下，可以将密码保存在文件中，并妥善设置权限。如<a href="https://curiositynotes.dev/posts/2375099b.html#7-5-%E6%8C%82%E8%BD%BD%E5%85%B6%E4%BB%96%E4%B8%BB%E6%9C%BA%E7%9A%84SMB%E5%85%B1%E4%BA%AB">搭建一台 PT 下载主机 - 挂载其他主机的 SMB 共享</a>中对登录远程 NAS 的 Samba 用户名和密码的使用。</p><h2 id="3-保护-ssh"><a class="markdownIt-Anchor" href="#3-保护-ssh"></a> 3 保护 SSH</h2><p>下载机 SSH 版本：OpenSSH_8.9p1 Ubuntu-3ubuntu0.10, OpenSSL 3.0.2 15 Mar 2022</p><h3 id="31-使用密钥连接-ssh"><a class="markdownIt-Anchor" href="#31-使用密钥连接-ssh"></a> 3.1 使用密钥连接 SSH</h3><p>SSH 是连接下载机的主要方式，首先需要设置使用密钥连接 SSH，不再使用密码。在本机（不是下载机）操作：</p><h4 id="311-生成密钥可选"><a class="markdownIt-Anchor" href="#311-生成密钥可选"></a> 3.1.1 生成密钥（可选）</h4><p>这是一个可选的步骤，如果本地之前生成过 SSH 密钥，并且想使用原来的密钥，可以跳过这个步骤。</p><p>生成新的密钥：<code>ssh-keygen -t ed25519 -C &quot;your@email.com&quot;</code>，按提示生成即可。生成的默认路径 <code>~/.ssh</code>，公钥的默认文件名为 <code>id_ed25519.pub</code>，私钥的默认文件名为 <code>id_ed25519</code>。</p><h4 id="312-上传公钥"><a class="markdownIt-Anchor" href="#312-上传公钥"></a> 3.1.2 上传公钥</h4><p>注意上传的是<strong>公钥</strong> <code>id_ed25519.pub</code>，一般以 <code>pub</code> 为拓展名，别把私钥传上去了。两种方法任选：</p><ul><li>使用命令上传：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-copy-id -i ~/.ssh/id_ed25519.pub ubuntu@192.168.50.219`</span><br></pre></td></tr></table></figure><p>验证用户 ubuntu 的密码后即可上传成功。</p><ul><li>手动上传</li></ul><p>首先打开公钥文件 <code>id_ed25519.pub</code>，复制其内容。</p><p>然后在<strong>Ubuntu 下载机</strong>操作（使用 SSH 密码连接，方便复制粘贴）：</p><p>将公钥内容粘贴到 authorized_keys 文件中，authorized_keys 文件开始时是不存在的，需要自己创建：<code>vim /home/ubuntu/.ssh/authorized_keys</code>。</p><p>保存后修改 authorized_keys 文件的权限为 600，只允许用户 ubuntu 对该文件读写：<code>sudo chmod 600 authorized_keys</code>。</p><h4 id="313-重启-sshd-服务"><a class="markdownIt-Anchor" href="#313-重启-sshd-服务"></a> 3.1.3 重启 sshd 服务</h4><p>公钥上传完成后，重启 SSH 服务：<code>sudo systemctl restart sshd</code>，在本机退出 SSH 后重新登录，无需输入密码。</p><h3 id="32-修改-sshd-配置文件"><a class="markdownIt-Anchor" href="#32-修改-sshd-配置文件"></a> 3.2 修改 sshd 配置文件</h3><p>配置文件位于 <code>/etc/ssh/sshd_config</code>，sshd 表示 SSH Server 的设置，而 <code>ssh_config</code> 是 SSH Client 的设置。修改的内容有些设置项存在于配置文件中，但是没有使用（被注释），需要取消注释并将值设置为以下推荐的内容：</p><p>打开配置文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><h4 id="321-修改监听地址和端口"><a class="markdownIt-Anchor" href="#321-修改监听地址和端口"></a> 3.2.1 修改监听地址和端口</h4><div class="tag-plugin colorful note" color="yellow"><div class="title">2024-11-30</div><div class="body"><p>更新：<strong>建议先看！</strong><a href="https://curiositynotes.dev/posts/2e972d60.html">修复因绑定 WireGuard 接口导致 SSH 启动失败的问题</a></p></div></div><p>SSH 默认监听任意 IPv4/IPv6 地址的 22 端口，使用命令 <code>sudo ss -tnlp | grep sshd</code>或<code>sudo netstat -ntlp | grep sshd</code> 查看：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1380/77;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184309_ff55a46a8b293928.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184309_ff55a46a8b293928.webp" alt="SSH 默认监听的地址和端口" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">SSH 默认监听的地址和端口</span></div></div><p>这里修改为只监听 IPv4 地址；只允许来自于局域网（192.168.50.0/24）和 WireGuard 局域网（192.168.70.0/24）的设备连接，修改监听地址为<strong>下载机</strong>自身在有线网卡和 WireGuard 接口 wg0 上的 IP；使用一个不常用的端口降低被扫描到的可能。</p><p>这里有必要再放一下下载机所处的网络结构图：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:3547/1715;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241126232500_ca0a329ed6415da6.webp" data-src="https://cos.curiositynotes.dev/imgs/241126232500_ca0a329ed6415da6.webp" alt="网络整体结构" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">网络整体结构</span></div></div><p>修改配置文件 <code>/etc/ssh/sshd_config</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改端口</span></span><br><span class="line">Port 53156</span><br><span class="line"><span class="comment"># 只监听IPv4</span></span><br><span class="line">AddressFamily inet</span><br><span class="line"><span class="comment"># 修改监听地址</span></span><br><span class="line">ListenAddress 192.168.50.219</span><br><span class="line">ListenAddress 192.168.70.219</span><br></pre></td></tr></table></figure><p>修改保存后重启 SSH 服务 <code>sudo systemctl restart sshd</code>，再次查看监听情况 <code>sudo ss -tnlp | grep sshd</code>：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1383/82;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184623_1eee5cdfd00d2940.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184623_1eee5cdfd00d2940.webp" alt="SSH 修改后的监听地址和端口" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">SSH 修改后的监听地址和端口</span></div></div><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>如果重启后连接断开，需要使用新端口连接 SSH，在 ssh 命令中指定端口：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -p 53156 ubuntu@192.168.50.219</span><br></pre></td></tr></table></figure><p>推荐本机 SSH 的 config 文件中设置主机别名和端口，操作更便捷一些，参考<a href="https://curiositynotes.dev/posts/b7c24d2f.html#2-4-%E4%BF%AE%E6%94%B9ssh%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6">同时使用不同的 GitHub 账号管理仓库 - 修改 ssh 配置文件</a></p></div></div><h4 id="322-登录设置"><a class="markdownIt-Anchor" href="#322-登录设置"></a> 3.2.2 登录设置</h4><p>不允许 root 用户通过 SSH 登录：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PermitRootLogin no</span><br></pre></td></tr></table></figure><p>不允许使用密码登录，不允许空密码：</p><div class="tag-plugin colorful note" color="warning"><div class="body"><p>设置不允许密码登录前确保已经正确配置密钥登录！</p></div></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PasswordAuthentication no</span><br><span class="line">PermitEmptyPasswords no</span><br></pre></td></tr></table></figure><p>设置超时自动断开连接：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ClientAliveInterval 300</span><br><span class="line">ClientAliveCountMax 0</span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>参数 <code>ClientAliveInterval</code> 指定服务器向客户端发送“存活”消息的时间间隔（以秒为单位），设置为 300，表示服务器每 5 分钟向客户端发送一个存活消息。</p><p>参数 <code>ClientAliveCountMax</code> 指定在断开连接之前服务器可以发送多少个连续的、未被响应的“存活”消息，设置为 0，表示服务器将不会多次发送存活消息，只要有未响应的消息就会立即导致客户端连接被关闭。</p></div></div><p>设置用户白名单：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">AllowUsers ubuntu</span><br></pre></td></tr></table></figure><h2 id="4-只开启必要的服务"><a class="markdownIt-Anchor" href="#4-只开启必要的服务"></a> 4 只开启必要的服务</h2><div class="tag-plugin colorful note" color="orange"><div class="title"><strong><p>2025-05-09 更新</p></strong></div><div class="body"><p>偶然发现 <a href="#41-%E5%85%B3%E9%97%AD-cups">4.1 - 4.2</a> 的操作并不能完全关闭对应服务，重启之后这些服务又会自动启动，这是因为有其他服务依赖这些服务。解决方法就是找到这些服务，如果确定不需要使用直接关闭即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl --reverse list-dependencies avahi-daemon.service</span><br><span class="line"><span class="comment"># avahi-daemon.service</span></span><br><span class="line"><span class="comment"># ● └─cups-browsed.service</span></span><br></pre></td></tr></table></figure><p>可以看到 cups-browsed.service 依赖 avahi-daemon.service，可以进一步查看 cups-browsed.service：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span>  /lib/systemd/system/cups-browsed.service</span><br><span class="line"><span class="comment"># [Unit]</span></span><br><span class="line"><span class="comment"># Description=Make remote CUPS printers available locally</span></span><br><span class="line"><span class="comment"># Requires=cups.service</span></span><br><span class="line"><span class="comment"># After=cups.service avahi-daemon.service network-online.target</span></span><br><span class="line"><span class="comment"># Wants=avahi-daemon.service network-online.target</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># [Service]</span></span><br><span class="line"><span class="comment"># ExecStart=/usr/sbin/cups-browsed</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># [Install]</span></span><br><span class="line"><span class="comment"># WantedBy=multi-user.target</span></span><br></pre></td></tr></table></figure><p>其中的 <code>After=cups.service avahi-daemon.service</code> 和 <code>Wants=avahi-daemon.service</code> 就是这两个服务被设置为 disable 后还能自动启动的原因。</p><p>直接禁止 cups-browsed.service 服务启动即可：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> cups-browsed</span><br></pre></td></tr></table></figure><p>参考：<a href="https://unix.stackexchange.com/a/480124">How to disable CUPS service on reboot with systemd?</a></p></div></div><p>这里按需关闭，下载机只用于下载，可以关闭其他不相关的服务。使用命令 <code>sudo netstat -ntulp</code> 查看使用的端口和占用端口的进程（想看到进程名需要使用<code>sudo</code>）。</p><h3 id="41-关闭-cups"><a class="markdownIt-Anchor" href="#41-关闭-cups"></a> 4.1 关闭 cups</h3><p>Ubuntu Desktop 默认有打印机共享服务 cups，关闭这个服务并禁止自动启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl stop cups</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> cups</span><br></pre></td></tr></table></figure><h3 id="42-关闭-avahi"><a class="markdownIt-Anchor" href="#42-关闭-avahi"></a> 4.2 关闭 avahi</h3><p>avahi-daemon 提供基于 mDNS(Multicast DNS) 和 DNS-SD(DNS Service Discovery) 的零配置网络服务。这个服务使设备能在本地网络中自动发现其他设备和它们提供的服务。不需要自动服务发现可以关闭：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl stop avahi-daemon</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> avahi-daemon</span><br></pre></td></tr></table></figure><h3 id="43-关闭-nmbd"><a class="markdownIt-Anchor" href="#43-关闭-nmbd"></a> 4.3 关闭 nmbd</h3><p>nmbd 是 Samba 套件中的一个守护进程，主要用于提供基于 NetBIOS 名称服务的网络功能。在 Windows 中，NetBIOS 名称服务允许主机通过网络名称（如计算机名，代替了 IP）相互通信。我一直都是使用 IP 访问 SMB，不需要主机名，关闭 nmbd 服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl stop nmbd</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> nmbd</span><br></pre></td></tr></table></figure><h3 id="44-关闭-samba-在-ipv6-上的监听"><a class="markdownIt-Anchor" href="#44-关闭-samba-在-ipv6-上的监听"></a> 4.4 关闭 Samba 在 IPv6 上的监听</h3><p>我不使用 IPv6 传输文件，这里关闭 Samba 在 IPv6 上的监听：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1139/294;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184640_be71efbe68da4453.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184640_be71efbe68da4453.webp" alt="Samba 默认会同时监听 IPv6 地址" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">Samba 默认会同时监听 IPv6 地址</span></div></div><p>Samba 使用参数 <code>bind interfaces only</code> 和参数 <code>interfaces</code> 来控制 Samba 服务器的网络行为：</p><ul><li><code>bind interfaces only</code> 参数用于限制 Samba 服务是否在“指定的接口或地址”上监听，可选值为 <code>yes</code> 和 <code>no</code>，默认值为 <code>no</code> 表示监听所有可用的接口和地址；</li><li><code>interfaces</code> 参数用来指定接口或地址，可以指定接口名称、IP 地址或者二者同时存在，例如 <code>interfaces = eth0 192.168.2.10/24 192.168.3.10/255.255.255.0</code>。</li></ul><p>下载机只会在局域网（192.168.50.0/24）中与 NAS 和其他主机互传文件，所以这里只设置（192.168.50.0/24），此外使用 <code>smbpasswd</code> 命令修改密码时需要监听 <code>localhost/127.0.0.1</code>，<code>127.0.0.1</code> 也应当添加到接口列表。这里不能监听 <code>lo</code> 接口，因为 <code>lo</code> 接口包括 IPv6 的回环地址 <code>::1</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加到 /etc/samba/smb.conf [global] 部分</span></span><br><span class="line"><span class="built_in">bind</span> interfaces only = <span class="built_in">yes</span></span><br><span class="line"><span class="comment"># interfaces = lo 192.168.50.0/24</span></span><br><span class="line">interfaces = 127.0.0.1 192.168.50.0/24</span><br></pre></td></tr></table></figure><p>保存后重启 smbd 服务，查看设置结果：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1033/126;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184653_cc89988a8bd83307.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184653_cc89988a8bd83307.webp" alt="Samba 只监听指定的 IPv4 地址" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">Samba 只监听指定的 IPv4 地址</span></div></div><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>不要试图在 <code>interfaces</code> 参数里添加 WireGuard 接口，或者 WireGuard 接口的 IP，不会生效！</p><p>而且由于设置了 <code>bind interfaces only = yes</code>，原本可以通过 WireGuard 连通的 SMB 也会无法访问。</p><p>参考<a href="https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html#BINDINTERFACESONLY">bind interfaces only 参数</a></p><blockquote><p>For file service it causes smbd(8) to bind only to the interface list given in the interfaces parameter. This restricts the networks that smbd will serve, to packets coming in on those interfaces. Note that you should <strong>not use this parameter</strong> for machines that are serving PPP or other intermittent or <strong>non-broadcast network interfaces</strong> as it will not cope with non-permanent interfaces.</p></blockquote><p>WireGuard 接口是不支持广播的，图中 wg0 和 wg6 都是 WireGuard 接口：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1320/256;width:400pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129190616_18d520e551c90b6d.webp" data-src="https://cos.curiositynotes.dev/imgs/241129190616_18d520e551c90b6d.webp" alt="WireGuard 接口不支持广播" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">WireGuard 接口不支持广播</span></div></div><p>参考：</p><ul><li><a href="https://unix.stackexchange.com/questions/529367/samba-is-not-listening-on-specified-wireguard-vpn-interface">Samba is not listening on specified wireguard / vpn interface</a>，但是选定的答案也是错误的，参考其他回复。</li><li><a href="https://hartmantam.xyz/hidden/samba-over-tailscale-wireguard-old/">Samba over Tailscale/Wireguard</a></li></ul><p>这种情况只能使用 <code>allow hosts</code> 来限定哪些 IP 可以访问 SMB 服务，不能使用 <code>interfaces</code> 参数。</p></div></div><h2 id="5-设置防火墙"><a class="markdownIt-Anchor" href="#5-设置防火墙"></a> 5 设置防火墙</h2><h3 id="51-linux-防火墙的简单认识"><a class="markdownIt-Anchor" href="#51-linux-防火墙的简单认识"></a> 5.1 Linux 防火墙的简单认识</h3><p>Linux 防火墙一般由两部分组成，一部分是作为内核模块的 Netfilter 数据包过滤框架，负责数据包的捕获和处理。另一部分是运行在用户空间配置该框架的用户程序。传统的用户空间工具是 iptables，用户使用 iptables 配置 Netfilter 规则。xtables 是 iptables 在内核中的实现，是 Netfilter 框架的组成部分。</p><p>nftables 是 Netfilter 框架的新一代的子系统，旨在替代 xtables（及用户空间工具 iptables、ip6tables 等），它简化了规则管理，并提供更丰富的功能和更好的性能。</p><p>现阶段的 Linux 内核同时支持两种系统，从 Ubuntu 22.04 LTS 开始，<a href="https://ubuntu.com/blog/whats-new-in-security-for-ubuntu-22-04-lts">防火墙后端默认使用 nftables</a>，传统的 iptables 用户空间管理工具仍然能使用，不过默认情况下使用 nftables 后端实现，同时还提供了新的 nft 用户空间工具，能够创建传统 iptables 不支持的更灵活的规则。</p><p>Ubuntu 默认使用 <a href="https://documentation.ubuntu.com/server/how-to/security/firewalls/">ufw-Uncomplicated Firewall</a> 作为防火墙程序，ufw 是 iptables 的一个上层抽象和简化工具，便于普通用户使用，而在复杂网络环境或需要更高自定义水平时，直接使用 iptables 仍是必要的。</p><p>我这里选用 iptables 作为防火墙配置工具。其实对于一台普通的主机（例如这台下载机），只需要开放特定的端口，阻止非必要的入站连接（有时也称为传入连接）等非常基础的防火墙功能，ufw 已经足够。选用 iptables 主要是我比较熟悉这个工具，而且可以配合使用 Fail2Ban，该工具对 iptables 支持比较完善。</p><p>为什么不用新的 nft（nftables）？因为不会，另外 iptables 还不大可能在短时间彻底被 nftables 替换掉。</p><h3 id="52-检查-iptables-是否可用"><a class="markdownIt-Anchor" href="#52-检查-iptables-是否可用"></a> 5.2 检查 iptables 是否可用</h3><p>首先关闭 ufw，使用命令 <code>sudo ufw status</code> 查看 ufw 状态，默认是关闭状态 <code>inactive</code>。如果 ufw 已经启用，使用命令 <code>sudo ufw disable</code> 禁用。</p><p>然后检查 iptables 版本 <code>sudo iptables -V</code>，我这里输出 <code>iptables v1.8.7 (nf_tables)</code>，这就是使用 nftables 作为后端的 iptables 工具。如果没有 iptables 命令，可能是没有使用 sudo，或者需要用 apt 安装一下。</p><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>在一些情况下，可能会出现命令不兼容的或者命令不生效的问题，此时可以考虑回退到之前的 iptables（iptables-legacy）</p><p>参考：</p><ul><li><a href="https://wiki.debian.org/nftables#Reverting_to_legacy_xtables">Reverting to legacy xtables</a></li><li><a href="https://unix.stackexchange.com/questions/762671/ubuntu-22-04-iptables-command-not-working">Ubuntu 22.04 iptables command not working</a></li></ul><p>不过目前我仍然使用 iptables-nft，没有出现问题。</p></div></div><h3 id="53-针对一般主机的-iptables-配置"><a class="markdownIt-Anchor" href="#53-针对一般主机的-iptables-配置"></a> 5.3 针对一般主机的 iptables 配置</h3><p>这部分主要参考了<a href="https://wiki.archlinux.org/title/Simple_stateful_firewall">Simple stateful firewall</a>，根据我的需要略作修改，非常建议去阅读原文。</p><div class="tag-plugin colorful note" color="warning"><div class="body"><p>注意 iptables 规则是立即生效的，如果使用 SSH 连接远程主机配置，一定小心别把自己挡在防火墙之外。</p><p>此外，以下操作没有考虑安装 Docker 的情况，Docker 也使用 iptables 管理容器网络，如果直接清除规则可能会影响容器工作。<a class="tag-plugin colorful hashtag" color="yellow"><svg t="1701408144765" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4228" width="200" height="200"><path d="M426.6 64.8c34.8 5.8 58.4 38.8 52.6 73.6l-19.6 117.6h190.2l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6s58.4 38.8 52.6 73.6l-19.4 117.6H896c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-42.6 256H832c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.6-117.4h-190.4l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.4-117.8H128c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l42.6-256H192c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6z m11.6 319.2l-42.6 256h190.2l42.6-256h-190.2z" p-id="4229"></path></svg><span>TODO</span></a></p></div></div><p>iptables 会按照顺序处理规则，一旦数据包匹配到某条规则，则不会向后检查规则。应当将最常匹配到的规则放在前面，减少匹配规则的开销。另外由于这个特性，一些限定较为精准的规则应当放在限定比较宽泛的规则之前，允许访问的规则应当放在拒绝访问的规则之前。</p><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>举例：</p><p>规则一：允许来自 IP 为 192.168.50.100 的主机使用 TCP 协议访问本机 12345 端口，iptables 规则为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">iptables -A INPUT -p tcp -s 192.168.50.100 --dport 12345 -j ACCEPT</span><br></pre></td></tr></table></figure><p>规则二：拒绝 IP 地址段为 192.168.50.0/24 的其他主机的任何访问，iptables 规则为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">iptables -A INPUT -s 192.168.50.0/24 -j DROP</span><br></pre></td></tr></table></figure><p>规则一应当写在规则二之前，如果将规则二写在前面，来自 192.168.50.100，目标端口为 12345 的 TCP 数据包，会因为先匹配规则二被丢弃。</p></div></div><p>针对一般主机的防火墙设置时，主要操作 iptables 的 filter 表，不使用参数 <code>-t &lt;table&gt;</code> 指定表时，默认操作的就是 filter 表。</p><h4 id="531-设置-ipv4-防火墙"><a class="markdownIt-Anchor" href="#531-设置-ipv4-防火墙"></a> 5.3.1 设置 IPv4 防火墙</h4><ul><li>恢复所有链的默认策略为 ACCEPT，清除所有规则，将防火墙恢复为初始状态：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先设置默认策略</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -P INPUT ACCEPT</span><br><span class="line"><span class="built_in">sudo</span> iptables -P FORWARD ACCEPT</span><br><span class="line"><span class="built_in">sudo</span> iptables -P OUTPUT ACCEPT</span><br><span class="line"><span class="comment"># 清空规则</span></span><br><span class="line">sduo iptables -F</span><br><span class="line"><span class="comment"># 查看结果</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -nvL</span><br><span class="line"><span class="comment"># 查看结果(可选)</span></span><br><span class="line"><span class="built_in">sudo</span> iptables-save</span><br></pre></td></tr></table></figure><p>清空后的 iptables 规则如图，<code>iptables-save</code> 的输出也可能为空，也是正常的。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1649/698;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184707_a44c45ef03ed3e6a.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184707_a44c45ef03ed3e6a.webp" alt="恢复 iptables 规则到默认策略" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">恢复 iptables 规则到默认策略</span></div></div><ul><li>添加自定义链，分别管理新到达的 UDP 数据包和 TCP 数据包：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> iptables -N MY_TCP</span><br><span class="line"><span class="built_in">sudo</span> iptables -N MY_UDP</span><br></pre></td></tr></table></figure><ul><li>设置 FORWARD 链默认规则：</li></ul><p>本机不用做 NAT 网关，不会有经本机转发到其他主机的数据，所以转发直接丢弃。同时检查内核是否开启了转发，默认是关闭的，如果开启了也要关闭。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># FORWARD链默认丢弃</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -P FORWARD DROP</span><br><span class="line"><span class="comment"># 查看内核是否开启转发(IPv4，IPv6)，输出0表示关闭</span></span><br><span class="line"><span class="built_in">cat</span> /proc/sys/net/ipv4/ip_forward</span><br><span class="line"><span class="built_in">cat</span> /proc/sys/net/ipv6/conf/all/forwarding</span><br><span class="line"><span class="comment"># 或者使用sysctl工具</span></span><br><span class="line">sysctl net.ipv4.ip_forward</span><br><span class="line">sysctl net.ipv6.conf.all.forwarding</span><br></pre></td></tr></table></figure><p>临时关闭内核转发（重启后恢复，临时关闭仅作命令参考，实际需要永久关闭）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 直接修改文件</span></span><br><span class="line"><span class="built_in">echo</span> 0 | <span class="built_in">sudo</span> <span class="built_in">tee</span> /proc/sys/net/ipv4/ip_forward</span><br><span class="line"><span class="built_in">echo</span> 0 | <span class="built_in">sudo</span> <span class="built_in">tee</span> /proc/sys/net/ipv6/conf/all/forwarding</span><br><span class="line"><span class="comment"># 或者使用sysctl工具</span></span><br><span class="line"><span class="built_in">sudo</span> sysctl -w net.ipv4.ip_forward=0</span><br><span class="line"><span class="built_in">sudo</span> sysctl -w net.ipv6.conf.all.forwarding=0</span><br></pre></td></tr></table></figure><p>永久关闭内核转发（如果默认已经关闭则无需操作）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 编辑配置文件</span></span><br><span class="line"><span class="built_in">sudo</span> vim /etc/sysctl.conf</span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加或修改</span></span><br><span class="line">net.ipv4.ip_forward = 0</span><br><span class="line">net.ipv6.conf.all.forwarding = 0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 应用更改，使配置生效</span></span><br><span class="line"><span class="built_in">sudo</span> sysctl -p</span><br></pre></td></tr></table></figure><ul><li>设置 OUTPUT 链默认规则：</li></ul><p>OUTPUT 链管理本机主动发出的数据（出站），一般来说不需要做什么限制，所以保持为全部 ACCEPT 即可。</p><ul><li>设置 INPUT 链规则：</li></ul><p>INPUT 链管理所有外部主机发送到主机的数据（入站），在目的为本机的数据包到达接口后首先经过 INPUT 链过滤。对于入站数据我们只想接收允许的数据包，拒绝或者丢弃其他的数据，所以默认策略应当为 DROP。</p><p>但是考虑到使用 SSH 连接主机，如果先配置默认策略 DROP，SSH 连接会被立即断开，INPUT 规则先设置允许规则，设置完自定义链规则后再设置 INPUT 默认 DROP 规则。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="comment"># 允许状态为RELATED或ESTABLISHED的数据包发送到本机</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT</span><br><span class="line"><span class="comment"># 允许接收来自lo接口的数据包，lo接口为本地回环接口</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -i lo -j ACCEPT</span><br><span class="line"><span class="comment"># 丢弃状态为INVALID的数据包</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -m conntrack --ctstate INVALID -j DROP</span><br><span class="line"><span class="comment"># 允许本机被其他主机ping，接收ICMP类型为Echo Request(8)，状态为NEW的数据包</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -p icmp --icmp-type echo-request -m conntrack --ctstate NEW -j ACCEPT</span><br><span class="line"><span class="comment"># 将接收到的状态为NEW的UDP连接使用自定义的MY_UDP链处理</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -p udp -m conntrack --ctstate NEW -j MY_UDP</span><br><span class="line"><span class="comment"># 将接收到的状态为NEW的TCP SYN连接使用自定义的MY_TCP链处理</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j MY_TCP</span><br><span class="line"><span class="comment"># 拒绝未能匹配之前规则的UDP数据，并且发送一个ICMP端口不可达消息给UDP数据的发送方</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable </span><br><span class="line"><span class="comment"># 拒绝未能匹配之前规则的TCP数据，并且发送TCP RESET数据包立即关闭TCP连接</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset</span><br><span class="line"><span class="comment"># 拒绝其他所有的连接，并且回复发送方ICMP协议不可达</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable</span><br></pre></td></tr></table></figure><ul><li>设置自定义链 MY_TCP，MY_UDP 的规则：</li></ul><p>使用命令 <code>sudo netstat -ntulp</code> 查看有哪些端口正在使用。注意之前在<a href="#3-2-1-%E4%BF%AE%E6%94%B9%E7%9B%91%E5%90%AC%E5%9C%B0%E5%9D%80%E5%92%8C%E7%AB%AF%E5%8F%A3">修改监听地址和端口</a>，将 SSH 的默认端口号由 22 修改为 53156。qBittorrent 的 WebUI 的默认端口为 8080，我这里修改为 8789。</p><p>最终确定需要开放入站连接的端口和协议类型：</p><p>TCP 协议需要允许：SSH 连接（53156）、qBittorrent 的 WebUI（8789）、SMB 连接（445，139）。</p><p>UDP 协议无需开放任何端口入站。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 允许SSH连接</span></span><br><span class="line"><span class="comment"># sudo iptables -A MY_TCP -p tcp --dport 22 -j ACCEPT</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A MY_TCP -p tcp --dport 53156 -j ACCEPT</span><br><span class="line"><span class="comment"># 允许qBittorrent的WebUI</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A MY_TCP -p tcp --dport 8789 -j ACCEPT</span><br><span class="line"><span class="comment"># 允许SMB</span></span><br><span class="line"><span class="built_in">sudo</span> iptables -A MY_TCP -p tcp --dport 139 -j ACCEPT</span><br><span class="line"><span class="built_in">sudo</span> iptables -A MY_TCP -p tcp --dport 445 -j ACCEPT</span><br></pre></td></tr></table></figure><p>最后设置 INPUT 链默认规则为 DROP，将所有未匹配到的数据包丢弃：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> iptables -P INPUT DROP</span><br></pre></td></tr></table></figure><p>至此，<strong>IPv4 防火墙配置完成</strong>。</p><p>使用命令 <code>sudo iptables-save -f iptables.rules</code> 可以将当前的规则保存在文件 <code>iptables.rules</code> 中，按上述设置过程得到的文件内容为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="comment"># Generated by iptables-save v1.8.7 on Thu Nov 28 00:06:39 2024</span></span><br><span class="line">*filter</span><br><span class="line">:INPUT DROP [0:0]</span><br><span class="line">:FORWARD DROP [0:0]</span><br><span class="line">:OUTPUT ACCEPT [0:0]</span><br><span class="line">:MY_TCP - [0:0]</span><br><span class="line">:MY_UDP - [0:0]</span><br><span class="line">-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT</span><br><span class="line">-A INPUT -i lo -j ACCEPT</span><br><span class="line">-A INPUT -m conntrack --ctstate INVALID -j DROP</span><br><span class="line">-A INPUT -p icmp -m icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT</span><br><span class="line">-A INPUT -p udp -m conntrack --ctstate NEW -j MY_UDP</span><br><span class="line">-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j MY_TCP</span><br><span class="line">-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable</span><br><span class="line">-A INPUT -p tcp -j REJECT --reject-with tcp-reset</span><br><span class="line">-A INPUT -j REJECT --reject-with icmp-proto-unreachable</span><br><span class="line">-A MY_TCP -p tcp -m tcp --dport 53156 -j ACCEPT</span><br><span class="line">-A MY_TCP -p tcp -m tcp --dport 8789 -j ACCEPT</span><br><span class="line">-A MY_TCP -p tcp -m tcp --dport 139 -j ACCEPT</span><br><span class="line">-A MY_TCP -p tcp -m tcp --dport 445 -j ACCEPT</span><br><span class="line">COMMIT</span><br><span class="line"><span class="comment"># Completed on Thu Nov 28 00:06:39 2024</span></span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="title"><strong><p>Notes:</p></strong></div><div class="body"><p>这里记录一些我在配置规则过程中的一些问题与理解。</p><p><strong>参数–ctstate 指定的状态是什么？UDP 是无连接协议，为什么会有状态？</strong></p><p>参考：<a href="https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html#USERLANDSTATES">iptables tutorial-数据包在用户空间的状态</a>、<a href="https://manpages.ubuntu.com/manpages/jammy/en/man8/iptables-extensions.8.html">States for --ctstate</a>、<a href="https://www.zsythink.net/archives/1597">iptables 扩展模块之 state 扩展</a></p><p>简单来说，iptables 的状态机制是通过连接跟踪（Connection Tracking）实现，conntrack 是 Linux 内核中负责监控活跃的网络连接的模块。conntrack 依据源 IP、目标 IP、源端口、目标端口等信息确定一个逻辑上的“连接”，不论这个“连接”自身是否维护状态信息，如 TCP 协议自身维护状态信息，需要三次握手建立连接；而 UDP 和 ICMP 协议都是无状态的，自身没有状态信息，也不建立连接。对于这些协议，conntrack 都会维护逻辑上的“连接”的状态。</p><p>连接状态有五种：NEW、ESTABLISHED、RELATED、INVALID、UNTRACKED：</p><ul><li>NEW：conntrack 模块看到的某个连接第一个包，对于 TCP，一般是第一次握手的 SYN 数据包；对于 UDP，如果这个包不属于任何已有的连接就会标记为 NEW；</li><li>ESTABLISHED：某个连接中在 NEW 之后的包都是 ESTABLISHED 状态，发送的 NEW 只要接收了应答就会变为 ESTABLISHED 状态，代表连接已建立；</li><li>RELATED：是一个新的连接，但是与一个状态为 ESTABLISHED 的连接有关；（不是很理解，常举的例子是 FTP-data 连接和 FTP-control 连接）；</li><li>INVALID：conntrack 没有识别数据包的状态和所属连接，一般的处理方式是直接丢弃；</li><li>UNTRACKED：UNTRACKED 状态是由 nftables 引入的，标识那些不经过连接跟踪的包。</li></ul><p>在设置收到 ICMP、UDP、TCP 连接的规则时，都加了参数 <code>--ctstate NEW</code>，即只有连接的第一个数据包会匹配到这一条规则，而接下来的数据包会直接匹配第一条 <code>--ctstate RELATED,ESTABLISHED</code> 规则。</p><p><strong>为什么要使用–reject-with 设置拒绝原因？</strong></p><p>在 <a href="https://wiki.archlinux.org/title/Simple_stateful_firewall">Simple stateful firewall</a> 中有过说明，设置不同的拒绝原因模仿了 Linux 的默认行为，同时明确告知对方通信被禁止而不是等待超时（DROP），有助于调试时清楚的知道连接请求失败的原因。</p><p>当然也可以全部改为 DROP，不明确拒绝，不回应直到请求发送方超时，隐藏本机开放的端口信息。</p><p><strong>为什么没有开放 WireGuard 端口（WireGuard 使用 UDP）？</strong></p><p>WireGuard 的确会监听接口配置文件中 <code>[Interface]</code> 部分中指定的端口，但是对于一个没有公网 IP 的 WireGuard 节点来说，其他节点是无法直接连接这个节点的，<code>[Interface]</code> 部分中的端口甚至可以不设置，接口启动时会监听一个随机端口，用于和中转服务器通信。</p><p>本机的 WireGuard 接口作为“客户端”，需要主动向暴露固定监听端口的服务端发送心跳包维持 WireGuard 隧道连接，所有的连接的 NEW 状态数据包都是本机主动发出，属于出站数据，不受到限制（OUTPUT ACCEPT）。服务器回应的数据包会被视为 ESTABLISHED 状态数据包直接被接收。</p><p>类似的，一般主机发出 DNS 请求（UDP:53，备用 TCP:53）时是 DNS 客户端，也完全不需要单独设置 DNS 请求的响应返回本机时的入站规则，对本机请求的回应入站时的状态是 ESTABLISHED，不会被阻止。（如果本机作为 DNS 服务器，才需要设置目标端口 53 的入站规则）。</p></div></div><h4 id="532-设置-ipv6-防火墙"><a class="markdownIt-Anchor" href="#532-设置-ipv6-防火墙"></a> 5.3.2 设置 IPv6 防火墙</h4><p>和 IPv4 的配置内容基本相同，不过用户空间程序使用 ip6tables，和 ICMP 协议有关的内容更新为 ICMPv6 对应的内容。</p><p>此外，在自定义链中开放了 qBittorrent-nox 监听的端口 51934 的 TCP 和 UDP 入站连接，这样其他 PT 用户才能连接到这台机器：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ip6tables -A MY_TCP -p tcp --dport 51934 -j ACCEPT</span><br><span class="line"><span class="built_in">sudo</span> ip6tables -A MY_UDP -p udp --dport 51934 -j ACCEPT</span><br></pre></td></tr></table></figure><p>类似的，使用使用命令 <code>sudo ip6tables-save -f ip6tables.rules</code> 可以将当前的规则保存在文件 <code>ip6tables.rules</code> 中，得到的文件内容为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="comment"># Generated by ip6tables-save v1.8.7 on Thu Nov 28 01:14:59 2024</span></span><br><span class="line">*filter</span><br><span class="line">:INPUT DROP [0:0]</span><br><span class="line">:FORWARD DROP [0:0]</span><br><span class="line">:OUTPUT ACCEPT [0:0]</span><br><span class="line">:MY_TCP - [0:0]</span><br><span class="line">:MY_UDP - [0:0]</span><br><span class="line">-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT</span><br><span class="line">-A INPUT -i lo -j ACCEPT</span><br><span class="line">-A INPUT -m conntrack --ctstate INVALID -j DROP</span><br><span class="line">-A INPUT -s fe80::/10 -p ipv6-icmp -j ACCEPT</span><br><span class="line">-A INPUT -p ipv6-icmp -m icmp6 --icmpv6-type 128 -m conntrack --ctstate NEW -j ACCEPT</span><br><span class="line">-A INPUT -p udp -m udp --sport 547 --dport 546 -j ACCEPT</span><br><span class="line">-A INPUT -p udp -m conntrack --ctstate NEW -j MY_UDP</span><br><span class="line">-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j MY_TCP</span><br><span class="line">-A INPUT -p udp -j REJECT --reject-with icmp6-adm-prohibited</span><br><span class="line">-A INPUT -p tcp -j REJECT --reject-with tcp-reset</span><br><span class="line">-A INPUT -j REJECT --reject-with icmp6-adm-prohibited</span><br><span class="line">-A MY_TCP -p tcp -m tcp --dport 51934 -j ACCEPT</span><br><span class="line">-A MY_UDP -p udp -m udp --dport 51934 -j ACCEPT</span><br><span class="line">COMMIT</span><br><span class="line"><span class="comment"># Completed on Thu Nov 28 01:14:59 2024</span></span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="title"><strong><p>Notes:</p></strong></div><div class="body"><p>这里对相比于 IPv4 规则明显不同的部分做一些说明。</p><p>新增了两条规则：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-A INPUT -s fe80::/10 -p ipv6-icmp -j ACCEPT</span><br></pre></td></tr></table></figure><p>这条规则接受从链路本地地址 fe80::/10 到达的 ICMPv6 数据包，邻居发现协议 NDP（类似于 IPv4 的 ARP）依赖于 ICMPv6 信息。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-A INPUT -p udp -m udp --sport 547 --dport 546 -j ACCEPT</span><br></pre></td></tr></table></figure><p>这条规则接受从 DHCPv6 服务器（使用 547 端口）发往 DHCPv6 客户端（使用 546 端口）的数据包。如果要使用 DHCPv6 则需要允许数据包入站。</p></div></div><h4 id="533-规则持久化保存"><a class="markdownIt-Anchor" href="#533-规则持久化保存"></a> 5.3.3 规则持久化保存</h4><p>使用 iptables、ip6tables 命令设置的防火墙规则都是临时规则，重启后会被删除，想要持久化保存规则，最简便的办法是用 netfilter-persistent 服务。</p><p>安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install iptables-persistent</span><br><span class="line"><span class="comment"># 确保服务自动启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> netfilter-persistent</span><br></pre></td></tr></table></figure><p>安装过程中会提示是否保存当前的防火墙规则，选择是，保存的规则文件位于 <code>/etc/iptables/rules.v4</code> 和 <code>/etc/iptables/rules.v6</code>。文件的内容和之前使用 <code>iptables-save</code> 命令保存的文件一致。</p><p>以后再修改防火墙规则，可以使用命令手动保存：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> netfilter-persistent save</span><br></pre></td></tr></table></figure><h2 id="6-封禁可疑-ip"><a class="markdownIt-Anchor" href="#6-封禁可疑-ip"></a> 6 封禁可疑 IP</h2><p><a href="https://github.com/fail2ban/fail2ban">Fail2Ban</a> 是一款开源的入侵防护软件，用于保护服务器免受暴力破解等潜在的恶意攻击。它通过监控日志文件以检测出多次失败的登录尝试，并自动添加防火墙规则（如 iptables）来暂时或永久禁止来自恶意 IP 地址的连接。</p><p>这里使用 Fail2Ban 监测 SSH 登录、qBittorrent 的 WebUI 登录和 Samba 登录的失败尝试。</p><h3 id="61-安装-fail2ban"><a class="markdownIt-Anchor" href="#61-安装-fail2ban"></a> 6.1 安装 Fail2Ban</h3><p>直接使用 apt 包管理器安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install fail2ban</span><br><span class="line"><span class="comment"># version 0.11.2</span></span><br></pre></td></tr></table></figure><h3 id="62-默认设置"><a class="markdownIt-Anchor" href="#62-默认设置"></a> 6.2 默认设置</h3><p>Fail2Ban 的基本原理是实时监控由 <code>logpath</code> 指定的日志文件，使用 <code>filter</code> 中的规则匹配日志，如果在 <code>findtime</code> 时段内，匹配次数达到了 <code>maxretry</code>，就会执行 <code>banaction</code>动作封禁 IP，封禁时间由 <code>bantime</code> 定义。其他参数的含义可以参考默认配置文件 <a href="https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf">jail.conf</a> 的注释。</p><p>Fail2Ban 提供了一些默认的日志匹配规则（位于 <code>/etc/fail2ban/filter.d</code>），一些默认封禁动作（位于 <code>/etc/fail2ban/action.d</code>）。</p><p>参考<a href="https://github.com/fail2ban/fail2ban/wiki/Proper-fail2ban-configuration">项目 WiKi-Proper fail2ban configuration</a>，在 <code>/etc/fail2ban/jail.d/</code> 路径下创建自定义配置文件 <code>my.local</code>。这个目录中可能会有自动生成的配置文件，比如我这里有一个 <code>defaults-debian.conf</code>，直接删除即可。</p><p>Fail2Ban 的配置项遵循一定的覆盖规则，总是自定义配置（<code>*.local</code>）覆盖默认配置（<code>jail.conf</code>），针对具体服务的设置（称为一个 jail，监狱）覆盖 <code>[DEFAULT]</code> 中的设置。先创建一个 <code>[DEFAULT]</code> 部分，这部分设置是 SSH、qBittorrent 的 WebUI 和 Samba 三个配置块的“全局”配置。</p><p>使用较为严格的封禁策略，5 分钟内有 3 次失败尝试直接封禁 72 小时。一旦某个 IP 被视为恶意的 IP，那么该 IP 也不应该连接其他端口了，直接使用 iptables-allports 封禁所有端口。当然这是比较激进的封禁策略，也可以使用 iptables-multiport 仅封禁对应服务的端口（如果不是默认端口要用 <code>port</code> 参数指定）。</p><p>自定义配置文件 <code>my.local</code> 内容：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[DEFAULT]</span><br><span class="line"><span class="comment"># 忽略的IP，即不会封禁的IP</span></span><br><span class="line">ignoreip = 127.0.0.1/8</span><br><span class="line"><span class="comment"># 5分钟内有3次失败的尝试封禁72小时</span></span><br><span class="line">maxretry = 3</span><br><span class="line">findtime = 5m</span><br><span class="line">bantime = 72h</span><br><span class="line"><span class="comment"># 使用iptables封禁于该IP的所有端口的连接</span></span><br><span class="line">banaction = iptables-allports</span><br></pre></td></tr></table></figure><h3 id="63-保护-ssh-登录"><a class="markdownIt-Anchor" href="#63-保护-ssh-登录"></a> 6.3 保护 SSH 登录</h3><p>其实 SSH 在修改为不常用的端口和禁止密码登录后已经变得相当安全，不过万一端口被扫到，还是会在系统日志（<code>/var/log/auth.log</code>）中留下一些垃圾记录。SSH 的配置十分简单，直接使用 Fail2Ban 提供的过滤规则即可：</p><p>在自定义配置文件 <code>my.local</code> 中追加 sshd 的 jail 规则：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[sshd]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">port = 53156</span><br><span class="line">filter = sshd</span><br><span class="line">logpath = /var/log/auth.log</span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="body"><ul><li>port：SSH 服务使用的端口，之前自定义为 53156，如果是默认则填 <code>ssh</code> 或 22；</li><li>filter：填 <code>sshd</code> 表示使用 <code>/etc/fail2ban/filter.d/sshd.conf</code> 作为匹配规则文件，<strong>但不要加 .conf 扩展名</strong>。</li></ul></div></div><h3 id="64-保护-qbittorrent-webui-登录"><a class="markdownIt-Anchor" href="#64-保护-qbittorrent-webui-登录"></a> 6.4 保护 qBittorrent-WebUI 登录</h3><p>Fail2Ban 默认的过滤规则中，并没有直接适用于 qBittorrent-WebUI 登录失败的日志，不过我在 <a href="https://github.com/fail2ban/fail2ban/issues/3738">issues#3738</a> 中找到了可以匹配的规则。</p><p>首先在 <code>/etc/fail2ban/filter.d</code> 目录中新建一个规则文件 <code>qbittorrent.conf</code>，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[Definition]</span><br><span class="line">failregex = ^\(W\)\s*-\s*WebAPI login failure\. Reason: [^,]+(?:, (?!IP:)[^,]+)*, IP:\s*&lt;ADDR&gt;, username:\s*&lt;F-USER&gt;\S*&lt;/F-USER&gt;</span><br></pre></td></tr></table></figure><p>这个正则表达式能够匹配这种格式的 WebUI 登录错误日志：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(W) 2024-11-28T01:35:23 - WebAPI login failure. Reason: invalid credentials, attempt count: 1, IP: 192.168.50.100, username: <span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>在自定义配置文件 <code>my.local</code> 中追加 qBittorrent-WebUI 登录的 jail，<code>filter</code> 参数使用我们自己创建的规则文件，qBittorrent 日志目录默认在运行 qBittorrent 的用户的 home 目录中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">[qbittorrent]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">port = 8789</span><br><span class="line">filter = qbittorrent</span><br><span class="line">logpath = /home/ubuntu/.local/share/qBittorrent/logs/qbittorrent.log</span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>默认的 WebUI 的语言设置和区域属性（General\Locale）相关联，界面设置为中文，那么输出的日志也是中文，上述规则无法匹配。我目前使用第三方 WebUI <a href="https://github.com/VueTorrent/VueTorrent">VueTorrent</a>，这个 WebUI 中的界面语言可以单独设置，不影响 Locale 属性。这样原版 UI 中将语言选择为英语，日志用英语输出，在第三方 UI 中设置中文。修改语言后需要<strong>重启</strong>qBittorrent 才能修改日志的语言。</p><p>另外的解决方法就是写一个支持中文的正则表达式去匹配中文日志，<psw>我对正则表达式过敏，写不来这东西</psw>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(W) 2024-11-28T01:49:53 - WebAPI 登录失败。原因：凭证无效，尝试次数：2，IP：192.168.50.100，用户名：<span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>使用以下命令测试规则：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> fail2ban-regex -l heavydebug ~/.local/share/qBittorrent/logs/qbittorrent.log /etc/fail2ban/filter.d/qbittorrent.conf</span><br></pre></td></tr></table></figure><p>最后，qBittorrent 其实也有一个简单的封禁多次登录失败的 IP 的功能，也勉强够用：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1133/523;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184728_bf7330253472fd99.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184728_bf7330253472fd99.webp" alt="qBittorrent 自身的封禁功能" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">qBittorrent 自身的封禁功能</span></div></div></div></div><h3 id="65-保护-samba-登录"><a class="markdownIt-Anchor" href="#65-保护-samba-登录"></a> 6.5 保护 Samba 登录</h3><p>同样 Fail2Ban 默认的过滤规则中，也没有针对 Samba 日志进行过滤匹配的规则，仍然需要自己创建规则。之前的 Samba 配置中对日志部分保持了默认，Samba 默认的日志级别为 0，只输出严重错误和启动停止信息。我们需要修改 Samba 设置输出用户认证的信息。</p><p>Samba 用于输出用户身份验证信息的模块有 auth、auth_audit 和 auth_json_audit，分别设置了一下发现日志输出格式不同。auth 输出详细的验证过程日志，auth_audit 输出的日志是“human_readable”日志，auth_json_audit 使用 JSON 格式输出类似 auth_audit 的内容。</p><p>最容易匹配的是 auth_json_audit 输出的 JSON 格式日志，这里设置常规日志的输出等级为 1，auth_json_audit 输出等级为 3（3：身份认证成功和失败都输出，2：只输出失败日志），auth_json_audit 的日志单独输出到<code>/var/log/samba/auth_json_audit.log</code>。</p><p>在 Samba 配置文件 <code>/etc/samba/smb.conf</code> 的 <code>[global]</code> 部分添加：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">log</span> level = 1 auth_json_audit:3@/var/log/samba/auth_json_audit.log</span><br></pre></td></tr></table></figure><p>然后在 <code>/etc/fail2ban/filter.d</code> 目录中新建一个匹配规则文件 <code>samba.conf</code>，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[Definition]</span><br><span class="line">failregex = NT_STATUS_WRONG_PASSWORD.*remoteAddress<span class="string">&quot;: &quot;</span>ipv4:&lt;HOST&gt;:</span><br><span class="line">            NT_STATUS_NO_SUCH_USER.*remoteAddress<span class="string">&quot;: &quot;</span>ipv4:&lt;HOST&gt;:</span><br></pre></td></tr></table></figure><p>过滤规则参考了 <a href="https://samba.tranquil.it/doc/en/samba_advanced_methods/samba_ad_fail2ban.html">Configuring Fail2ban for Samba-AD</a>，匹配用户名不存在和密码错误两种情况。</p><p>最后在自定义配置文件 <code>my.local</code> 中追加 smbd 的 jail 规则，Samba 在验证身份时会建立两次连接，如果使用全局的 <code>maxretry=3</code> 很容易造成因为输错密码被封 IP，这里覆盖 <code>[DEFAULT]</code> 设置，放宽对失败次数的限制：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[smbd]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">maxretry = 8</span><br><span class="line">port = 139,445</span><br><span class="line">filter = samba</span><br><span class="line">logpath = /var/log/samba/auth_json_audit.log</span><br></pre></td></tr></table></figure><p>还可以给可靠的局域网设备添加白名单（ignoreip 参数），防止被意外封禁。</p><p>最终的 <code>my.local</code> 文件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line">[DEFAULT]</span><br><span class="line"><span class="comment"># 忽略的IP，即不会封禁的IP</span></span><br><span class="line">ignoreip = 127.0.0.1/8</span><br><span class="line"><span class="comment"># 5分钟内有3次失败的尝试封禁72小时</span></span><br><span class="line">maxretry = 3</span><br><span class="line">findtime = 5m</span><br><span class="line">bantime = 72h</span><br><span class="line"><span class="comment"># 使用iptables封禁于该IP的所有端口的连接</span></span><br><span class="line">banaction = iptables-allports</span><br><span class="line"></span><br><span class="line">[sshd]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">port = 53156</span><br><span class="line">filter = sshd</span><br><span class="line">logpath = /var/log/auth.log</span><br><span class="line"></span><br><span class="line">[qbittorrent]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">port = 8789</span><br><span class="line">filter = qbittorrent</span><br><span class="line">logpath = /home/ubuntu/.local/share/qBittorrent/logs/qbittorrent.log</span><br><span class="line"></span><br><span class="line">[smbd]</span><br><span class="line">enabled = <span class="literal">true</span></span><br><span class="line">maxretry = 8</span><br><span class="line">port = 139,445</span><br><span class="line">filter = samba</span><br><span class="line">logpath = /var/log/samba/auth_json_audit.log</span><br></pre></td></tr></table></figure><p>使用命令启动 Fail2Ban，并设置为自启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start fail2ban</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> fail2ban</span><br></pre></td></tr></table></figure><p>Fail2Ban 的日志记录在 <code>/var/log/fail2ban.log</code>，如果启动失败可以查看日志。</p><p>查看所有 jail 的状态，或者查看某个 jail 的状态：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> fail2ban-client status</span><br><span class="line"><span class="built_in">sudo</span> fail2ban-client status sshd</span><br></pre></td></tr></table></figure><p>最后看一下测试效果，我使用一台局域网设备（192.168.50.106）在 WebUI 用错误的用户名和密码登录两次，均被 Fail2Ban 日志记录。然后用 SSH 使用错误密码登录三次，IP 被封禁，此时刷新 WebUI 发现也已经无法访问，符合预期，因为封禁策略使用的是 iptables-allports，直接封禁 IP 的所有端口，查看 iptables 记录可见 Fail2Ban 通过添加一条阻止入站的规则封禁 IP。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2447/380;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184755_a0ec95aaa9689f01.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184755_a0ec95aaa9689f01.webp" alt="fail2ban.log 中记录了 192.168.50.106 的登录失败记录" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">fail2ban.log 中记录了 192.168.50.106 的登录失败记录</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1540/418;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129184853_de9ccee13e66036e.webp" data-src="https://cos.curiositynotes.dev/imgs/241129184853_de9ccee13e66036e.webp" alt="192.168.50.106 设备已被 sshd jail 封禁" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">192.168.50.106 设备已被 sshd jail 封禁</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2561/181;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241129185006_77444b601a63e349.webp" data-src="https://cos.curiositynotes.dev/imgs/241129185006_77444b601a63e349.webp" alt="Fail2Ban 创建的 iptables 规则" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">Fail2Ban 创建的 iptables 规则</span></div></div><p>如果发生了误封，可以手动解封，需要指定 jail 和 IP：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> fail2ban-client <span class="built_in">set</span> &lt;JAIL&gt; unbanip &lt;IP&gt;</span><br></pre></td></tr></table></figure><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>断断续续终于把这篇文章写完了，对于一台工作在局域网的下载机，这些防御手段实在是有些过剩。<span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/ablobcatrainbow.png"/></span></p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://wiki.archlinux.org/title/Security">ArchLinux-Security</a></li><li><a href="https://www.debian.org/doc/manuals/securing-debian-manual/index.en.html">Securing Debian Manual</a></li><li><a href="https://manpages.ubuntu.com/manpages/jammy/en/man5/sshd_config.5.html">manpages-sshd_config.5</a></li><li><a href="https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html">smb.conf — The configuration file for the Samba suite</a></li><li><a href="https://unix.stackexchange.com/questions/529367/samba-is-not-listening-on-specified-wireguard-vpn-interface">Samba is not listening on specified wireguard / vpn interface</a></li><li><a href="https://hartmantam.xyz/hidden/samba-over-tailscale-wireguard-old/">Samba over Tailscale/Wireguard</a></li><li><a href="https://documentation.ubuntu.com/server/how-to/security/firewalls/">Ubuntu-Firewalls</a></li><li><a href="https://wiki.archlinux.org/title/Simple_stateful_firewall">Simple stateful firewall</a></li><li><a href="https://help.ubuntu.com/community/IptablesHowTo">IptablesHowTo</a></li><li><a href="https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html">Iptables 指南</a></li><li><a href="https://quickref.me/zh-CN/docs/iptables.html">iptables 备忘清单</a></li><li><a href="https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf">fail2ban-jail.conf</a></li><li><a href="https://github.com/fail2ban/fail2ban/issues/3738">fail2ban-issues#3738</a></li><li><a href="https://samba.tranquil.it/doc/en/samba_advanced_methods/samba_ad_fail2ban.html">Configuring Fail2ban for Samba-AD</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-11-28：初始版本&lt;/li&gt;
&lt;li&gt;2024-11-30：修复因绑定 WireGuard 接口</summary>
      
    
    
    
    <category term="PT" scheme="https://curiositynotes.dev/categories/PT/"/>
    
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="网络" scheme="https://curiositynotes.dev/tags/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="iptables" scheme="https://curiositynotes.dev/tags/iptables/"/>
    
    <category term="Fail2Ban" scheme="https://curiositynotes.dev/tags/Fail2Ban/"/>
    
  </entry>
  
  <entry>
    <title>搭建一台 PT 下载主机</title>
    <link href="https://curiositynotes.dev/posts/2375099b/"/>
    <id>https://curiositynotes.dev/posts/2375099b/</id>
    <published>2024-11-17T09:50:00.000Z</published>
    <updated>2026-05-20T14:08:16.431Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2024-11-17：初始版本</li><li>2024-11-21：添加了网络结构图和 SMB 方案图</li><li>2024-12-13：在<a href="#6-3-%E8%AE%BE%E7%BD%AEqBittorrent">设置 qBittorrent 部分</a>添加了指向<a href="https://curiositynotes.dev/posts/61024c29.html">不要使用 WebUI 跳过身份验证功能</a>的链接</li></ul><h2 id="1-概述"><a class="markdownIt-Anchor" href="#1-概述"></a> 1 概述</h2><p>最近用一些闲置设备搭建了一个用于 PT 下载的小主机，这里记录一下配置的过程。我用 PT 基本属于养老状态，偶尔下载一些资源，不想花费过多时间和精力去折腾，只是需要一台机器能长期开机保种而已。</p><p>此外由于目前尴尬的网络环境，以及我的硬件都是从闲置的硬件中挑选，一些方案可能比较奇怪，并不符合一般家庭网络场景下的常规选择，也没有性能、性价比和功耗等方面的考虑。</p><p>学校的校园网只提供无线方式接入，直接接入校园网的设备能获取到<code>2001</code>开头的公网 IPv6 地址（全球单播地址）。之前配置过一台 NAS，也在 NAS 上跑过一段时间的教育网 IPv6 PT，当时的方案是编译了带无线网卡驱动的 OpenWRT 软路由，通过无线网卡作为 WAN 口连接校园网，OpenWRT 配置了 IPv6 中继模式，让 LAN 侧设备都能获取到<code>2001</code>开头的 IPv6 地址。但是这个方案最近几个月变得不稳定，NAS 上连接教育网 PT 站还算正常，其他 LAN 侧设备虽然有 IPv6 地址，但无法访问 IPv6 网站，时好时坏，尝试回退软路由的配置到之前的备份也没有解决问题。最近新注册了其他非教育网 PT 站，NAS 也无法访问了。所以就准备单独搭建一个通过无线网卡联网的主机来挂 PT 保种，正好有一台联想 M710q 准系统主机闲置吃灰，也还有几块闲置的硬盘可以用。</p><h3 id="11-硬件组成"><a class="markdownIt-Anchor" href="#11-硬件组成"></a> 1.1 硬件组成</h3><p>主机使用联想 ThinkCentre M710q 准系统，<a href="https://download.lenovo.com/pccbbs/thinkcentre_pdf/m710q_ug_hmm_en.pdf">用户手册</a>，配件情况如下：</p><ul><li>CPU：I3-7100T（与 M710q 一起购买，支持 VT-d，当时想用来安装 PVE）</li><li>内存 1:16G DDR4 内存（旧笔记本拆机）</li><li>内存 2：空</li><li>NVMe1：KIOXIA 512G SSD（旧笔记本拆机）</li><li>NVMe2：主板未焊接，好像可以改 BIOS 支持</li><li>SATA：Transcend 120G SSD（旧笔记本拆机，近十年前的 mSATA 硬盘，用一块转接板转为 SATA 接口）</li><li>网卡 M.2：AX210（本来是买给笔记本用的，装上后经常突然重启）</li><li>USB：前面板 2 个 + 后面板 4 个，USB3.0 接口，接一个硬盘盒，安装了一块笔记本拆机的 2.5 寸 1T HDD</li></ul><p>暂时的规划是用 120G 的 SSD 做系统盘，512G 的 SSD 和 1T 的 HDD 挂载到系统中存储下载的内容。对于我来说，PT 下载的数据存储的可靠性不需要太高，值得收藏的资源下载之后我会尽快同步到 NAS 中存储。</p><div class="tag-plugin colorful note" color="warning"><div class="title"><strong><p>数据无价，谨慎考虑！</p></strong></div><div class="body"><p>下载机存储方案相当草率：</p><p>这块 512G 的 SSD 已经被长期高强度使用，不知道什么时候会罢工，用在这里发挥一下余热；</p><p>USB 硬盘盒是一个极容易数据火葬场的方案，但这块 1T 硬盘同样已经在报废的笔记本中使用了近十年，用在这里刚好。</p></div></div><h3 id="12-软件组成"><a class="markdownIt-Anchor" href="#12-软件组成"></a> 1.2 软件组成</h3><ul><li>Ubuntu Desktop 22.04<ul><li>用户名：<code>ubuntu</code></li></ul></li><li>WireGuard，外网远程访问；</li><li>qBittorrent-nox，PT 下载与保种上传；</li><li>Samba，SMB 共享。</li></ul><h3 id="13-网络结构"><a class="markdownIt-Anchor" href="#13-网络结构"></a> 1.3 网络结构</h3><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:3547/1715;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241126232500_ca0a329ed6415da6.webp" data-src="https://cos.curiositynotes.dev/imgs/241126232500_ca0a329ed6415da6.webp" alt="网络整体结构" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">网络整体结构</span></div></div><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>所有公网地址都是编的，仅用来说明网络结构。</p><p>所有<s>表面上是</s>单播 IPv6 地址（2001 开头）都被校园网防火墙关闭了入站连接，在校园网之外是无法 Ping 的。</p></div></div><p>图中标注为<mark class="tag-plugin colorful mark" color="green">绿色的 IPv6 地址</mark>，是从校园网 AP 获得的能够稳定访问 IPv6 站点的地址。</p><p>标注为<mark class="tag-plugin colorful mark" color="orange">橙色的 IPv6 地址</mark>，是经过 OpenWRT 中继模式获得的 IPv6 地址，具有这些内网地址的设备访问 IPv6 站点并不稳定，故障似乎是近几个月才出现，因为我已经在 NAS 上正常用了一年多的 PT，原因暂时未知。</p><p><strong>这也是我单独搞一个能直接连接校园网 AP，获取 IPv6 地址的下载机的主要原因。</strong></p><p>OpenWRT 路由器通过无线接口 wlan0 连接校园网 AP：</p><ul><li>WAN 侧获得校园网 IPv4 地址（10.192.xx.111）、单播 IPv6 地址（2001:250:20x❌x❌x:111）；</li><li>LAN 侧局域网使用 192.168.50.0/24 地址段，网关 IP 为 192.168.50.1，IPv6 设置为中继模式，局域网设备也能获取到单播 IPv6 地址。</li></ul><p>公网 WireGuard 服务器：</p><ul><li>阿里云小水管服务器，拥有公网 IPv4 地址（39.106.117.205）；</li><li>服务器作为所有没有公网 IP 的 WireGuard 节点的对端设备，其他节点之间的流量由服务器中转。</li><li>WireGuard 虚拟局域网网段为 192.168.70.0/24，服务器自身局域网 IP 为 192.168.70.1；</li><li>使用<a href="https://github.com/wg-easy/wg-easy">WireGuard-Easy</a>搭建 WireGuard 中转服务器，WireGuard-Easy 是一个用 WebUI 管理 WireGuard 节点的简单易用的工具，运行在 docker 容器中。</li><li>WireGuard 中转服务器转发节点之间的数据的防火墙规则由 WireGuard-Easy 设置（容器中），容器外由 docker 开放端口（iptables），云服务器还需要在其控制台防火墙规则中开放对应的端口。</li></ul><p>Ubuntu-PT 下载机：</p><ul><li>通过无线接口 wlp2s0 连接校园网 AP，获得校园网 IPv4 地址（10.192.xx.219）、全球单播 IPv6 地址（2001:250:20x❌x❌x:219）；</li><li>通过有线接口 enp0s31f6 连接路由器 LAN 口，获得局域网 IP 地址（192.168.50.219），主要用于局域网内设备间 SMB 传输文件。禁用 IPv6，不用路由器中继的 IPv6；</li><li>通过接口 wg0 连接公网 WireGuard 服务器，主要用于穿透校园网 AP 隔离，也可以与非校园网设备的互连。</li></ul><h2 id="2-ubuntu-系统安装"><a class="markdownIt-Anchor" href="#2-ubuntu-系统安装"></a> 2 Ubuntu 系统安装</h2><p>系统使用 Ubuntu Desktop 22.04，也可以用 Server 版本，基本所有操作都可以不使用图形界面。</p><p>常规的系统安装，注意选对安装的硬盘全新安装。遇到的问题是之前在 KIOXIA SSD 中安装过 PVE，Ubuntu 安装后启动时会进入 grub rescue 模式，将 KIOXIA SSD 分区表删除即可，这个硬盘里的内容本来也是要清空的。</p><h2 id="3-ubuntu-初始配置"><a class="markdownIt-Anchor" href="#3-ubuntu-初始配置"></a> 3 Ubuntu 初始配置</h2><ul><li><p>设置 root 密码 : <code>sudo passwd root</code></p></li><li><p>连接 WiFi，校园网的 WiFi 身份验证方式一般和普通路由器的 WPA/WPA2-PSK 不同，我这里使用 WPA/WPA2-EAP 加密，EAP 类型选择 PEAP，认证方式选择 EAP-MSCHAPV2，鉴权和密码分别输入校园网的登录用户名和密码，其他设置留空或保持默认即可。</p></li><li><p>修改软件源<a href="https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/">Tsinghua Open Source Mirror</a></p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span>  /etc/apt/sources.list /etc/apt/sources.list_bak</span><br><span class="line"><span class="built_in">sudo</span> vim /etc/apt/sources.list</span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt full-upgrade -y</span><br></pre></td></tr></table></figure></li><li><p>开启 SSH 服务</p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install ssh -y</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> ssh</span><br><span class="line"><span class="comment"># 有关于 SSH 安全性的设置可以最后一起修改，暂时不需要修改配置文件</span></span><br></pre></td></tr></table></figure></li><li><p>安装 Zsh，我习惯 Zsh 终端，安装过程参考之前文章：<a href="https://curiositynotes.dev/posts/d3ff5a5a.html">在 Ubuntu 中安装配置 Zsh</a></p></li></ul><p>至此，完成了一个普通 Ubuntu 系统的安装和简单设置。</p><h2 id="4-使用-wireguard-实现外网访问"><a class="markdownIt-Anchor" href="#4-使用-wireguard-实现外网访问"></a> 4 使用 WireGuard 实现外网访问</h2><p>校园网存在 AP 隔离，不同设备间不能互访，上一小节的配置是在 M710q 上外接显示器和键盘鼠标来完成，现在解决“远程”SSH 连接的问题，让管理 M710q 能脱离显示器。</p><p>使用 WireGuard 是一个比较简单的方案，之前的其他设备互访也都是通过这个方法。WireGuard 是一种简单、高效和现代的虚拟专用网技术，简单理解是可以将不在同一局域网的设备连接到一个虚拟的局域网中实现相互访问，这里不做过多介绍。</p><p>WireGuard 本身不是“服务器 - 客户端”类型的连接，而是“节点 - 节点”的连接，相连的节点是对等的，互为对方的对端（peer）。但是这种情况要求至少有一方有公网 IP，如果两个节点都位于局域网中是无法连接的。这就不得不把 WireGuard 用成了“服务器 - 客户端”的模式，即让一个拥有公网 IP 的服务器充当 WireGuard 服务器节点，这个节点是所有无公网 IP 的节点的对端，无公网 IP 节点之间互访的流量会通过服务器中转。</p><p>我使用的是一个阿里云的小水管（1Mbps）服务器，只能用来连接 SSH，传文件就别想了。服务器使用<a href="https://github.com/wg-easy/wg-easy">WireGuard Easy</a>建立，该工具提供了 WebUI 管理所有对端，设置 Ubuntu 下载机的虚拟子网 IP 后即可下载生成的配置文件 wg0.conf。对于只作为“客户端”的节点来说，不需要转发流量，不需要在配置文件中增加<code>iptables</code>规则，直接使用 WireGuard Easy 生成的配置文件无需修改。</p><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>每个设备都有 IPv6 公网地址，用 IPv6 配置 WireGuard 可以避免服务器中转，两台机器都在校园网有时甚至可以跑满校园网带宽（看运气），但是校园网可能会阻止来自非校园网的入站连接，非校园网环境不可用，<psw>（似乎也有解决办法，探索关键词：校园网免流）</psw> 。</p><p>我的应用场景中一般不需要从下载机远程传输文件，不过多折腾，能连通 SSH 即可。</p></div></div><p>Ubuntu 系统中安装 WireGuard 非常简单，直接用<code>apt</code>安装即可，安装后将配置文件 wg0.conf 移动至<code>/etc/wireguard</code>目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line"><span class="built_in">sudo</span> apt install wireguard -y</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">mv</span> wg0.conf /etc/wireguard</span><br><span class="line"></span><br><span class="line"><span class="comment"># 启动，wg0是WireGuard的接口名，来自于配置文件名</span></span><br><span class="line"><span class="built_in">sudo</span> wg-quick up wg0</span><br><span class="line"><span class="comment"># 停止</span></span><br><span class="line"><span class="built_in">sudo</span> wg-quick down wg0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看接口状态</span></span><br><span class="line"><span class="built_in">sudo</span> wg</span><br><span class="line"><span class="built_in">sudo</span> wg show</span><br><span class="line"></span><br><span class="line"><span class="comment"># 打开和关闭开机自启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> wg-quick@wg0</span><br><span class="line"><span class="built_in">sudo</span> systemctl start wg-quick@wg0</span><br></pre></td></tr></table></figure><p>WireGuard 安装成功后，就可以用加入同一个 WireGuard 局域网的其他设备 SSH 连接到下载机的 WireGuard 局域网 IP 地址了，这样既能够在 AP 隔离的校园网内访问，也可以在非校园网环境访问。</p><h2 id="5-挂载硬盘"><a class="markdownIt-Anchor" href="#5-挂载硬盘"></a> 5 挂载硬盘</h2><p>对于一块全新的（已经删除分区的）硬盘，挂载到 Ubuntu 上的步骤一般为：创建分区，格式化，挂载，设置自动挂载。</p><p>如果硬盘有数据，可能需要删除分区，删除的步骤也在本节最后列出用作参考，不是必须的步骤。</p><p>文件系统选择 ext4，优点是 Linux 可以无痛兼容，缺点是 Windows 不能直接读取。后面会用 SMB 共享的方式让 Windows 来读写文件，不需要硬盘直连到 Windows 电脑上读写。</p><p>将硬盘挂载信息写入<code>/etc/fstab</code>文件，该文件在系统启动时被读入，执行挂载操作。挂载时使用硬盘的 UUID（Universally Unique Identifier），UUID 是固定的，避免了用设备节点名称（如/dev/sda1）挂载时，设备名称变化带来的挂载失败的问题。</p><div class="tag-plugin colorful note" color="warning"><div class="body"><p>涉及数据安全，请务必谨慎操作！</p></div></div><h3 id="51-创建分区"><a class="markdownIt-Anchor" href="#51-创建分区"></a> 5.1 创建分区</h3><p>首先找到空硬盘：<code>sudo fdisk -l</code>或者<code>lsblk</code>，这里需要挂载的硬盘设备名为<code>/dev/nvme0n1</code>。</p><p>使用<code>fdisk</code>分区：<code>sudo fdisk /dev/nvme0n1</code>，用<code>m</code>命令打印帮助，如果只建立一个分区，直接用命令<code>n</code>，按照提示创建分区（保持默认一直回车即可），最后用<code>w</code>命令应用修改。<strong>用 w 保存修改之前，硬盘分区不会发生实际修改，随时可以退出 fdisk</strong>。</p><h3 id="52-格式化"><a class="markdownIt-Anchor" href="#52-格式化"></a> 5.2 格式化</h3><p>指定文件系统：<code>sudo mkfs -t ext4 /dev/nvme0n1</code>。</p><h3 id="53-挂载"><a class="markdownIt-Anchor" href="#53-挂载"></a> 5.3 挂载</h3><p>创建挂载目录，一般在<code>/mnt</code>目录下，我这里创建在当前用户 home 目录下：<code>mkdir -p /home/ubuntu/nvssd</code>。</p><p>挂载：<code>sudo mount /dev/nvme0n1 /home/ubuntu/nvssd</code>。</p><p>检查是否挂载成功：<code>df -hT</code>。</p><p>调整目录权限：挂载后<code>nvssd</code>目录会被修改为 root 所有，普通用户无读写权限。直接修改<code>nvssd</code>目录权限让当前用户 ubuntu 可用读写：<code>sudo chown ubuntu:ubuntu nvssd</code>。</p><h3 id="54-设置自动挂载"><a class="markdownIt-Anchor" href="#54-设置自动挂载"></a> 5.4 设置自动挂载</h3><p>查到硬盘的 UUID：<code>sudo blkid</code>，注意找对应硬盘的 UUID（不是 PARTUUID）。如果没有 UUID，可能是格式化没有成功。必须使用<code>sudo</code>，否则可能看不到所有硬盘。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2229/212;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117185230_1210dd98e1f12c2d.webp" data-src="https://cos.curiositynotes.dev/imgs/241117185230_1210dd98e1f12c2d.webp" alt="找到要挂载硬盘的 UUID" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">找到要挂载硬盘的 UUID</span></div></div><p>编辑 fstab：<code>sudo vim /etc/fstab</code>，在最后一行加入：<strong>（替换为你自己硬盘的 UUID 和挂载路径）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">UUID=6d0a******9c2 /home/ubuntu/nvssd  ext4  auto,nofail,user,rw  0  0</span><br><span class="line"><span class="comment"># nofail 防止硬盘故障无法开机</span></span><br><span class="line"><span class="comment"># user,rw 表示允许其他用户读写</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2000/300;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117185735_ad6b5706cd4e9fd6.webp" data-src="https://cos.curiositynotes.dev/imgs/241117185735_ad6b5706cd4e9fd6.webp" alt="编辑 fstab 文件" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">编辑 fstab 文件</span></div></div><p>测试自动挂载：<code>sudo mount -a</code>，测试前先取消已有的挂载。与手动挂载一样，此时仍可能有权限问题，用同样方法调整：<code>sudo chown ubuntu:ubuntu nvssd</code>。最好重启后用命令<code>df -hT</code>检查是否自动挂载成功。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1471/332;width:600pix;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117185523_d19a591b78269d5e.webp" data-src="https://cos.curiositynotes.dev/imgs/241117185523_d19a591b78269d5e.webp" alt="硬盘已成功挂载" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">硬盘已成功挂载</span></div></div><h3 id="55-其他问题"><a class="markdownIt-Anchor" href="#55-其他问题"></a> 5.5 其他问题</h3><ul><li>取消挂载：<code>sudo umount /home/ubuntu/nvssd</code>，取消挂载时如果当前工作目录（cwd）在挂载目录中会提示挂载目录正忙，取消挂载失败。</li><li>如需挂载 FAT32 和 NTFS 等非 UNIX 权限文件系统，可以通过挂载选项指定<code>uid</code>和<code>gid</code>修复权限问题，<a href="https://zhuanlan.zhihu.com/p/163001267">参考评论区</a>，但是不要用在 EXT4 等文件系统中包含权限信息的文件系统中，这类文件系统直接修改挂载目录的所有权即可。</li><li>删除分区方法：使用<code>fdisk</code>删除分区：<code>sudo fdisk /dev/nvme0n1</code>，输入<code>d</code>命令，输入分区编号依次删除即可，最后用<code>w</code>命令保存。</li></ul><h2 id="6-配置-qbittorrent"><a class="markdownIt-Anchor" href="#6-配置-qbittorrent"></a> 6 配置 qBittorrent</h2><p>最初由于<s>无知</s>安装了普通版本的<a href="https://www.qbittorrent.org/download">qbittorrent</a>，连接显示器导入了大量的种子之后才想起来查一下如何只启动 WebUI 而不启动 GUI，结果发现无 GUI 版本的程序叫<a href="https://github.com/qbittorrent/qBittorrent/wiki/Running-qBittorrent-without-X-server-(WebUI-only,-systemd-service-set-up,-Ubuntu-15.04-or-newer)">qbittorrent-nox</a>。 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs/blobcatsaitama.png"/></span></p><p>幸运的是<code>qbittorrent</code>和<code>qbittorrent-nox</code>可以共存（不能同时运行），默认情况下配置文件和种子信息数据存放路径相同，可以无缝切换。</p><h3 id="61-安装-qbittorrent-nox"><a class="markdownIt-Anchor" href="#61-安装-qbittorrent-nox"></a> 6.1 安装 qbittorrent-nox</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加qBittorrent PPA源</span></span><br><span class="line"><span class="built_in">sudo</span> add-apt-repository ppa:qbittorrent-team/qbittorrent-stable</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装</span></span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install qbittorrent-nox -y</span><br></pre></td></tr></table></figure><p>安装的版本为 qBittorrent v4.5.5，以下设置基于这个版本。</p><h3 id="62-启动-qbittorrent-nox"><a class="markdownIt-Anchor" href="#62-启动-qbittorrent-nox"></a> 6.2 启动 qbittorrent-nox</h3><p>启动 qbittorrent-nox 有两种方式可以选择，使用当前用户直接运行和创建独立的用户运行。</p><p>主要区别是：</p><ul><li>使用当前用户运行：<ul><li>优点：<ul><li>设置和管理较为简单，不需要创建新用户，也不需要配置权限；</li><li>下载的文件直接在当前用户的权限范围内，访问和修改都较为方便。</li></ul></li><li>缺点：<ul><li>安全性较低，权限较大。如果 qbittorrent-nox 被利用，攻击者可能获取当前用户的所有权限。</li></ul></li></ul></li><li>使用独立的用户运行<ul><li>优点：<ul><li>可以限制 qbittorrent-nox 仅拥有必要的权限，降低安全风险。</li></ul></li><li>缺点：<ul><li>文件访问不便，qbittorrent-nox 只能往新用户有写入权限的目录下载文件；并且下载的文件权限属于新用户，需要额外的权限调整才能让其他用户读写下载的文件。</li></ul></li></ul></li></ul><p>考虑到下载机是我个人使用，不会将其暴露在公网，安全风险不是很高，简单起见直接用当前用户运行，不过下面仍然给出两种方式的操作步骤用作参考，选择任意一种即可。</p><h4 id="使用当前用户运行"><a class="markdownIt-Anchor" href="#使用当前用户运行"></a> 使用当前用户运行</h4><ul><li>运行</li></ul><p>运行：<code>qbittorrent-nox</code>，此时可以访问 WebUI。默认用户<code>admin</code>，密码<code>adminadmin</code>，设置信息和种子数据保存在当前用户的 home 目录中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">QBT_DATA_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.local/share/qBittorrent/BT_backup&quot;</span></span><br><span class="line">QBT_CONFIG_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.config/qBittorrent&quot;</span></span><br></pre></td></tr></table></figure><ul><li>设置后台运行及开机自启动：</li></ul><p>较新版本的 qBittorrent 提供了 systemd 的配置文件<code>/usr/lib/systemd/system/qbittorrent-nox@.service</code>，无需自己编写，运行时需要指定一个启动程序的用户作为参数。<br />这个文件在：<a href="https://github.com/qbittorrent/qBittorrent/blob/master/dist/unix/systemd/qbittorrent-nox%40.service.in">github：qbittorrent-nox%40.service.in</a>，如果系统中没有可以直接在这里复制。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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><span class="line"><span class="comment"># 启动，&#x27;@&#x27;后为指定的参数，由哪个用户运行 qbittorrent-nox</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl start qbittorrent-nox@ubuntu</span><br><span class="line"><span class="comment"># 停止</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl stop qbittorrent-nox@ubuntu</span><br><span class="line"><span class="comment"># 查看状态</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status qbittorrent-nox@ubuntu</span><br><span class="line"><span class="comment"># 开机自启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> qbittorrent-nox@ubuntu</span><br><span class="line"><span class="comment"># 关闭开机自启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> qbittorrent-nox@ubuntu</span><br></pre></td></tr></table></figure><h4 id="使用独立的用户运行"><a class="markdownIt-Anchor" href="#使用独立的用户运行"></a> 使用独立的用户运行</h4><ul><li>创建用户</li></ul><p>创建一个非特权的新用户来运行 qbittorrent-nox，这里指定<code>--system</code>参数创建系统用户，系统用户拥有更少的权限，默认 shell 是<code>/usr/sbin/nologin</code>，即不允许从终端和 SSH 登录，也不需要密码。用<code>--group</code>参数创建用户组，对于系统用户如果不指定则会不创建新组。添加<code>--home</code>参数创建用户 home 目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> adduser --system --group --home /home/qbtuser qbtuser</span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>注意创建用户命令<code>adduser</code>和<code>useradd</code>是不同的两个命令，<code>useradd</code>更底层，<code>adduser</code>更友好一些；</p><p>创建用户组是为了后面协助解决权限问题；</p><p>需要 home 目录是因为 qbittorrent-nox 启动时会在启动用户的 home 目录保存设置信息和种子数据；</p><p>注意建立系统用户在不同系统可能有不同的行为，提前用 <code>man adduser</code>确认。如 Debian12 中如果不指定<code>--home</code>则不会创建 home 目录，但在 Ubuntu22.04 中，不指定<code>--home</code>也会创建 home 目录。</p></div></div><p>如果没有指定<code>--system</code>参数，创建的是普通用户，需禁止用户从终端和 SSH 登录，使用命令<code>sudo usermod -s /usr/sbin/nologin qbtuser</code>，如需要恢复该用户的登录权限，使用命令<code>sudo usermod -s /bin/bash qbtuser</code>。</p><ul><li>解决权限问题</li></ul><p>一般可能会遇到两处权限问题：</p><ol><li>使用 qbtuser 启动的程序只能往 qbtuser 具有写入权限的目录中下载文件。</li><li>使用 qbtuser 启动的程序下载的文件权限属于 qbtuser，其他用户不能访问。</li></ol><p>如果下载的东西都存在 qbtuser home 目录中，且 home 目录足够大，第一个问题不需要处理。</p><p>如果需要向其他用户拥有的目录下载，比如我使用当前用户 ubuntu 挂载的硬盘目录<code>nvssd</code>，需要单独设置挂载目录的写入权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> setfacl -R -m <span class="string">&quot;u:qbtuser:rwx&quot;</span> /home/ubuntu/nvssd</span><br></pre></td></tr></table></figure><p>该命令递归地给予用户 qbtuser 对目录<code>/home/ubuntu/nvssd</code>及其所有子目录和文件的读、写和执行权限，允许用户 qbtuser 完全访问和操作这些文件和目录，而不影响其他用户的权限设置。</p><p>也可以像<a href="#5-%E6%8C%82%E8%BD%BD%E7%A1%AC%E7%9B%98">挂载硬盘</a>中提到的那样，使用<code>sudo chown qbtuser:qbtuser /home/ubuntu/nvssd</code>将挂载目录的所有权转移至 qbtuser 用户，但这样又会让其他用户（如当前用户 ubuntu）无法访问<code>nvssd</code>，也就是变成了第二个问题那种情况，需要进一步将需要读取文件的用户加入到 qbtuser 用户组，让其他用户可以访问属于 qbtuser 的文件和目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用adduser将已存在的用户ubuntu加入到已存在的用户组qbtuser中</span></span><br><span class="line"><span class="built_in">sudo</span> adduser ubuntu qbtuser</span><br><span class="line"><span class="comment"># 或者使用usermod命令 -a表示append</span></span><br><span class="line"><span class="built_in">sudo</span> usermod -aG qbtuser ubuntu</span><br><span class="line"><span class="comment"># 查看用户是否成功加入组</span></span><br><span class="line"><span class="built_in">groups</span> ubuntu</span><br></pre></td></tr></table></figure><ul><li>运行及开机自动运行</li></ul><p>参考<a href="#%E4%BD%BF%E7%94%A8%E5%BD%93%E5%89%8D%E7%94%A8%E6%88%B7%E8%BF%90%E8%A1%8C">使用当前用户运行</a>中的命令，将所有用户名替换成新的系统用户用户名 qbtuser。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start qbittorrent-nox@qbtuser</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> qbittorrent-nox@qbtuser</span><br></pre></td></tr></table></figure><h3 id="63-设置-qbittorrent"><a class="markdownIt-Anchor" href="#63-设置-qbittorrent"></a> 6.3 设置 qBittorrent</h3><p>设置 qBittorrent 在 WebUI 中进行，这部分设置内容需要<strong>参考 PT 站的要求</strong>结合个人习惯设置，这里列出部分我觉得有用的设置。</p><blockquote><p>部分设置参考了用户 <em>@ColderCoder</em> 发布在各 PT 站的帖子，在此表示感谢！</p></blockquote><ul><li>下载页面<ul><li>勾选为所有文件预分配磁盘空间；</li><li>修改默认保存路径到挂载的硬盘目录；</li></ul></li><li>连接页面<ul><li>监听端口设置为 10000-65535 之间的数，不要过小，也不要随机；</li><li>取消勾选使用 UPnP/NAT-PMP，自己在路由器设置端口转发；</li><li>IP 过滤可以写一个<code>.dat</code>文件过滤掉所有 IPv4 地址，文件内容只需写入：<code>0.0.0.0-255.255.255.255</code>即可；</li></ul></li><li>速度页面<ul><li>有些 PT 站会有最大速度要求，可以在这里加全局限制防止违规被 ban；</li></ul></li><li>BitTorrent 页面<ul><li>隐私部分<strong>全部取消勾选</strong>（针对 PT）；</li><li>取消勾选 Torrent 排队；</li><li>取消做种限制（按需）；</li></ul></li><li>RSS 页面<ul><li>考虑到近期 qBittorrent 的<a href="https://sharpsec.run/rce-vulnerability-in-qbittorrent/">漏洞</a>，取消勾选所有 RSS 功能；</li></ul></li><li>WebUI 页面<ul><li>设置语言为中文；</li><li>端口修改为不常用的端口；</li><li>取消勾选使用 UPnP/NAT-PMP，自己设置端口转发，使用 WireGuard 时，通过虚拟专用网络隧道访问，无需端口转发；</li><li>建议启用 HTTPS<a class="tag-plugin colorful hashtag" color="yellow"><svg t="1701408144765" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4228" width="200" height="200"><path d="M426.6 64.8c34.8 5.8 58.4 38.8 52.6 73.6l-19.6 117.6h190.2l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6s58.4 38.8 52.6 73.6l-19.4 117.6H896c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-42.6 256H832c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.6-117.4h-190.4l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.4-117.8H128c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l42.6-256H192c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6z m11.6 319.2l-42.6 256h190.2l42.6-256h-190.2z" p-id="4229"></path></svg><span>TODO</span></a>；即便是用 WireGuard，仍然建议配置 HTTPS（<em>来自 AI 的建议，但我觉得没什么用</em>）；</li><li>验证部分的用户名和密码就是登录 WebUI 的用户名和密码，建议设置复杂密码；</li><li><strong>不要开启两个跳过身份验证功能</strong>；为什么<a href="https://curiositynotes.dev/posts/61024c29.html">不要使用 qBittorrent WebUI 的跳过身份验证功能</a>？</li></ul></li><li>高级页面<ul><li>网络接口选为无线网络接口，即有 IPv6 地址的接口；</li><li>绑定到的可选 IP 地址选为所有 IPv6；</li><li>磁盘 IO 类型设置为 POSIX 可以减少内存占用，需要 libtorrent 版本在 2.0 及以上，用<code>ldd /usr/bin/qbittorrent-nox | grep libtorrent</code>查看 qbittorrent-nox 依赖的库版本；</li><li>勾选允许来自同一 IP 地址的多个连接；</li><li>增大最大并行 HTTP 发布，直接加两个 0；</li></ul></li></ul><p>标记为<a class="tag-plugin colorful hashtag" color="yellow"><svg t="1701408144765" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4228" width="200" height="200"><path d="M426.6 64.8c34.8 5.8 58.4 38.8 52.6 73.6l-19.6 117.6h190.2l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6s58.4 38.8 52.6 73.6l-19.4 117.6H896c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-42.6 256H832c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.6-117.4h-190.4l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.4-117.8H128c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l42.6-256H192c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6z m11.6 319.2l-42.6 256h190.2l42.6-256h-190.2z" p-id="4229"></path></svg><span>TODO</span></a>的内容以<s>可能</s>后会单独补充。</p><div class="tag-plugin colorful note" color="warning"><div class="title">再次提醒</div><div class="body"><p>qBittorrent 的设置建议参考 PT 站的新手教程，谨慎修改不知道是什么意思的设置。</p></div></div><h3 id="64-备份与恢复"><a class="markdownIt-Anchor" href="#64-备份与恢复"></a> 6.4 备份与恢复</h3><p>在<a href="#%E4%BD%BF%E7%94%A8%E5%BD%93%E5%89%8D%E7%94%A8%E6%88%B7%E8%BF%90%E8%A1%8C">使用当前用户运行</a>部分提到过 qBittorrent 的设置信息和种子数据保存在当前用户的 home 目录中：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">QBT_DATA_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.local/share/qBittorrent/BT_backup&quot;</span></span><br><span class="line">QBT_CONFIG_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.config/qBittorrent&quot;</span></span><br></pre></td></tr></table></figure><p>qBittorrent 的备份和恢复只需要关心这两个目录中的文件。这里分享一个 ChatGPT 帮我写的备份程序，使用前注意修改为你自己的路径：</p><p>备份<code>qBittorrent_backup.sh</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># 指定保存备份文件的目录</span></span><br><span class="line">BACKUP_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/qbittorrent_backup&quot;</span></span><br><span class="line"><span class="comment"># 确保备份目录存在，不存在则创建</span></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="string">&quot;<span class="variable">$BACKUP_DIR</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">mkdir</span> -p <span class="string">&quot;<span class="variable">$BACKUP_DIR</span>&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="comment"># 需要备份的qBittorrent设置和数据目录</span></span><br><span class="line">QBT_DATA_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.local/share/qBittorrent/BT_backup&quot;</span></span><br><span class="line">QBT_CONFIG_DIR=<span class="string">&quot;<span class="variable">$HOME</span>/.config/qBittorrent&quot;</span></span><br><span class="line"><span class="comment"># 检查目录是否存在</span></span><br><span class="line"><span class="keyword">if</span> [ ! -d <span class="string">&quot;<span class="variable">$QBT_DATA_DIR</span>&quot;</span> ] || [ ! -d <span class="string">&quot;<span class="variable">$QBT_CONFIG_DIR</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;qBittorrent data or config directory does not exist.&quot;</span></span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="comment"># 获取当前时间戳</span></span><br><span class="line">TIMESTAMP=$(<span class="built_in">date</span> +%Y%m%d%H%M%S)</span><br><span class="line"><span class="comment"># 备份，备份文件名添加备份时的时间戳</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Starting backup of qBittorrent settings and data...&quot;</span></span><br><span class="line">tar -czf <span class="string">&quot;<span class="variable">$BACKUP_DIR</span>/qbittorrent_data_<span class="variable">$&#123;TIMESTAMP&#125;</span>.tar.gz&quot;</span> -C <span class="string">&quot;<span class="variable">$QBT_DATA_DIR</span>&quot;</span> .</span><br><span class="line">tar -czf <span class="string">&quot;<span class="variable">$BACKUP_DIR</span>/qbittorrent_config_<span class="variable">$&#123;TIMESTAMP&#125;</span>.tar.gz&quot;</span> -C <span class="string">&quot;<span class="variable">$QBT_CONFIG_DIR</span>&quot;</span> .</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Backup completed successfully.&quot;</span></span><br></pre></td></tr></table></figure><h2 id="7-配置-smb-共享"><a class="markdownIt-Anchor" href="#7-配置-smb-共享"></a> 7 配置 SMB 共享</h2><p>开始提到我还有一台 OpenWRT 软路由在使用中，给我的一些其他设备提供了一个局域网环境，主要是给一些不能直接连接校园网 WiFi 的物联网设备提供网络，也可以让其他局域网主机连接 NAS。这台下载机同样需要接入到路由器的局域网中，因为我需要用 Windows 主机查看下载的内容，也需要将值得收藏的资源传输到 NAS 中保存。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2678/1312;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241126232618_2f7c6b4db71d12d1.webp" data-src="https://cos.curiositynotes.dev/imgs/241126232618_2f7c6b4db71d12d1.webp" alt="SMB 部分网络结构" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">SMB 部分网络结构</span></div></div><p>网络结构是下载机使用网线连接路由器，网线接口只用于下载机和内网其他主机（Windows 主机、NAS）之间传输文件。下载机的下载和上传以及连接 PT 站通过无线网卡直接连接校园网 WiFi 获取 IPv6。</p><p>原本计划在下载机配置 SMB 共享，将下载目录共享出去，Windows 主机和 NAS 挂载这个共享目录，可以实现 Windows 主机读写下载机、NAS 读写下载机的目的。但是实际测试发现 NAS 挂载 CIFS（SMB）共享目录并不稳定，传输文件时会经常卡住。</p><p>最后方案修改为：</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1767/1424;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241121215148_5084237524c9bec6.webp" data-src="https://cos.curiositynotes.dev/imgs/241121215148_5084237524c9bec6.webp" alt="最终 SMB 共享方案" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">最终 SMB 共享方案</span></div></div><ul><li>下载机通过 SMB 共享下载目录<code>/home/ubuntu/nvssd</code>，实现与 Windows 主机的文件传输；</li><li>NAS 中创建一个单独的目录<code>/PTShare</code>和一个低权限（只对<code>/PTShare</code>目录有读写权限）的 SMB 用户<code>ptuser</code>，专门用于下载机与 NAS 之间的文件传输，由下载机将 NAS 的共享目录挂载到<code>/home/ubuntu/NAS_PTShare</code>。</li></ul><p>该方案虽然比较“将就”，但测试下来还是能够稳定的满足需求的。</p><p>下面<a href="#7-1-%E5%AE%89%E8%A3%85SMB%E6%9C%8D%E5%8A%A1">7.1</a>、<a href="#7-2-%E4%BF%AE%E6%94%B9Samba%E9%85%8D%E7%BD%AE">7.2</a>、<a href="#7-3-%E5%90%AF%E5%8A%A8Samba%E6%9C%8D%E5%8A%A1">7.3</a>和<a href="#7-4-%E4%BF%AE%E5%A4%8D%E9%BB%98%E8%AE%A4%E8%B7%AF%E7%94%B1%E9%97%AE%E9%A2%98">7.4</a>介绍下载机作为 SMB 服务端，安装并配置 SMB 服务，让 Windows 客户端能够访问下载机的 SMB 共享目录。<a href="#7-5-%E6%8C%82%E8%BD%BD%E5%85%B6%E4%BB%96%E4%B8%BB%E6%9C%BA%E7%9A%84SMB%E5%85%B1%E4%BA%AB">7.5</a>介绍下载机作为 SMB 客户端，挂载 NAS 上 SMB 服务器共享的目录。</p><h3 id="71-安装-samba"><a class="markdownIt-Anchor" href="#71-安装-samba"></a> 7.1 安装 Samba</h3><p>SMB 协议是“客户端 - 服务器”类型的协议，客户端通过该协议可以访问服务器上的共享文件系统，打印机等资源。Samba 是在 Linux 和 UNIX 系统上实现 SMB 协议的一个免费软件。在 Ubuntu 系统中可以直接使用<code>apt</code>包管理器安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install samba -y</span><br></pre></td></tr></table></figure><h3 id="72-修改-samba-配置"><a class="markdownIt-Anchor" href="#72-修改-samba-配置"></a> 7.2 修改 Samba 配置</h3><p>Samba 的配置文件位于<code>/etc/samba/smb.conf</code>，修改后可以使用命令<code>testparm</code>来测试配置文件是否存在错误，修改配置文件后建议重启 Samba 服务使文件修改生效。</p><p>配置文件由若干小节（section）组成，默认配置文件中一般包含<code>[global]</code>、<code>[homes]</code>、<code>[printers]</code>和<code>[print$]</code>。在我安装的版本中（Version 4.15.13-Ubuntu），<code>[homes]</code>部分已经被注释掉了，在一些旧版本中，<code>[homes]</code>部分默认是开启的。</p><p><code>[global]</code>部分定义了一些全局配置，通常是规定了 Samba 服务器的行为，也可能包含一些“共享资源”参数的默认值。除<code>[global]</code>之外的所有小节都定义了一个“共享资源”，这些共享资源要么是文件共享服务（即将创建的共享目录就是这种），要么是可打印服务（如<code>[printers]</code>和<code>[print$]</code>）。</p><p>首先修改<code>[global]</code>部分，修改两处原有的配置，追加两条新增的配置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 原配置文件中为yes，这里改为no；</span></span><br><span class="line"><span class="comment"># 这个配置会影响后面创建文件和目录的权限设置，修改为no即可</span></span><br><span class="line">obey pam restrictions = no</span><br><span class="line"></span><br><span class="line"><span class="comment"># 原配置文件中为yes，这里改为no；</span></span><br><span class="line"><span class="comment"># 该配置允许共享在没有身份验证的情况下被访问，通常用于让网络中的任何人都能访问某些公开的资源，不需要输入用户名和密码</span></span><br><span class="line">usershare allow guests = no</span><br><span class="line"></span><br><span class="line"><span class="comment"># 以下两条配置为新增配置，放在[global]部分最后即可</span></span><br><span class="line"><span class="comment"># 设置SMB协议版本不低于SMB3，设置前确保所有客户端都支持SMB3</span></span><br><span class="line">server min protocol=SMB3</span><br><span class="line"><span class="comment"># 安全模式设置为user，要求连接的客户端提供有效的用户名和密码，实际上这是默认的安全模式，不设置这一条也可以</span></span><br><span class="line">security=user</span><br></pre></td></tr></table></figure><p>如果客户端挂载时出现连接问题，可以考虑删除<code>server min protocol=SMB3</code>试一下，我在用 NAS 挂载下载机的 SMB 共享时，NAS 上的 SMB 客户端就不支持 SMB3 协议。</p><p>然后处理<code>[homes]</code>、<code>[printers]</code>和<code>[print$]</code>，如果没有明确的理由需要使用这些功能，建议全部注释掉以关闭 home 目录的共享和打印机共享。我这里全部注释。</p><p>最后增加要共享的目录，我这里直接将硬盘的挂载目录共享出去，以目录<code>/home/ubuntu/nvssd</code>为例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line">[NVSSD]</span><br><span class="line">    <span class="comment"># 资源描述信息</span></span><br><span class="line">    comment = NVMe SSD 500G</span><br><span class="line">    <span class="comment"># 共享的目录</span></span><br><span class="line">    path = /home/ubuntu/nvssd</span><br><span class="line">    <span class="comment"># 允许连接的主机</span></span><br><span class="line">    hosts deny = 0.0.0.0/0</span><br><span class="line">    hosts allow = 192.168.50.0/24 192.168.70.0/24</span><br><span class="line">    <span class="comment"># 允许访问的用户（多用户用逗号隔开，&quot;@组名&quot;可以指定一组用户）</span></span><br><span class="line">    valid <span class="built_in">users</span> = ubuntu</span><br><span class="line">    <span class="comment"># 禁止访问的用户</span></span><br><span class="line">    <span class="comment"># invalid users = root</span></span><br><span class="line">    <span class="comment"># 允许写入的用户</span></span><br><span class="line">    <span class="comment"># write list = ubuntu</span></span><br><span class="line">    <span class="comment"># 共享目录可被浏览（</span></span><br><span class="line">    <span class="comment"># 设置为no后访问主机地址看不到path目录，但访问包含目录的地址可以打开目录</span></span><br><span class="line">    browseable = <span class="built_in">yes</span></span><br><span class="line">    <span class="comment"># 共享目录是否可写</span></span><br><span class="line">    writable = <span class="built_in">yes</span></span><br><span class="line">    <span class="comment"># 是否允许guest用户访问</span></span><br><span class="line">    guest ok = no</span><br><span class="line">    <span class="comment"># 新建文件的最高权限（DOS权限-&gt;UNIX权限后与该参数按位与得到UNIX权限）</span></span><br><span class="line">    <span class="comment"># 文件0664与Ubuntu默认用户权限相同</span></span><br><span class="line">    create mode = 0664</span><br><span class="line">    <span class="comment"># 新建目录的最高权限（DOS权限-&gt;UNIX权限后与该参数按位与得到UNIX权限）</span></span><br><span class="line">    <span class="comment"># 目录0775与Ubuntu默认用户权限相同</span></span><br><span class="line">    directory mode = 0775</span><br><span class="line">    <span class="comment"># 新建文件的最低权限（将上一步得到的UNIX权限与该参数按位或得到最终权限）</span></span><br><span class="line">    <span class="comment"># 默认值为0000，Windows创建的权限与Linux显示的权限不一致时可以启用该参数强制修改权限</span></span><br><span class="line">    force create mode = 0664</span><br><span class="line">    <span class="comment"># 新建目录的最低权限（将上一步得到的UNIX权限与该参数按位或得到最终权限）</span></span><br><span class="line">    <span class="comment"># 默认值为0000，Windows创建的权限与Linux显示的权限不一致时可以启用该参数强制修改权限</span></span><br><span class="line">    force directory mode = 0775</span><br></pre></td></tr></table></figure><p>设置项的含义和用途基本已在注释中说明，这里说明两处细节：</p><ul><li><strong>访问控制</strong></li></ul><p>出于安全性考虑，使用<code>hosts deny</code>和<code>hosts allow</code>限定了允许连接的主机 IP 段，即先拒绝所有 IP 的连接，然后放通内网网段（192.168.50.0/24）和 WireGuard 内网网段（192.168.70.0/24）的连接。</p><p><code>hosts deny</code>和<code>hosts allow</code>也可以在<code>[global]</code>部分设置，但是与其他参数惯用的：“共享资源”中的设置优先于（覆盖）“全局默认”的设置不同，对于可连接的主机设置是<code>[global]</code>会覆盖“共享资源”中对应的条目。此外如果再加上仅有<code>hosts deny</code>或者仅有<code>hosts allow</code>时的特殊规则，会让 Samba 的访问控制逻辑变得十分复杂。Samba 访问控制的详细分析可以访问文末相关的参考链接。</p><p>配置后可以使用命令<code>testparm /etc/samba/smb.conf pc 192.168.1.11</code>测试配置文件中设置的访问控制的有效性，<code>pc</code>是待测试的客户端主机名，可以使用任意字符串，<code>192.168.1.11</code>是待测试的客户端的 IP，可以测试当前的客户端 IP 能否访问共享的资源。</p><p>我这里用简单明确的方式，不在<code>[global]</code>设置，仅在具体的“共享资源”中设置，缺点就是如果需要修改则需要逐个修改所有共享。</p><ul><li><strong>权限控制</strong></li></ul><p>配置文件中使用了<code>create mode</code>和<code>directory mode</code>分别控制在共享目录中创建文件和目录的权限，按照<a href="https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html#CREATEMASK">文档</a>描述，新建的文件或目录的权限是在完成 DOS 权限模型到 UNIX 权限模型的映射后，将得到的权限值与该参数<strong>按位与</strong>得到新建文件的权限。</p><p>二进制运算中，经常用按位与运算<code>x = x &amp; (~mask)</code>来将<code>x</code>中对应<code>mask</code>为 1 的那些位修改为 0，而不影响对应<code>mask</code>为 0 的那些位，这里将映射后的权限与该参数按位与，就是将该参数没有指定的（为 0 的）那些权限移除，计算后的权限一定不包含该参数没有的那些权限，可以认为这个参数限制了新建文件和目录的最高权限。</p><p>文档中同时说明了<code>force create mode</code>和<code>force directory mode</code>的用途，即将<code>create mode</code>和<code>directory mode</code>产生的权限结果再和<code>force</code>参数做一次<strong>按位或</strong>，按位或运算<code>x = x | mask</code>用于将<code>x</code>中对应<code>mask</code>为 1 的那些位修改为 1，而不影响对应<code>mask</code>为 0 的那些位，这里使用按位或运算添加了该参数所指定的权限，也就是最终权限中一定包含该参数所指定的那些权限。可以认为这个参数限制了新建文件和目录的最低权限。</p><p>总结：</p><ul><li><code>create mode</code>和<code>directory mode</code>用来移除权限，客户端创建文件或目录的权限经过这两个参数移除之后，得到不超过这两个参数的权限；</li><li><code>force create mode</code>和<code>force directory mode</code>用来增加权限，上一步得到的权限经过这两个参数增加后，得到不低于这两个参数指定的权限；</li><li><code>create mode</code>和<code>directory mode</code>设置的权限高于<code>force create mode</code>和<code>force directory mode</code>设置的权限是没有意义的。</li></ul><p>举个例子：Windows 客户端创建一个文件，假定初始权限是 644（我并没有深入了解 DOS 权限到 UNIX 权限是如何转换的，这里仅假设一个权限说明权限参数的作用），文件所有者有读写权限，组内用户和其他用户有读权限。首先和<code>create mask = 0604</code><strong>按位与</strong>得到权限为 0604，移除了组用户的读权限，然后和<code>force create mode = 0741</code>做<strong>按位或</strong>运算得到最终权限 0745，给所有者和其他用户添加了可执行权限，给组内用户增加了可执行权限。</p><p>可以用计算器（八进制模式）计算结果。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:557/294;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117175915_93e6e5c1a1c8a820.webp" data-src="https://cos.curiositynotes.dev/imgs/241117175915_93e6e5c1a1c8a820.webp" alt="计算权限结果" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">计算权限结果</span></div></div><p>也可以按照以上例子中的参数设置，新建文件验证文件权限（<em>但是这个设置完全是我为了举例子瞎编的，完全不合理，测试一下即可，不要使用！</em>）。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1191/89;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180048_c294152224d6ab49.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180048_c294152224d6ab49.webp" alt="新建文件测试权限设置有效" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">新建文件测试权限设置有效</span></div></div><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>这里有一个坑点，文档中说明<code>create mask</code>等同于<code>create mode</code>，<code>directory mask</code>等同于<code>directory mode</code>。但是，带<code>force</code>的参数只有<code>force create mode</code>和<code>force directory mode</code>，没有<code>force create mask</code>和<code>force directory mask</code>。</p><p>错误的使用了<code>force create mask</code>和<code>force directory mask</code>在<code>testparm</code>时会提示未知参数被忽略，但我起初没注意到这个错误提示……</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1142/495;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180226_4f6f273b40453d78.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180226_4f6f273b40453d78.webp" alt="提示 force create mask 是未知参数" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">提示 force create mask 是未知参数</span></div></div><p>另外在<code>[global]</code>设置处提到，如果设置了<code>obey pam restrictions = yes</code>，最终权限也可能不符合预期计算的结果，<a href="https://serverfault.com/questions/645081/samba-permissions-being-ignored">参考：Samba permissions being ignored</a>。</p></div></div><p>实际上，搭配使用不带<code>force</code>的参数和带<code>force</code>的参数，不论初始权限是什么最终权限总能修改成所需要的权限。</p><p>我这里将两参数指定为相同的权限，最终得到的效果就是不论是从什么类型的客户端新建或上传文件，权限都会强制修改为 Ubuntu 当前用户 ubuntu 的默认权限，文件为 664，目录为 775。</p><div class="tag-plugin colorful note" color="cyan"><div class="title"><strong><p>Ubuntu 用户的新建文件和目录的权限是怎么得到的？</p></strong></div><div class="body"><p>Linux 用户新建文件的默认权限是用 umask（用户文件创建模式掩码）计算的，通过屏蔽掉特定权限位来决定新文件和目录的默认权限。可以用<code>umask</code>命令查看当前用户的 umask 值，也可以指定参数<code>umask -S</code>查看 umask 值对应的权限信息。</p><p>Ubuntu 默认的<code>umask=002</code>：</p><ul><li>对于目录，起始权限为<code>777</code>，目录有可执行权限表示被打开，用<code>umask=002</code>表示移除了其他用户的写权限，得到最终权限<code>775</code>；</li><li>对于文件，起始权限为<code>666</code>，因为大部分文件不需要执行权限，用<code>umask=002</code>表示移除了其他用户的写权限，得到最终权限<code>664</code>；</li></ul><p>计算过程是起始权限<code>mode</code>与<code>umask</code>按位取反的值按位与，即<code>mode &amp; (~umask)</code>，也可以直接理解成<code>mode - umask</code>。</p><p>所以，Ubuntu 用户的新建文件和目录的权限分别为 664 和 775。</p></div></div><h3 id="73-启动-samba-服务"><a class="markdownIt-Anchor" href="#73-启动-samba-服务"></a> 7.3 启动 Samba 服务</h3><p>启动服务之前，还需要添加客户端登录 Samba 服务器的用户名和密码，<strong>只能使用系统中存在的用户</strong>，如果想使用独立的用户访问 SMB 服务，先添加新用户，添加新用户的方法在<a href="#%E4%BD%BF%E7%94%A8%E7%8B%AC%E7%AB%8B%E7%9A%84%E7%94%A8%E6%88%B7%E8%BF%90%E8%A1%8C">使用独立的用户运行</a>中介绍过。</p><p>我这里直接使用当前用户 ubuntu，添加时提示输入的密码是登录 SMB 服务器的密码，<strong>不是登录终端和 SSH 的密码</strong>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加Samba用户</span></span><br><span class="line"><span class="built_in">sudo</span> smbpasswd -a ubuntu</span><br></pre></td></tr></table></figure><p>其他可能用得到的命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 列出所有Samba用户</span></span><br><span class="line"><span class="built_in">sudo</span> pdbedit -L</span><br><span class="line"><span class="comment"># 禁用Samba用户</span></span><br><span class="line">smbpasswd -d ubuntu</span><br><span class="line"><span class="comment"># 启用Samba用户</span></span><br><span class="line">smbpasswd -e ubuntu</span><br><span class="line"><span class="comment"># 删除Samba用户</span></span><br><span class="line">smbpasswd -x ubuntu</span><br></pre></td></tr></table></figure><p>Samba 启动停止和自启动等也用<code>systemctl</code>管理：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl start smbd</span><br><span class="line"><span class="comment"># 停止</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl stop smbd</span><br><span class="line"><span class="comment"># 查看状态</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status smbd</span><br><span class="line"><span class="comment"># 开机自启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> smbd</span><br><span class="line"><span class="comment"># 关闭开机自启动</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">disable</span> smbd</span><br></pre></td></tr></table></figure><p>启动后在 Windows 上访问<code>\\192.168.50.219</code>（下载机局域网 IP 地址），输入 Samba 用户名和密码后，就可以查看下载机的共享目录了。</p><h3 id="74-修复默认路由问题"><a class="markdownIt-Anchor" href="#74-修复默认路由问题"></a> 7.4 修复默认路由问题</h3><p>受限于网络环境，我的下载机同时连接了网线和 WiFi。网线接口禁用了 IPv6，仅用于局域网内的 SMB 文件互传；无线网卡负责访问外网，包括常规的 IPv4 流量和连接 PT 站的 IPv6 流量。</p><p>Ubuntu Desktop 22.04 默认用 NetworkManager 管理网络连接（Sever 版似乎用 netplan），有线网络和无线网络都会自动创建默认网关，而有线网络的优先级默认高于无线网络（<a href="https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/con_how-networkmanager-manages-multiple-default-gateways_managing-the-default-gateway-setting">参考：NetworkManager 管理多个默认网关</a>）。有线网络 Ethernet 默认的 Metric 为 100，无线网络 Wi-Fi 的默认 Metric 为 600，数值越小优先级越高。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1788/331;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180504_b00052b2bd135d11.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180504_b00052b2bd135d11.webp" alt="默认设置" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">默认设置</span></div></div><p>图中第一条网关为<code>192.168.50.1</code>（OpenWRT 路由器），接口为<code>enp0s31f6</code>的路由规则应当被删除，并且以后也不自动创建这条路由。</p><p>修改有线网卡的连接配置让其不再自动添加默认网关：</p><p>显示所有连接：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nmcli connection show</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1592/210;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180752_067d40d33fcb16b8.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180752_067d40d33fcb16b8.webp" alt="显示所有连接" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">显示所有连接</span></div></div><p>设置连接<code>'Wired connection 1'</code>的<code>ipv4.never-default</code>值为<code>yes</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nmcli connection modify <span class="string">&#x27;Wired connection 1&#x27;</span> ipv4.never-default <span class="built_in">yes</span></span><br></pre></td></tr></table></figure><p>重启<code>'Wired connection 1'</code>连接：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> nmcli connection down <span class="string">&#x27;Wired connection 1&#x27;</span></span><br><span class="line"><span class="built_in">sudo</span> nmcli connection up <span class="string">&#x27;Wired connection 1&#x27;</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2606/208;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180839_837552e136279cdf.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180839_837552e136279cdf.webp" alt="修改后重启连接" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">修改后重启连接</span></div></div><p>最后查看路由表，现在的路由表符合预期：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">route -n</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1664/290;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241117180933_b2e880e3cbbedbee.webp" data-src="https://cos.curiositynotes.dev/imgs/241117180933_b2e880e3cbbedbee.webp" alt="路由表设置成功" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">路由表设置成功</span></div></div><h3 id="75-挂载其他主机的-smb-共享"><a class="markdownIt-Anchor" href="#75-挂载其他主机的-smb-共享"></a> 7.5 挂载其他主机的 SMB 共享</h3><p>这里将 Ubuntu 下载机（IP：192.168.50.219）作为 SMB 客户端，挂载 NAS（IP：192.168.50.233）上 SMB 服务器共享的目录，用于下载机与 NAS 之间的文件传输。虽然在 Linux 主机中使用 NFS (Network File System) 挂载可能更合理，但是 NAS 先前已经设置好了 SMB 服务器，此处为了减少工作量也使用了 SMB 方式。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1857/733;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/241121215947_4b80e2191490acec.webp" data-src="https://cos.curiositynotes.dev/imgs/241121215947_4b80e2191490acec.webp" alt="挂载 NAS 的 SMB 共享" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">挂载 NAS 的 SMB 共享</span></div></div><p>首先在 NAS 中创建一个单独的共享目录<code>PTShare</code>和一个专门让下载机登录的 SMB 用户<code>ptuser</code>，<code>ptuser</code>用户设置为只对<code>PTShare</code>目录具有读写权限。</p><p>然后在 Ubuntu 下载机操作，挂载 NAS 上的共享目录<code>PTShare</code>，首先安装必要的工具包：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install cifs-utils</span><br></pre></td></tr></table></figure><p>挂载，将 NAS 上的远程目录<code>PTShare</code>挂载到本地的目录<code>/home/ubuntu/NAS_PTShare</code>，本地目录需要提前创建：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p /home/ubuntu/NAS_PTShare</span><br><span class="line"><span class="built_in">sudo</span> mount -t cifs -o uid=1000,gid=1000,username=ptuser //192.168.50.233/PTShare /home/ubuntu/NAS_PTShare</span><br></pre></td></tr></table></figure><p>挂载命令中的挂载参数<code>uid=1000</code>，也可以直接使用用户名<code>uid=ubuntu</code>，设置这个参数指定挂载目录中文件的默认所有权，如果不设置默认为<code>uid=0</code>，即<code>root</code>用户。</p><p>输入挂载命令后，会提示输入密码，这个密码是在 NAS 中创建的 SMB 用户<code>ptuser</code>的登录密码。密码也可以直接使用参数<code>password=xxxxx</code>的方式指定，但如果包含<code>,</code>则会出错，更推荐的做法是将登录信息写在凭证文件中。</p><p>建立凭证文件，如<code>/root/.ptuser.smb_credit</code>，安全起见最好将这个文件设置为仅有 root 用户可以读写，这里放在 root 用户的 home 目录中，并将权限设置为 600：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /root/.ptuser.smb_credit</span><br><span class="line"><span class="comment"># 写入SMB用户名和密码</span></span><br><span class="line">username=ptuser</span><br><span class="line">password=xxxxxx</span><br><span class="line"><span class="comment"># 修改权限为600</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chmod</span> 600 /root/.ptuser.smb_credit</span><br></pre></td></tr></table></figure><p>挂载命令修改为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> mount -t cifs -o uid=1000,gid=1000,credentials=/root/.ptuser.smb_credit //192.168.50.233/PTShare /home/ubuntu/NAS_PTShare</span><br></pre></td></tr></table></figure><p>查看挂载情况：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">df</span> -hT</span><br></pre></td></tr></table></figure><p>还可以使用参数指定 SMB 协议版本为 SMB3 及以上（可选）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用SMB3.0</span></span><br><span class="line"><span class="built_in">sudo</span> mount -t cifs -o uid=1000,gid=1000,seal,vers=3.0,credentials=/root/.ptuser.smb_credit //192.168.50.233/PTShare /home/ubuntu/NAS_PTShare</span><br></pre></td></tr></table></figure><p>取消挂载方式与挂载硬盘相同：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> umount /home/ubuntu/NAS_PTShare</span><br></pre></td></tr></table></figure><p>至此完成了在 Ubuntu 下载机挂载 NAS 上的 SMB 共享目录的过程，注意这种挂载是临时的，重启后会失效，这与普通硬盘的挂载是相同的。设置自动挂载的方式也一样，通过将挂载信息添加到<code>/etc/fstab</code>文件实现开机自动挂载，参考<a href="#5-%E6%8C%82%E8%BD%BD%E7%A1%AC%E7%9B%98">挂载硬盘</a>，我这里并没有设置自动挂载，给出参考写法：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 添加到/etc/fstab</span></span><br><span class="line">//192.168.50.233/PTShare  /home/ubuntu/NAS_PTShare  cifs  _netdev,nofail,uid=1000,gid=1000,credentials=/root/.ptuser.smb_credit  0  0</span><br></pre></td></tr></table></figure><p>与直接执行<code>mount</code>命令相比增加了两个参数，<code>_netdev</code>表明这是一个网络设备，系统会在网络就绪之后再尝试挂载；<code>nofail</code>表示不报告错误，不指定这个参数时如果挂载出错可能导致系统无法启动。</p><p>至此基本完成了下载机的配置，本来还计划记录一些提高安全性的内容，这里也标记为<a class="tag-plugin colorful hashtag" color="green"><svg t="1701408144765" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4228" width="200" height="200"><path d="M426.6 64.8c34.8 5.8 58.4 38.8 52.6 73.6l-19.6 117.6h190.2l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6s58.4 38.8 52.6 73.6l-19.4 117.6H896c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-42.6 256H832c35.4 0 64 28.6 64 64s-28.6 64-64 64h-137.8l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.6-117.4h-190.4l-23 138.6c-5.8 34.8-38.8 58.4-73.6 52.6s-58.4-38.8-52.6-73.6l19.4-117.8H128c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l42.6-256H192c-35.4 0-64-28.6-64-64s28.6-64 64-64h137.8l23-138.6c5.8-34.8 38.8-58.4 73.6-52.6z m11.6 319.2l-42.6 256h190.2l42.6-256h-190.2z" p-id="4229"></path></svg><span>DONE</span></a>，<s>后面再单独写吧</s>。已完成，见<a href="https://curiositynotes.dev/posts/ce06158c.html">提高 PT 下载机安全性的配置</a> 。</p><h2 id="参考"><a class="markdownIt-Anchor" href="#参考"></a> 参考</h2><ul><li><a href="https://zhuanlan.zhihu.com/p/163001267">Ubuntu 自动挂载硬盘实现所有用户可读写</a></li><li><a href="https://ubuntu.com/server/docs/share-access-controls">Share access controls</a></li><li><a href="https://github.com/qbittorrent/qBittorrent/wiki/Running-qBittorrent-without-X-server-(WebUI-only,-systemd-service-set-up,-Ubuntu-15.04-or-newer)">Running qBittorrent without X server</a></li><li><a href="https://aimerneige.com/zh/post/linux/install-qbittorrent-nox-on-ubuntu-server/">在 Ubuntu 服务器上安装 qBittorrent-nox</a></li><li><a href="https://cn.linux-terminal.com/?p=3439">如何在 Ubuntu 18.04 桌面或服务器上安装 qBittorrent</a></li><li><a href="https://www.samba.org/samba/docs/current/man-html/smb.conf.5.html">smb.conf — The configuration file for the Samba suite</a></li><li><a href="https://zhuanlan.zhihu.com/p/266495858">树莓派 Samba 服务器</a></li><li><a href="https://www.samba.org/samba/docs/using_samba/ch06.html">Chapter 6. The Samba Configuration File</a></li><li><a href="https://www.cnblogs.com/buxiangxin/p/8621410.html">Samba 访问控制 smb.conf</a></li><li><a href="https://serverfault.com/questions/645081/samba-permissions-being-ignored">Samba permissions being ignored</a></li><li><a href="https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/proc_fixing-unexpected-routing-behavior-due-to-multiple-default-gateways_managing-the-default-gateway-setting">24.10. 修复因为多个默认网关导致的意外路由行为</a></li><li><a href="https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/7/html/storage_administration_guide/mounting_an_smb_share">9.2. 挂载 SMB 共享</a></li><li><a href="https://manpages.ubuntu.com/manpages/jammy/en/man8/mount.8.html">manpages-mount.8</a></li><li><a href="https://manpages.ubuntu.com/manpages/jammy/en/man8/mount.cifs.8.html">manpages-mount.cifs.8</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-11-17：初始版本&lt;/li&gt;
&lt;li&gt;2024-11-21：添加了网络结构图和 SMB 方案图&lt;</summary>
      
    
    
    
    <category term="PT" scheme="https://curiositynotes.dev/categories/PT/"/>
    
    
    <category term="Ubuntu" scheme="https://curiositynotes.dev/tags/Ubuntu/"/>
    
    <category term="网络" scheme="https://curiositynotes.dev/tags/%E7%BD%91%E7%BB%9C/"/>
    
    <category term="qBittorrent" scheme="https://curiositynotes.dev/tags/qBittorrent/"/>
    
    <category term="PT" scheme="https://curiositynotes.dev/tags/PT/"/>
    
    <category term="WireGuard" scheme="https://curiositynotes.dev/tags/WireGuard/"/>
    
    <category term="SMB" scheme="https://curiositynotes.dev/tags/SMB/"/>
    
    <category term="Samba" scheme="https://curiositynotes.dev/tags/Samba/"/>
    
  </entry>
  
  <entry>
    <title>在 Debian12 中从源代码编译安装 GCC</title>
    <link href="https://curiositynotes.dev/posts/a07ad8d1/"/>
    <id>https://curiositynotes.dev/posts/a07ad8d1/</id>
    <published>2024-09-24T10:08:49.000Z</published>
    <updated>2026-05-20T14:08:16.428Z</updated>
    
    <content type="html"><![CDATA[<p>目前在虚拟机中安装了 Debian 12.7，使用<code>apt</code>安装的<code>build-essential</code>中默认的 GCC 版本是 12.2.0，如需安装 GCC13+ 版本可以使用源代码自行构建。</p><h2 id="1-准备源代码"><a class="markdownIt-Anchor" href="#1-准备源代码"></a> 1 准备源代码</h2><p>下载并解压源代码，这里选择更新到 13.3.0 版本。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://ftp.gnu.org/gnu/gcc/gcc-13.3.0/gcc-13.3.0.tar.gz</span><br><span class="line">tar -xvf gcc-13.3.0.tar.gz</span><br></pre></td></tr></table></figure><h2 id="2-安装依赖"><a class="markdownIt-Anchor" href="#2-安装依赖"></a> 2 安装依赖</h2><p>首先确保使用<code>apt</code>安装了基本的编译环境。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install -y build-essential libgmp-dev libmpfr-dev libmpc-dev gcc-multilib</span><br></pre></td></tr></table></figure><p>然后下载 GCC 13.3.0 编译过程中需要的依赖。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> gcc-13.3.0</span><br><span class="line">./contrib/download_prerequisites</span><br></pre></td></tr></table></figure><h2 id="3-编译安装"><a class="markdownIt-Anchor" href="#3-编译安装"></a> 3 编译安装</h2><p>根据<a href="https://gcc.gnu.org/wiki/FAQ#configure">GCC 文档</a>的说明，不能在代码所在目录<code>gcc-13.3.0</code>编译，应当创建一个和代码<strong>同级</strong>的目录生成 Makefile 文件，这里创建<code>gcc-build</code>目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 回到 gcc-13.3.0 所在的 home 目录后创建 gcc-build</span></span><br><span class="line"><span class="built_in">cd</span> ~</span><br><span class="line"><span class="built_in">mkdir</span> gcc-build</span><br></pre></td></tr></table></figure><p>创建后的目录结构应为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── gcc-13.3.0</span><br><span class="line">├── gcc-13.3.0.tar.gz</span><br><span class="line">├── gcc-build</span><br></pre></td></tr></table></figure><p>运行配置程序生成 Makefile，同样根据<a href="https://gcc.gnu.org/wiki/InstallingGCC">GCC 安装文档</a>，这个步骤不要添加过多自己不清楚是干什么的参数。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入 gcc-build 目录后使用 configure 生成 Makefile</span></span><br><span class="line"><span class="built_in">cd</span> gcc-build</span><br><span class="line">../gcc-13.3.0/configure --enable-languages=c,c++</span><br></pre></td></tr></table></figure><h3 id="31-编译"><a class="markdownIt-Anchor" href="#31-编译"></a> 3.1 编译</h3><p>这个过程可能会非常慢，取决于硬件条件，使用虚拟机的话可以临时把 CPU 和内存都调到最大。我在虚拟机中编译，AMD R7-6800H 内核分配 16，内存分配 8G，编译时间大约 55 分钟。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make -j$(<span class="built_in">nproc</span>)</span><br></pre></td></tr></table></figure><p>编译过程中可能会出现错误，大多数情况是因为缺少依赖，根据报错信息搜索安装对应的依赖即可。编译中断后，可以在<code>gcc-build</code>目录中重复执行<code>make -j$(nproc)</code>命令，已经编译过的文件不会再次编译，幸运的话可以节省一些时间。</p><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>不过仍然建议出错中断后，删除<code>gcc-build</code>文件夹后彻底重新编译。</p></div></div><h3 id="32-安装"><a class="markdownIt-Anchor" href="#32-安装"></a> 3.2 安装</h3><p>这一步也会花费一些时间，但是比编译要快得多。在<code>configure</code>时没有指定<code>--prefix</code>参数，默认安装到了<code>/usr/local/bin</code>。向<code>/usr/local/bin</code>目录写入文件需要使用<code>sudo</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> make install</span><br></pre></td></tr></table></figure><h3 id="33-添加动态库的路径"><a class="markdownIt-Anchor" href="#33-添加动态库的路径"></a> 3.3 添加动态库的路径</h3><p>安装完成后会看到终端中有类似这样的提示：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Libraries have been installed <span class="keyword">in</span>:</span><br><span class="line">   /usr/local/lib/../lib64</span><br><span class="line"></span><br><span class="line">If you ever happen to want to <span class="built_in">link</span> against installed libraries</span><br><span class="line"><span class="keyword">in</span> a given directory, LIBDIR, you must either use libtool, and</span><br><span class="line">specify the full pathname of the library, or use the <span class="string">&#x27;-LLIBDIR&#x27;</span></span><br><span class="line">flag during linking and <span class="keyword">do</span> at least one of the following:</span><br><span class="line">   - add LIBDIR to the <span class="string">&#x27;LD_LIBRARY_PATH&#x27;</span> environment variable</span><br><span class="line">     during execution</span><br><span class="line">   - add LIBDIR to the <span class="string">&#x27;LD_RUN_PATH&#x27;</span> environment variable</span><br><span class="line">     during linking</span><br><span class="line">   - use the <span class="string">&#x27;-Wl,-rpath -Wl,LIBDIR&#x27;</span> linker flag</span><br><span class="line">   - have your system administrator add LIBDIR to <span class="string">&#x27;/etc/ld.so.conf&#x27;</span></span><br><span class="line"></span><br><span class="line">See any operating system documentation about shared libraries <span class="keyword">for</span></span><br><span class="line">more information, such as the ld(1) and ld.so(8) manual pages.</span><br></pre></td></tr></table></figure><p>这是提示我们新的 GCC 的动态库安装在了<code>/usr/local/lib/../lib64</code>路径下，需要通过给出的方法中的至少一种，将这个路径添加到动态库搜索路径中。</p><p>可以选择修改环境变量的方式修改：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> LD_LIBRARY_PATH=<span class="variable">$LD_LIBRARY_PATH</span>:/usr/local/lib/../lib64</span><br><span class="line"><span class="built_in">export</span> LD_LIBRARY_PATH=<span class="variable">$LD_LIBRARY_PATH</span>:/usr/local/lib/../lib32</span><br></pre></td></tr></table></figure><p>将上面两条命令写入终端配置文件（如<code>.bashrc</code>、<code>.zshrc</code>等），可以实现永久配置。</p><p>也可以选择修改<code>/etc/ld.so.conf</code>配置文件，需要使用<code>sudo</code>，新加入的路径放在上面会优先于原来的路径查找：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 原文件</span></span><br><span class="line">include /etc/ld.so.conf.d/*.conf</span><br><span class="line"><span class="comment"># 修改后</span></span><br><span class="line">/usr/local/lib/../lib64</span><br><span class="line">/usr/local/lib/../lib32</span><br><span class="line">include /etc/ld.so.conf.d/*.conf</span><br></pre></td></tr></table></figure><p>修改<code>/etc/ld.so.conf</code>配置文件后，需要使用<code>sudo ldconfig</code>命令刷新。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>程序在使用新的 GCC 编译之后，运行时如果报错<code>version GLIBCXX_X.X.X' not found</code>，大概率是这一步设置没有做对。</p><p>使用<code>ldd ./a.out</code>查看编译出的可执行文件的动态库依赖关系，可以检测动态库搜索路径是否正确。</p></div></div><h2 id="4-更新软连接"><a class="markdownIt-Anchor" href="#4-更新软连接"></a> 4 更新软连接</h2><p>虽然已经编译安装完成，但使用<code>gcc -v</code>命令查看版本还没有更新，还需要修改<code>gcc</code>和<code>g++</code>命令的软连接，将其指向我们刚编译安装的<code>/usr/local/bin/gcc</code>和<code>/usr/local/bin/g++</code>。</p><div class="tag-plugin colorful note" color="cyan"><div class="body"><p>如果经常需要切换版本，可以使用使用<code>update-alternatives</code>来管理不同的版本，这里简单起见，直接修改软连接。</p></div></div><p>查看旧版本的 GCC 所在的路径</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">which</span> gcc</span><br><span class="line"><span class="comment"># /usr/bin/gcc</span></span><br><span class="line"><span class="built_in">ls</span> -l /usr/bin/gcc* /usr/bin/g++*</span><br><span class="line"><span class="comment"># /usr/bin/g++ -&gt; g++-12</span></span><br><span class="line"><span class="comment"># /usr/bin/g++-12 -&gt; x86_64-linux-gnu-g++-12</span></span><br><span class="line"><span class="comment"># /usr/bin/gcc -&gt; gcc-12</span></span><br><span class="line"><span class="comment"># /usr/bin/gcc-12 -&gt; x86_64-linux-gnu-gcc-12</span></span><br></pre></td></tr></table></figure><p>可以发现原本的<code>gcc</code>命令也是一个软连接，指向另一个软连接<code>gcc-12</code>，而<code>gcc-12</code>指向了真正的可执行文件<code>x86_64-linux-gnu-gcc-12</code>。<code>g++</code>命令也是一样。</p><p>现在删除原本的<code>gcc</code>和<code>g++</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> /usr/bin/gcc /usr/bin/g++</span><br></pre></td></tr></table></figure><p>新建指向<code>/usr/local/bin/gcc</code>和<code>/usr/local/bin/g++</code>的<code>gcc</code>和<code>g++</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">ln</span> -s /usr/local/bin/gcc /usr/bin/gcc</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">ln</span> -s /usr/local/bin/g++ /usr/bin/g++</span><br></pre></td></tr></table></figure><p>此时就可以使用<code>gcc -v</code>验证更新后的版本了。</p><h2 id="5-删除文件"><a class="markdownIt-Anchor" href="#5-删除文件"></a> 5 删除文件</h2><p>使用新的 GCC 编译一个程序，并且该程序能正常运行。简单测试一下安装正常之后，可以删除 GCC 编译后产生的文件，位于目录<code>gcc-build</code>；GCC 代码文件，位于目录<code>gcc-13.3.0</code>；GCC 代码压缩包<code>gcc-13.3.0.tar.gz</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> -rf gcc-build gcc-13.3.0 gcc-13.3.0.tar.gz</span><br></pre></td></tr></table></figure><p>至此 GCC 更新完成。</p><h2 id="6-参考"><a class="markdownIt-Anchor" href="#6-参考"></a> 6 参考</h2><ul><li><a href="https://gcc.gnu.org/wiki/InstallingGCC">InstallingGCC</a></li><li><a href="https://gcc.gnu.org/wiki/FAQ#configure">GCC FAQ</a></li><li><a href="https://stackoverflow.com/questions/78306968/installing-gcc13-and-g13-in-debian-bookworm-rust-docker-image">Installing gcc13 and g++13 in Debian bookworm rust docker image</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;目前在虚拟机中安装了 Debian 12.7，使用&lt;code&gt;apt&lt;/code&gt;安装的&lt;code&gt;build-essential&lt;/code&gt;中默认的 GCC 版本是 12.2.0，如需安装 GCC13+ 版本可以使用源代码自行构建。&lt;/p&gt;
&lt;h2 id=&quot;1-准备源代</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Linux" scheme="https://curiositynotes.dev/tags/Linux/"/>
    
    <category term="Debian" scheme="https://curiositynotes.dev/tags/Debian/"/>
    
  </entry>
  
  <entry>
    <title>树莓派 4B 温控风扇</title>
    <link href="https://curiositynotes.dev/posts/bdd76590/"/>
    <id>https://curiositynotes.dev/posts/bdd76590/</id>
    <published>2024-07-11T01:14:50.000Z</published>
    <updated>2026-05-20T14:08:16.426Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2024-07-11：初始版本</li><li>2024-07-15：添加<a href="#TL-DR">TL;DR</a>，删除了文中的长代码，完整代码上传到 Github</li></ul><p>最近要用到吃灰已久的树莓派 4B，发现原来的 TF 卡已经损坏。更换 TF 卡写入最新 64bit 的 RaspiOS-Lite，配置时发现之前使用的温控风扇的程序文件也找不到了，索性直接重新整理一下。</p><p>买外壳时自带了一个风扇，是普通的不支持 PWM 调速的 5V 风扇，只需要用程序根据 CPU 温度控制风扇启停即可。</p><h2 id="tldr"><a class="markdownIt-Anchor" href="#tldr"></a> TL;DR</h2><blockquote><p>完整代码已经发布在 Github 仓库<a href="https://github.com/gsh1209/pi_auto_fan.git"><strong>pi_auto_fan</strong></a></p></blockquote><p>控制程序及设置控制程序自启动的步骤略繁琐，使用<code>Makefile</code>把需要操作的步骤打包成了<code>install</code>和<code>uninstall</code>，直接运行<code>sudo make insatll</code>即可完成<strong>编译、创建系统服务、启动服务、设置自启动</strong>，注意必须使用<code>sudo</code>，因为创建系统服务需要使用<code>root</code>权限。</p><p>运行<code>sudo make install</code>之前仍然需要按照文章中<a href="#2-3-%E7%BC%96%E8%AF%91%E6%B5%8B%E8%AF%95">编译测试</a>部分的步骤安装依赖。</p><p>默认使用<strong>GPIO 14</strong>作为控制 IO（<code>fan_pin=14</code>），上限阈值为 60°（<code>temp_high=60.0</code>），下限阈值为 50°（<code>temp_low=50.0</code>），如需修改需要在文件<code>auto_fan.c</code>中修改对应代码。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/gsh1209/pi_auto_fan.git</span><br><span class="line"><span class="built_in">cd</span> pi_auto_fan</span><br><span class="line"><span class="comment"># 仅编译，无需sudo</span></span><br><span class="line">make</span><br><span class="line"><span class="comment"># 安装</span></span><br><span class="line"><span class="built_in">sudo</span> make install</span><br><span class="line"><span class="comment"># 卸载</span></span><br><span class="line"><span class="built_in">sudo</span> make uninstall</span><br></pre></td></tr></table></figure><p>查看 GPIO 状态的一些命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">raspi-gpio get | grep <span class="string">&quot;GPIO 14&quot;</span></span><br><span class="line">gpio readall</span><br></pre></td></tr></table></figure><h2 id="1-控制电路"><a class="markdownIt-Anchor" href="#1-控制电路"></a> 1 控制电路</h2><p>找到了之前画的驱动电路，使用了两个三极管（型号均为 S8050）级联来进一步减轻 GPIO 输出电流的负担。电路很简单，不过当时还在嘉立创白嫖了一块 PCB，PCB 源文件已经找不到了，只能找到一个布局的图片。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1789/1376;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715035432_74fc9811decb9464.webp" data-src="https://cos.curiositynotes.dev/imgs/240715035432_74fc9811decb9464.webp" alt="风扇驱动电路图" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">风扇驱动电路图</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1262/733;width:300px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715035446_9d95ee511892ae2a.webp" data-src="https://cos.curiositynotes.dev/imgs/240715035446_9d95ee511892ae2a.webp" alt="风扇驱动 PCB 布局" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">风扇驱动 PCB 布局</span></div></div><p>这个电路是经过长时间验证的，曾经用树莓派跑了大半年的局域网打印机共享服务器和 SMB 文件共享服务器，使用的散热方案就是这套电路，长时间工作时三极管也不会发热。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:931/661;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715042635_1d12f565eb6b9ed6.webp" data-src="https://cos.curiositynotes.dev/imgs/240715042635_1d12f565eb6b9ed6.webp" alt="风扇控制 PCB" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">风扇控制 PCB</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2295/1674;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715042632_a58aebdd6afdc4b2.webp" data-src="https://cos.curiositynotes.dev/imgs/240715042632_a58aebdd6afdc4b2.webp" alt="控制板与树莓派的连接" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">控制板与树莓派的连接</span></div></div><p>树莓派上的控制 IO 使用的是<strong>GPIO 14</strong>，紧挨着 5V 电源和 GND，可以方便的使用 3PIN 的杜邦线进行连接，不过 GPIO 14 其实也是 UART 串口的<strong>TXD</strong>，如果需要用到串口可以使用其他 IO 作为风扇控制 IO。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2064/1185;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715034925_36aa1175280d5d41.webp" data-src="https://cos.curiositynotes.dev/imgs/240715034925_36aa1175280d5d41.webp" alt="树莓派 4B 的 GPIO 定义" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">树莓派 4B 的 GPIO 定义</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1200/421;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240715043325_0bf4f591a5deef72.webp" data-src="https://cos.curiositynotes.dev/imgs/240715043325_0bf4f591a5deef72.webp" alt="风扇控制使用的 GPIO" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">风扇控制使用的 GPIO</span></div></div><blockquote><p><em>GPIO 定义图片来自<a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#gpio">树莓派文档</a></em></p></blockquote><h2 id="2-控制程序"><a class="markdownIt-Anchor" href="#2-控制程序"></a> 2 控制程序</h2><p>之前的程序使用的是 Python 来控制，但是由于控制程序需要长期运行，Python 脚本占用的系统资源更高一些，尤其对于我的 1GB 内存版本有些不友好，这次使用 C 语言来写控制程序。</p><h3 id="21-cpu-温度读取"><a class="markdownIt-Anchor" href="#21-cpu-温度读取"></a> 2.1 CPU 温度读取</h3><p>树莓派系统中提供了可供读取的文件来获取 CPU 温度，文件位于<code>/sys/class/thermal/thermal_zone0/temp</code>，文件内容是整数形式的 CPU 温度，包含三位小数。如果需要将温度输出到终端显示需要将获取的数值除以 1000 进行转换。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">printf</span>(<span class="string">&quot;CPU temperature: %.2f°C\n&quot;</span>, atof(cpu_temp_str) / <span class="number">1000.0</span>);</span><br></pre></td></tr></table></figure><p>这里的温控风扇的程序运行在后台，不需要向终端显示温度，为了简单我暂时也不需要打印 log，所以这里的获取温度的函数不做转换直接返回文件内容转换为整数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 从文件获取 CPU 温度</span></span><br><span class="line"><span class="comment"> * 返回文件内容，不转换为浮点数</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">getCpuTemp</span><span class="params">()</span> &#123;</span><br><span class="line">    FILE *file;</span><br><span class="line">    <span class="type">char</span> cpu_temp_file[] = <span class="string">&quot;/sys/class/thermal/thermal_zone0/temp&quot;</span>;</span><br><span class="line">    <span class="type">char</span> cpu_temp_str[<span class="number">10</span>];</span><br><span class="line">    file = fopen(cpu_temp_file, <span class="string">&quot;r&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span>(<span class="literal">NULL</span> == file) &#123;</span><br><span class="line">        perror(<span class="string">&quot;fopen&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(<span class="literal">NULL</span> == fgets(cpu_temp_str, <span class="keyword">sizeof</span>(cpu_temp_str), file)) &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;read error!\n&quot;</span>);</span><br><span class="line">        fclose(file);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    fclose(file);</span><br><span class="line">    <span class="keyword">return</span> atoi(cpu_temp_str);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="22-启停控制逻辑"><a class="markdownIt-Anchor" href="#22-启停控制逻辑"></a> 2.2 启停控制逻辑</h3><p>控制程序每 2s 读取一次温度，和温度阈值进行比较，控制风扇的启停。为了防止风扇在阈值附近反复的开启和关闭，控制程序设置了上限阈值<code>temp_high</code>和下限阈值<code>temp_low</code>两个阈值，当温度超过上限时风扇启动，当温度低于下限时风扇停止。程序中设置上下限阈值为 60℃和 50℃，可根据环境温度等情况按需调整。</p><p>上下限阈值是直接写到程序代码中的，如果需要修改则要重新编译，合理的做法是作为参数传入或者使用其他方式配置，包括用于控制的 GPIO 也是这样，但是因为懒暂时就不写了。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> temp_high = <span class="number">60.0</span>;</span><br><span class="line"><span class="type">float</span> temp_low = <span class="number">50.0</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">enum</span> &#123;</span> ON, OFF &#125; fan_status;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> &#123;</span><br><span class="line">    initFanGpio();</span><br><span class="line">    fan_status status = OFF;</span><br><span class="line">    <span class="keyword">while</span>(<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="type">int</span> cpu_temp = getCpuTemp();</span><br><span class="line">        <span class="keyword">if</span>(OFF == status) &#123;</span><br><span class="line">            <span class="keyword">if</span>(cpu_temp &gt; (<span class="type">int</span>)(temp_high * <span class="number">1000</span>)) &#123;</span><br><span class="line">                digitalWrite(fan_pin, HIGH);</span><br><span class="line">                status = ON;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span>(cpu_temp &lt; (<span class="type">int</span>)(temp_low * <span class="number">1000</span>)) &#123;</span><br><span class="line">                digitalWrite(fan_pin, LOW);</span><br><span class="line">                status = OFF;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        sleep(<span class="number">2</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="23-编译测试"><a class="markdownIt-Anchor" href="#23-编译测试"></a> 2.3 编译测试</h3><p>在编译程序之前，需要确保已经安装需要的依赖</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install build-essential git -y</span><br></pre></td></tr></table></figure><p>还需要安装 GPIO 控制库 WiringPi，我使用源码编译安装，也可以直接下载<a href="https://github.com/WiringPi/WiringPi/releases">Releases</a>中编译好的二进制包安装。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/WiringPi/WiringPi.git</span><br><span class="line"><span class="built_in">cd</span> WiringPi</span><br><span class="line">./build</span><br></pre></td></tr></table></figure><p>使用<code>gpio readall</code>命令来验证<code>WiringPi</code>是否安装成功。</p><p>编译时需要添加<code>-l</code>参数链接<code>WiringPi</code>库：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc auto_fan.c -o auto_fan -lwiringPi</span><br></pre></td></tr></table></figure><p>使用<code>sysbench</code>命令让 CPU 快速升温，测试风扇能否按照预期工作，<code>sysbench</code>需要安装。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install sysbench -y</span><br><span class="line">sysbench --num-threads=4 --<span class="built_in">test</span>=cpu --cpu-max-prime=200000 run</span><br></pre></td></tr></table></figure><p>测试确保控制程序<code>auto_fan</code>功能正常后，按照 Linux 系统中文件的组织习惯将其复制到<code>/usr/bin</code>目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> auto_fan /usr/bin</span><br></pre></td></tr></table></figure><h2 id="3-设置控制程序自动启动"><a class="markdownIt-Anchor" href="#3-设置控制程序自动启动"></a> 3 设置控制程序自动启动</h2><p>在树莓派中设置程序自动启动和一般 Linux 系统中的方法类似，如使用定时任务<code>crontab</code>的<code>@reboot</code>，将运行程序的命令写入<code>rc.local</code>等，这里使用<code>systemd</code>自定义系统服务的方法来实现自动启动控制程序。</p><h3 id="31-创建系统服务"><a class="markdownIt-Anchor" href="#31-创建系统服务"></a> 3.1 创建系统服务</h3><p>systemctl 的服务配置文件存放在目录<code>/usr/lib/systemd</code>中，有系统 system 和用户 user 之分，需要开机不登录就能运行的程序，存放在系统服务中即<code>/usr/lib/systemd/system</code>目录下。风扇控制程序应当创建为系统服务，在<code>/usr/lib/systemd/system</code>目录新建一个<code>auto_fan.service</code>配置文件，需要使用<code>sudo</code>，内容如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=Auto Fan Service by gsh1209</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=simple</span><br><span class="line">Restart=always</span><br><span class="line">ExecStart=/usr/bin/auto_fan &amp;</span><br><span class="line">KillMode=mixed</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p>其中关键的配置项有<code>ExecStart=/usr/bin/auto_fan &amp;</code>和<code>KillMode=mixed</code>：</p><ul><li><code>ExecStart</code>表示服务启动时运行的命令，这里在后台（使用参数<code>&amp;</code>）运行编译后的风扇控制程序，参考<code>man systemd.service</code>。</li><li><code>KillMode</code>表示服务终止时的操作，设置为<code>mixed</code>表示主进程将收到<code>SIGTERM</code>信号，子进程收到<code>SIGKILL</code>信号，参考<code>man systemd.kill</code>。</li></ul><p>设置<code>KillMode</code>的目的是在服务停止时需要恢复 GPIO 的状态，尤其是如果此时风扇已被打开，服务停止风扇应当被立即关闭，这里通过在程序中接收<code>SIGTERM</code>信号，并自定义信号处理函数的方式实现服务停止时，程序退出前恢复 GPIO 为默认状态。</p><p>还可以使用<code>ExecStop=</code>指定一个服务停止时运行的命令来实现恢复 GPIO 状态这个功能，命令既可以使用系统提供的<code>raspi-gpio</code>，也可以使用由<code>WiringPi</code>库提供的<code>gpio</code>命令，还可以使用 C 语言单独写一个实现释放 GPIO 的程序。</p><blockquote><p>根据<a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#power-on-states">树莓派 4B 的文档</a>和<a href="https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf">BCM2711 的 Datasheet</a>(<em>GPIO_PUP_PDN_CNTRL_REG0 Register</em>)，树莓派 GPIO 14 复位后的默认状态为：输入，内部下拉。</p></blockquote><h3 id="32-设置服务开机自动启动"><a class="markdownIt-Anchor" href="#32-设置服务开机自动启动"></a> 3.2 设置服务开机自动启动</h3><p>使用命令设置服务为<code>enable</code>即可开机自启动</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> auto_fan.service</span><br></pre></td></tr></table></figure><h3 id="33-systemctl-常用命令"><a class="markdownIt-Anchor" href="#33-systemctl-常用命令"></a> 3.3 systemctl 常用命令</h3><ul><li>启动、停止、重启服务</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start/stop/restart auto_fan.service</span><br></pre></td></tr></table></figure><ul><li>开启、关闭开机自启</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span>/disable auto_fan.service</span><br></pre></td></tr></table></figure><ul><li>查看运行状态</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl status auto_fan.service</span><br></pre></td></tr></table></figure><ul><li>重载配置文件（服务启动时修改了 service 文件，需要重载配置）</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl daemon-reload</span><br></pre></td></tr></table></figure><h2 id="4-其他"><a class="markdownIt-Anchor" href="#4-其他"></a> 4 其他</h2><p>最后对比了一下 Python 版本和 C 版本的程序，在 CPU 占用上没什么差别。在内存占用上的差距也没有想象中那么大：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Python程序的pid 3669</span></span><br><span class="line">pi@raspberrypi:~ $ <span class="built_in">cat</span> /proc/3669/status | grep VmRSS</span><br><span class="line">VmRSS:      7680 kB</span><br><span class="line"><span class="comment"># C程序的pid 3561</span></span><br><span class="line">pi@raspberrypi:~ $ <span class="built_in">cat</span> /proc/3561/status | grep VmRSS</span><br><span class="line">VmRSS:      1408 kB</span><br></pre></td></tr></table></figure><p>使用的 Python 程序</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> RPi.GPIO <span class="keyword">as</span> GPIO</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_cpu_temp</span>():</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;/sys/class/thermal/thermal_zone0/temp&quot;</span>, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">float</span>(f.read()) / <span class="number">1000</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">fan_pin = <span class="number">14</span></span><br><span class="line">temp_high = <span class="number">60.0</span></span><br><span class="line">temp_low = <span class="number">50.0</span></span><br><span class="line"></span><br><span class="line">GPIO.setmode(GPIO.BCM)</span><br><span class="line">GPIO.setwarnings(<span class="literal">False</span>)</span><br><span class="line">GPIO.setup(fan_pin, GPIO.OUT, initial=GPIO.LOW)</span><br><span class="line">is_fan_off = <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        temp = get_cpu_temp()</span><br><span class="line">        <span class="keyword">if</span> is_fan_off == <span class="literal">True</span>:</span><br><span class="line">            <span class="keyword">if</span> temp &gt; temp_high:</span><br><span class="line">                GPIO.output(fan_pin, GPIO.HIGH)</span><br><span class="line">                is_fan_off = <span class="literal">False</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">if</span> temp &lt; temp_low:</span><br><span class="line">                GPIO.output(fan_pin, GPIO.LOW)</span><br><span class="line">                is_fan_off = <span class="literal">True</span></span><br><span class="line">        time.sleep(<span class="number">5.0</span>)</span><br><span class="line"><span class="keyword">except</span>:</span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">GPIO.cleanup(fan_pin)</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-07-11：初始版本&lt;/li&gt;
&lt;li&gt;2024-07-15：添加&lt;a href=&quot;#TL-DR&quot;</summary>
      
    
    
    
    <category term="工具" scheme="https://curiositynotes.dev/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="树莓派" scheme="https://curiositynotes.dev/tags/%E6%A0%91%E8%8E%93%E6%B4%BE/"/>
    
  </entry>
  
  <entry>
    <title>使用 Docker 独立部署 Waline 评论系统</title>
    <link href="https://curiositynotes.dev/posts/dc23e930/"/>
    <id>https://curiositynotes.dev/posts/dc23e930/</id>
    <published>2024-03-25T17:01:06.000Z</published>
    <updated>2026-05-20T14:08:16.424Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2024-03-26: 初始版本</li><li>2024-04-21: 更新<a href="#2-2-1-Nginx%E9%85%8D%E7%BD%AE%E8%A1%A5%E4%B8%81-%E7%A6%81%E6%AD%A2%E7%94%A8%E6%88%B7%E8%AE%BF%E9%97%AEWaline%E7%9A%84demo%E9%A1%B5%E9%9D%A2">禁止用户访问 Waline 的 demo 页面的 Nginx 配置</a></li><li>2024-05-10: 更新 MySQL8.4 版本删除 default-authentication-plugin 参数后的 MySQL docker-compose 配置</li><li>2024-08-20: 更新 MySQL 的 docker-compose 配置，添加<code>TZ</code>环境变量修复评论时间显示异常的问题</li><li>2024-08-20: 更新 MySQL 修改用户身份认证插件的两种方法</li><li>2024-08-30: 更新 MySQL 修改用户身份认证插件部分内容</li></ul><p>Waline 推荐使用 LeanCloud 和 Vercel 部署，但是 Vercel 受限于网络原因访问并不稳定，刷新评论时间较长，还可能会失败。使用 Docker 独立部署方式将 Waline 部署在博客服务器上可以有效地改善加载速度。部署 Waline 首先要准备数据库，这里使用 MySQL 数据库，同样运行在 Docker 中方便管理。</p><p>在初次创建 MySQL 数据库时，需要导入 waline.sql 创建初始的表结构，应当按照先后顺序分别创建 MySQL 容器和 Waline 容器。</p><p>如果想使用一个 docker-compose.yml 来管理两个容器，可以提前启动一个临时的数据库容器初始化表结构，然后删除这个临时容器，在 docker-compose.yml 中依次以相同配置启动 MySQL 容器和 Waline 容器，通过设置 depends_on 来规定启动顺序，但是 MySQL 初始化时间可能较长，可能会出现错误，<a href="https://hub.docker.com/_/mysql">参考 (No connections until MySQL init completes)</a>，还可以使用 <a href="https://github.com/vishnubob/wait-for-it">wait-for-it.sh</a> 工具来让 Waline 容器等待 MySQL 容器（通过检查端口是否可用）就绪。</p><h2 id="1-mysql-数据库-docker-部署"><a class="markdownIt-Anchor" href="#1-mysql-数据库-docker-部署"></a> 1 MySQL 数据库 Docker 部署</h2><h3 id="11-使用-docker-compose-部署-mysql-数据库"><a class="markdownIt-Anchor" href="#11-使用-docker-compose-部署-mysql-数据库"></a> 1.1 使用 docker-compose 部署 MySQL 数据库</h3><h4 id="111-创建-mysql-容器"><a class="markdownIt-Anchor" href="#111-创建-mysql-容器"></a> 1.1.1 创建 MySQL 容器</h4><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">waline_net:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;mysql_waline_net&quot;</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">db_mysql:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">mysqldb</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">mysql:8.4</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">&quot;Asia/Shanghai&quot;</span></span><br><span class="line">      <span class="attr">MYSQL_ROOT_PASSWORD:</span> <span class="string">&quot;root_password&quot;</span> <span class="comment"># 数据库 root 密码</span></span><br><span class="line">      <span class="attr">MYSQL_USER:</span> <span class="string">&quot;waline&quot;</span> <span class="comment"># 数据库用户</span></span><br><span class="line">      <span class="attr">MYSQL_PASSWORD:</span> <span class="string">&quot;waline_password&quot;</span> <span class="comment"># 数据库用户密码</span></span><br><span class="line">      <span class="attr">MYSQL_DATABASE:</span> <span class="string">&quot;waline_db&quot;</span> <span class="comment"># 数据库名</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">3306</span><span class="string">:3306</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline_net</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">./mysql:/var/lib/mysql</span></span><br><span class="line">    <span class="attr">command:</span></span><br><span class="line">      <span class="comment"># 适用于 8.0-8.4（不高于 8.4）版本的 MySQL</span></span><br><span class="line">      <span class="comment"># --default-authentication-plugin=mysql_native_password</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># MySQL8.4 版本不再使用 default-authentication-plugin 参数，使用这个参数会导致启动失败</span></span><br><span class="line">      <span class="comment"># 需要设置参数 mysql-native-password=ON 参数加载 mysql-native-password 插件</span></span><br><span class="line">      <span class="string">--mysql-native-password=ON</span></span><br><span class="line">      <span class="string">--character-set-server=utf8mb4</span></span><br><span class="line">      <span class="string">--collation-server=utf8mb4_unicode_ci</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:440/98;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012103_c45331d07f183957.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012103_c45331d07f183957.webp" alt="创建 MySQL 容器" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">创建 MySQL 容器</span></div></div><p>此处设置了网络 waline_net，并设置<code>external: false</code>，如果不指定<code>name</code>，在容器启动时将会创建一个名为 mysql_waline_net 的网络，该名称默认由 docker-compose 文件所在目录名 (docker-compose project name) 和设置的网络 waline_net 组成，指定了<code>name</code>则会按照该设置命名创建的网络。</p><p>这里指定名字的目的是在创建 waline 容器时使用相同名字的网络完成容器间互连。如果不设置<code>name</code>，即使指定相同的网络 waline_net，由于 waline 容器的 docker-compose 文件与 MySQL 的 docker-compose 文件上级目录名不同，自动生成的网络名也不相同，如果 waline 的设置<code>external: false</code>，则会创建另一个网络 (waline_waline_net)；如果 waline 的设置<code>external: true</code>，则会提示找不到声明的网络。</p><blockquote><p>此方式创建的网络即使在容器删除后也不会自动删除，确认在没有其他容器使用时可以手动删除<br />查看所有网络<code>docker network ls</code><br />查看网络详细信息<code>docker network inspect &lt;NET_NAME&gt;</code>，输出的<code>Containers</code>字段表示正在使用该网络的容器<br />删除网络<code>docker network rm &lt;NET_NAME&gt;</code></p></blockquote><p>在最后通过<code>--mysql-native-password=ON</code>（<a href="https://dev.mysql.com/doc/relnotes/mysql/8.4/en/news-8-4-0.html#mysqld-8-4-0-deprecation-removal">参考链接</a>）参数加载 mysql-native-password 插件，MySQL8.0 以上的版本默认使用 caching_sha2_password 身份验证插件，但 Waline 客户端并不支持新的插件，需要修改为 native 插件。（2024.08.30 更新：<code>--mysql-native-password=ON</code>似乎并不是完全替代<code>--default-authentication-plugin</code>，<code>--mysql-native-password=ON</code>只是加载了插件，让插件变得可用，没有修改默认的插件，还需要使用 SQL 命令来修改用户的默认认证插件。）</p><blockquote><p>插件的可用状态（ACTIVE/DISABLED）可以使用命令<code>SHOW PLUGINS;</code>查看。</p></blockquote><p>启动后进入容器，使用 root 用户登录 MySQL，运行以下命令修改 waline 用户的认证插件。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator">/</span><span class="operator">/</span> 已存在waline用户时，修改waline用户的认证方式</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">&#x27;waline&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">&#x27;waline_password&#x27;</span>;</span><br><span class="line"><span class="operator">/</span><span class="operator">/</span> 或者 （<span class="string">&#x27;waline&#x27;</span>@<span class="string">&#x27;%&#x27;</span> 表示从远程连接的waline用户，<span class="string">&#x27;%&#x27;</span>不包括<span class="string">&#x27;localhost&#x27;</span>）</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">&#x27;waline&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">&#x27;waline_password&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="operator">/</span><span class="operator">/</span> 如果创建容器时不创建用户（在docker<span class="operator">-</span>compose文件中不设置`MYSQL_USER`和`MYSQL_PASSWORD`）</span><br><span class="line"><span class="operator">/</span><span class="operator">/</span> 后续创建用户时指定认证方式</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;waline&#x27;</span>@<span class="string">&#x27;%&#x27;</span> IDENTIFIED <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">&#x27;waline_password&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> <span class="keyword">ALL</span> PRIVILEGES <span class="keyword">ON</span> waline_db.<span class="operator">*</span> <span class="keyword">TO</span> <span class="string">&#x27;waline&#x27;</span>;</span><br><span class="line">flush privileges;</span><br><span class="line"></span><br><span class="line"><span class="operator">/</span><span class="operator">/</span> 查看修改结果</span><br><span class="line"><span class="keyword">SELECT</span> <span class="keyword">User</span>,Host,plugin <span class="keyword">FROM</span> mysql.user;</span><br></pre></td></tr></table></figure><blockquote><p>如未加载 mysql-native-password 插件，<code>SHOW PLUGINS;</code>命令查看状态为 DISABLED。执行<code>ALERT USER …</code>命令时会遇到<code>ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded</code>错误。</p><p>如果在第一次启动容器时没有设置<code>--mysql-native-password=ON</code>，可以修改 docker compose 文件后重启容器即可完成加载。</p></blockquote><p>接下来需要使用官方提供的<a href="https://github.com/walinejs/waline/blob/main/assets/waline.sql">waline.sql 文件</a>初始化数据库，可以选择使用命令操作 (方法一)，也可以选择使用数据库管理软件操作 (方法二)。</p><h4 id="112-初始化数据库-方法一"><a class="markdownIt-Anchor" href="#112-初始化数据库-方法一"></a> 1.1.2 初始化数据库 (方法一)</h4><p>下载 waline.sql，保存到 docker-compose 文件所在的目录</p><p>使用命令<code>docker compose up -d</code>创建并运行容器，容器运行后，挂载到容器内部的本地目录<code>./mysql</code>会自动创建，相关的目录权限也会由 docker 自动处理。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:386/61;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012212_db9f2afac2d4cdf7.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012212_db9f2afac2d4cdf7.webp" alt="mysql 目录自动创建" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">mysql 目录自动创建</span></div></div><p>此时可以将 waline.sql 文件复制到<code>./mysql</code>中，这个 sql 文件需要在容器内运行来初始化数据表。复制时需要使用<code>sudo</code>，因为 docker 创建的目录本地普通用户没有写入权限。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:493/41;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012238_79e142c9fc277a3f.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012238_79e142c9fc277a3f.webp" alt="复制 waline.sql 文件" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">复制 waline.sql 文件</span></div></div><p>使用命令<code>docker exec -it &lt;容器名称&gt; bash</code>进入 Docker 容器内的 bash，查看 waline.sql 文件已经能在容器内<code>/var/lib/mysql/waline.sql</code>找到。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:680/104;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012303_994133e3838e889c.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012303_994133e3838e889c.webp" alt="在容器内部找到 waline.sql" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">在容器内部找到 waline.sql</span></div></div><p>使用设置的用户名 waline 和对应的密码登录 mysql 命令行<code>mysql -u waline -p</code>。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:795/352;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012322_531ce58f35c290aa.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012322_531ce58f35c290aa.webp" alt="登录 mysql" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">登录 mysql</span></div></div><p>使用命令<code>show databases;</code>查看数据库，<strong>需要注意 SQL 命令应当以&quot;;&quot;结尾</strong>。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:302/251;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012337_677cf6cc0c49936a.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012337_677cf6cc0c49936a.webp" alt="查看所有数据库" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看所有数据库</span></div></div><p>指定操作的数据库，导入 waline.sql 文件内容。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">use waline_db;</span><br><span class="line">source <span class="operator">/</span>var<span class="operator">/</span>lib<span class="operator">/</span>mysql<span class="operator">/</span>waline.sql;</span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:494/419;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012516_e25b7637d94138fe.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012516_e25b7637d94138fe.webp" alt="导入 waline.sql 文件" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">导入 waline.sql 文件</span></div></div><p>使用命令<code>show tables;</code>查看所有的数据表，使用<code>desc &lt;表名&gt;</code>或<code>show columns from &lt;表名&gt;</code>来查看初始化的结果。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:253/204;width:400px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012538_9856d70ec9021d19.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012538_9856d70ec9021d19.webp" alt="查看所有表" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看所有表</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:840/493;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012555_0ee24e311d4145e1.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012555_0ee24e311d4145e1.webp" alt="查看 wl_Comment 数据表" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看 wl_Comment 数据表</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:875/496;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012606_3028c9587c43610c.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012606_3028c9587c43610c.webp" alt="查看 wl_User 数据表" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看 wl_User 数据表</span></div></div><p>使用<code>exit</code>命令退出 sql 命令行，再次输入<code>exit</code>退出 Docker 环境，至此数据库的初始化已经完成。</p><h4 id="113-初始化数据库-方法二"><a class="markdownIt-Anchor" href="#113-初始化数据库-方法二"></a> 1.1.3 初始化数据库 (方法二)</h4><p>如果担心在命令行中操作数据库会出现错误，还可以借助数据库管理软件操作。这里使用开源的 DBeaver 远程连接服务器上的数据库进行初始化。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:561/677;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326172731_b5755bfe7dd44fc0.webp" data-src="https://cos.curiositynotes.dev/imgs/240326172731_b5755bfe7dd44fc0.webp" alt="DBeaver 配置连接" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">DBeaver 配置连接</span></div></div><p>连接前应当注意将远程服务器防火墙中对应 MySQL 容器映射到主机的端口 (本文中是 3306) 打开。使用的 MySQL 版本为 8.0 时，还需要设置驱动属性里的<code>allowPublicKeyRetrieval</code>为<code>TRUE</code>。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:702/655;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012634_76abae54c44fc843.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012634_76abae54c44fc843.webp" alt="DBeaver 配置驱动属性" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">DBeaver 配置驱动属性</span></div></div><p>连接成功后打开 SQL 编辑器，将 waline.sql 文件内容 (使用文本编辑器打开) 全部复制到输入框。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:737/382;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012656_01165534afc58512.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012656_01165534afc58512.webp" alt="DBeaver 连接成功" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">DBeaver 连接成功</span></div></div><p>点击运行脚本，运行结束后无报错信息即可，展开数据库中的<code>表</code>选项，可以看到初始化成功的结果。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:621/592;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012722_68125be789c0dcd2.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012722_68125be789c0dcd2.webp" alt="运行 SQL 脚本" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">运行 SQL 脚本</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1067/891;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012725_8a3b86d843399c7d.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012725_8a3b86d843399c7d.webp" alt="SQL 脚本运行成功" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">SQL 脚本运行成功</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:963/615;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012729_7868424c25d80ab6.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012729_7868424c25d80ab6.webp" alt="已成功创建数据表" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">已成功创建数据表</span></div></div><blockquote><p><strong>使用此方法初始化成功后，如后续没有直接操作数据库的需求，建议在服务器防火墙中关闭对应端口的访问权限以增加安全性。</strong></p></blockquote><h2 id="2-waline-docker-部署"><a class="markdownIt-Anchor" href="#2-waline-docker-部署"></a> 2 Waline Docker 部署</h2><h3 id="21-使用-docker-compose-部署-waline"><a class="markdownIt-Anchor" href="#21-使用-docker-compose-部署-waline"></a> 2.1 使用 docker-compose 部署 Waline</h3><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">waline_net:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">&quot;mysql_waline_net&quot;</span></span><br><span class="line">    <span class="attr">external:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">waline:</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">waline</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">lizheming/waline:latest</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">networks:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">waline_net</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="number">8360</span><span class="string">:8360</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">$&#123;PWD&#125;/data:/app/data</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="attr">TZ:</span> <span class="string">&quot;Asia/Shanghai&quot;</span></span><br><span class="line">      <span class="attr">MYSQL_HOST:</span> <span class="string">db_mysql</span> <span class="comment"># 使用 MySQL 容器的 services</span></span><br><span class="line">      <span class="attr">MYSQL_PORT:</span> <span class="string">&quot;3306&quot;</span> <span class="comment"># MySQL 容器内部端口</span></span><br><span class="line">      <span class="attr">MYSQL_DB:</span> <span class="string">&quot;waline_db&quot;</span> <span class="comment"># MySQL 数据库名</span></span><br><span class="line">      <span class="attr">MYSQL_USER:</span> <span class="string">&quot;waline&quot;</span> <span class="comment"># MySQL 用户名</span></span><br><span class="line">      <span class="attr">MYSQL_PASSWORD:</span> <span class="string">&quot;waline_password&quot;</span> <span class="comment"># MySQL 密码</span></span><br><span class="line">      <span class="attr">SITE_NAME:</span> <span class="string">&quot;DEMO_BLOG&quot;</span> <span class="comment"># 网站名，会在消息推送中显示</span></span><br><span class="line">      <span class="attr">SITE_URL:</span> <span class="string">&quot;https://blog.demo.com&quot;</span> <span class="comment"># 网站 URL</span></span><br><span class="line">      <span class="attr">AUTHOR_EMAIL:</span> <span class="string">&quot;&quot;</span> <span class="comment"># 博主邮箱，用来区分发布的评论是否是博主</span></span><br><span class="line">      <span class="attr">LOGIN:</span> <span class="string">&quot;&quot;</span> <span class="comment"># 登录后才能评论 (force)</span></span><br><span class="line">      <span class="attr">IPQPS:</span> <span class="number">30</span> <span class="comment"># 基于 IP 的评论发布频率限制，单位为秒</span></span><br><span class="line">      <span class="attr">DISABLE_REGION:</span> <span class="string">&quot;true&quot;</span> <span class="comment"># 是否隐藏评论者的归属地</span></span><br><span class="line">      <span class="attr">DISABLE_USERAGENT:</span> <span class="string">&quot;false&quot;</span> <span class="comment"># 是否隐藏评论者的 UA</span></span><br><span class="line">      <span class="attr">SECURE_DOMAINS:</span> <span class="string">&quot;&quot;</span> <span class="comment"># 安全域名配置，逗号分隔多条</span></span><br><span class="line">      <span class="attr">COMMENT_AUDIT:</span> <span class="string">&quot;yes&quot;</span> <span class="comment"># 评论发布审核开关</span></span><br><span class="line">      <span class="attr">PUSH_PLUS_KEY:</span> <span class="string">&quot;keykeykey&quot;</span> <span class="comment"># pushplus 用户 token</span></span><br><span class="line">      <span class="attr">PUSH_PLUS_CHANNEL:</span> <span class="string">&quot;wechat&quot;</span> <span class="comment"># pushplus 发送渠道</span></span><br></pre></td></tr></table></figure><p>Waline 的配置文件中除了服务器等基础配置外，额外使用 PushPlus 做消息推送，如需使用其他推送服务，对应官网<a href="https://waline.js.org/reference/server/env.html">环境变量配置</a>和<a href="https://waline.js.org/guide/features/notification.html">消息通知</a>修改即可。使用命令<code>docker compose up -d</code>创建并运行容器，容器运行后可以查看<code>waline_net</code>的网关 IP 用于 Nginx 反向代理。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:971/82;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012811_05bae9626bd064b4.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012811_05bae9626bd064b4.webp" alt="查看 docker0 IP" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看 docker0 IP</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1008/867;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/240326012814_e325afe37b5fda9f.webp" data-src="https://cos.curiositynotes.dev/imgs/240326012814_e325afe37b5fda9f.webp" alt="查看 waline_net 网关 IP" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">查看 waline_net 网关 IP</span></div></div><h3 id="22-使用-nginx-反向代理到自定义域名"><a class="markdownIt-Anchor" href="#22-使用-nginx-反向代理到自定义域名"></a> 2.2 使用 Nginx 反向代理到自定义域名</h3><blockquote><p>注意 Nginx 运行在 Docker 容器中时，反向代理的<code>proxy_pass</code>不能使用<code>127.0.0.1</code>或<code>localhost</code>，可以使用公网 IP，<code>docker0</code>网桥的 IP，或者指定的子网的网关 IP。上述配置中，在 Nginx 中可以使用公网 IP，docker0 IP：172.17.0.1 或者 waline_net 子网网关 IP：192.168.16.1。</p></blockquote><p>Nginx 反向代理配置如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">server</span><br><span class="line">    &#123;</span><br><span class="line">        listen 443 ssl http2;</span><br><span class="line">        server_name waline.demo.com;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># SSL设置</span></span><br><span class="line">        <span class="comment"># 证书及私钥</span></span><br><span class="line">        ssl_certificate fullchain.cer;</span><br><span class="line">        ssl_certificate_key demo.com.key;</span><br><span class="line">        <span class="comment"># 缓存超时时间</span></span><br><span class="line">        ssl_session_timeout 5m;</span><br><span class="line">        <span class="comment"># 加密套件的类型</span></span><br><span class="line">        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;</span><br><span class="line">        <span class="comment"># 可用的TLS协议的类型</span></span><br><span class="line">        ssl_protocols TLSv1.2 TLSv1.3;</span><br><span class="line">        <span class="comment"># 优先使用服务器端加密套件</span></span><br><span class="line">        ssl_prefer_server_ciphers on;</span><br><span class="line">        <span class="comment"># 启用HTTP严格传输安全HSTS</span></span><br><span class="line">        add_header Strict-Transport-Security <span class="string">&quot;max-age=31536000; includeSubDomains; preload&quot;</span> always;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># proxy to 8360</span></span><br><span class="line">        location / &#123;</span><br><span class="line">            <span class="comment"># nginx部署在docker中时，反代宿主机或其他容器中的服务时不能使用127.0.0.1</span></span><br><span class="line">            proxy_pass https://172.17.0.1:8360;</span><br><span class="line">            proxy_set_header Host <span class="variable">$host</span>;</span><br><span class="line">            proxy_set_header X-Real-IP <span class="variable">$remote_addr</span>;</span><br><span class="line">            proxy_set_header X-Forwarded-For <span class="variable">$proxy_add_x_forwarded_for</span>;</span><br><span class="line">            proxy_set_header X-Forwarded-Proto <span class="variable">$scheme</span>;</span><br><span class="line">            proxy_set_header REMOTE-HOST <span class="variable">$remote_addr</span>;</span><br><span class="line">            add_header X-Cache <span class="variable">$upstream_cache_status</span>;</span><br><span class="line">            add_header Cache-Control no-cache;</span><br><span class="line">            expires 12h;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>部署结束后可以登录 Waline 管理页面创建管理员账号，后续步骤与其他部署方法相同。</p><h4 id="221-nginx-配置补丁-禁止用户访问-waline-的-demo-页面"><a class="markdownIt-Anchor" href="#221-nginx-配置补丁-禁止用户访问-waline-的-demo-页面"></a> 2.2.1 Nginx 配置补丁 - 禁止用户访问 Waline 的 demo 页面</h4><p>上述配置全部成功后，访问反向代理的自定义域名<code>waline.demo.com</code>时，会显示 Waline 的 demo 页面，如在部署后不想再显示此页面，不允许其他用户直接访问这个页面可以在 Nginx 配置中稍作修改，屏蔽掉对自定义域名的直接访问。</p><p>具体修改为，将原来的<code>location</code>的匹配规则由<code>/</code>修改为<code>~* ^/.+</code>；新增一个精确匹配根路径的规则<code>= /</code>，直接返回<code>403</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 原反代配置</span></span><br><span class="line">location ~* ^/.+ &#123;</span><br><span class="line">    proxy_pass http://172.17.0.1:8360;</span><br><span class="line">    <span class="comment"># ...原反代配置...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment"># 屏蔽根路径访问</span></span><br><span class="line">location = / &#123;</span><br><span class="line">    <span class="built_in">return</span> 403;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>对根路径的屏蔽可以用<code>&quot;= /&quot;</code>精确匹配，也可以用<code>&quot;/&quot;</code>利用匹配规则优先级进行配置</p></blockquote><p>设置完成后，重启 Nginx 或重新加载配置即可实现在不影响评论正常功能的情况下，屏蔽对 demo 页面的访问。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2024-03-26: 初始版本&lt;/li&gt;
&lt;li&gt;2024-04-21: 更新&lt;a href=&quot;#2-2-</summary>
      
    
    
    
    <category term="Blog" scheme="https://curiositynotes.dev/categories/Blog/"/>
    
    
    <category term="Waline" scheme="https://curiositynotes.dev/tags/Waline/"/>
    
    <category term="MySQL" scheme="https://curiositynotes.dev/tags/MySQL/"/>
    
    <category term="Docker" scheme="https://curiositynotes.dev/tags/Docker/"/>
    
    <category term="Nginx" scheme="https://curiositynotes.dev/tags/Nginx/"/>
    
  </entry>
  
  <entry>
    <title>在 Windows 中配置 PlatformIO-IDE 开发环境</title>
    <link href="https://curiositynotes.dev/posts/6650c7d9/"/>
    <id>https://curiositynotes.dev/posts/6650c7d9/</id>
    <published>2023-12-05T03:22:13.000Z</published>
    <updated>2026-05-20T14:08:16.423Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2023-12-04: 配置 PlatformIO IDE 环境，记录安装过程。</li><li>2023-12-06: 更新问题 5-1 的解决情况。</li><li>2023-12-22: 更新问题 5-1 的解决情况。</li><li>TBD</li></ul><h2 id="1-安装前准备"><a class="markdownIt-Anchor" href="#1-安装前准备"></a> 1 安装前准备</h2><p>参考 PlatformIO IDE 的<a href="https://docs.platformio.org/en/latest/integration/ide/vscode.html#installation">安装文档</a>，确保满足文档中要求的环境。对于 Windows 系统，只需要安装 VSCode 即可继续安装 PlatformIO IDE。</p><h3 id="11-设置代理"><a class="markdownIt-Anchor" href="#11-设置代理"></a> 1.1 设置代理</h3><p>受限于网络情况，需要设置代理来顺利地下载 PlatformIO 需要的附件。根据官方文档中关于<a href="https://docs.platformio.org/en/latest/integration/ide/vscode.html#proxy-server-support">代理服务器</a>的说明，有两种方法可以配置 PlatformIO IDE 的代理：</p><ul><li>在 VSCode 的设置中配置：在设置中搜索<code>proxy</code>，设置<code>http.proxy</code>项，并且取消勾选<code>http.proxyStrictSSL</code>项</li><li>设置系统环境变量：设置环境变量<code>HTTP_PROXY</code>，<code>HTTPS_PROXY</code>和<code>PLATFORMIO_SETTING_ENABLE_PROXY_STRICT_SSL</code></li></ul><p>由于还需要设置其他环境变量，这里一并使用设置系统环境变量的方法设置代理。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置代理（cmd）</span></span><br><span class="line">setx HTTP_PROXY <span class="string">&quot;http://127.0.0.1:7890/&quot;</span></span><br><span class="line">setx HTTPS_PROXY <span class="string">&quot;http://127.0.0.1:7890/&quot;</span></span><br><span class="line">setx PLATFORMIO_SETTING_ENABLE_PROXY_STRICT_SSL <span class="string">&quot;false&quot;</span></span><br><span class="line"><span class="comment"># 查看设置的变量（需重新打开命令行窗口）</span></span><br><span class="line"><span class="built_in">echo</span> %HTTP_PROXY%</span><br></pre></td></tr></table></figure><blockquote><p><code>setx</code> 命令功能是在在用户或系统环境创建或修改环境变量，默认创建当前用户变量。在命令最后指定<code>/M</code>参数可以创建系统环境变量，创建系统环境变量时需要以管理员身份运行终端。</p><p><code>set</code> 命令设置终端的环境变量，此处不适用。</p><p>两个代理服务器变量的值内容相同，都是以<code>http://</code>开头。具体的服务器地址和端口根据自己的代理软件设置情况修改。</p><p>代理服务器地址端口号后要有<code>/</code>斜线。</p></blockquote><h3 id="12-设置-platformio-core-的安装目录"><a class="markdownIt-Anchor" href="#12-设置-platformio-core-的安装目录"></a> 1.2 设置 PlatformIO Core 的安装目录</h3><p>PlatformIO Core 安装目录会存储所有的开发平台包（包括工具链、框架、SDK、上传和调试工具等）、全局库以及其他 PlatformIO Core 服务数据。该目录的大小会随着已安装的开发平台的数量增多而不断变大。在 Windows 系统中，默认的安装目录是<code>%HOMEPATH%\.platformio</code>。如果不希望该目录在 C 盘用户目录占用过多空间，可以在安装前设置通过设置环境变量<code>PLATFORMIO_CORE_DIR</code>指定 PlatformIO Core 的安装目录。如果在设置前已安装 PlatformIO Core，设置环境变量后删除原来的<code>.platformio</code>目录，重启 PlatformIO IDE(VSCode) 重新安装即可生效。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置PlatformIO Core安装目录</span></span><br><span class="line">setx PLATFORMIO_CORE_DIR <span class="string">&quot;D:\PIO_Core&quot;</span></span><br></pre></td></tr></table></figure><blockquote><p>设置的目录需要提前创建好，安装过程不会自动创建，如果没有创建环境变量指定的路径，会自动安装到<code>%HOMEPATH%\.platformio</code>默认目录。</p></blockquote><h2 id="2-安装-platformio-ide"><a class="markdownIt-Anchor" href="#2-安装-platformio-ide"></a> 2 安装 PlatformIO IDE</h2><h3 id="21-安装-vscode-插件"><a class="markdownIt-Anchor" href="#21-安装-vscode-插件"></a> 2.1 安装 VSCode 插件</h3><p>在 VSCode 拓展商店中搜索<code>platformio ide</code>，安装。</p><blockquote><p>安装成功后侧栏会显示 PlatformIO 图标，点击该图标，插件会开始下载 PlatformIO Core 和内置的 Python 解释器。</p><p>即使本机已经配置有满足条件的 Python3 环境（Python &gt; 3.6, PlatformIO IDE 不支持 conda 环境<a href="https://github.com/platformio/platformio-vscode-ide/issues/914#issuecomment-1107620596">#issue914</a>），仍然建议使用 IDE 插件内置的 Python。</p></blockquote><h3 id="22-修改插件设置"><a class="markdownIt-Anchor" href="#22-修改插件设置"></a> 2.2 修改插件设置</h3><p>打开 VSCode 设置，搜索<code>platformio</code>，找到 PlatformIO IDE 插件设置。修改以下设置：</p><ul><li>勾选<code>Disable PIOHome Startup</code>：启动时不自动启动 PlatformIO Home，PIOHome 是 PlatformIO 的交互式用户界面。</li><li>编辑<code>customPyPiIndexUrl</code>：设置 IDE 内置的 Python 解释器的<code>pip</code>源：<code>https://pypi.tuna.tsinghua.edu.cn/simple/</code></li></ul><p>对应的设置<code>json</code>为：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;platformio-ide.disablePIOHomeStartup&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line"><span class="attr">&quot;platformio-ide.customPyPiIndexUrl&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://pypi.tuna.tsinghua.edu.cn/simple/&quot;</span></span><br></pre></td></tr></table></figure><p>其他设置保持默认即可，这里使用在线安装 PlatformIO Core（由 IDE 插件自动安装），不需要指定<code>customPATH</code>；PIO Home 在浏览器中的使用体验要好于在 VSCode 中直接打开，这里禁止 PIO Home 在 VSCode 中自动打开，需要使用时在终端中运行<code>pio home</code>，在弹出的浏览器中使用 PIO Home 功能。</p><h3 id="23-打开-platformio-ide-插件"><a class="markdownIt-Anchor" href="#23-打开-platformio-ide-插件"></a> 2.3 打开 PlatformIO IDE 插件</h3><p>打开 PlatformIO IDE 插件，开始自动下载安装相关内容，此步骤耗时较长，代理配置正确且速度良好的情况下，大约需要 1~3 分钟。安装成功后提示重新加载或重启 VSCode。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:800/380;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_811f543fd407958f.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_811f543fd407958f.webp" alt="安装成功提示重载 VSCode" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">安装成功提示重载 VSCode</span></div></div><h2 id="3-安装后设置"><a class="markdownIt-Anchor" href="#3-安装后设置"></a> 3 安装后设置</h2><h3 id="31-安装pio命令"><a class="markdownIt-Anchor" href="#31-安装pio命令"></a> 3.1 安装<code>pio</code>命令</h3><p>PlatformIO Core 的一些设置需要使用<code>pio</code>命令，安装时并未自动添加到<code>Path</code>路径。<code>pio</code>命令的可执行文件安装位置在<code>%PLATFORMIO_CORE_DIR%\penv\Scripts</code>，其中<code>PLATFORMIO_CORE_DIR</code>为安装前指定的 PlatformIO Core 的安装目录。将这个目录添加到系统或用户<code>Path</code>环境变量中，可以在任意终端中使用<code>pio</code>命令。编辑<code>Path</code>环境变量建议使用图形界面操作，防止使用命令误操作覆盖原变量内容。</p><p>打开运行对话框（Win+R），输入<code>sysdm.cpl</code>，打开系统属性，进入高级选项卡，打开环境变量，双击<code>Path</code>进行编辑。添加<code>pio</code>命令所在路径<code>%PLATFORMIO_CORE_DIR%\penv\Scripts</code>。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:612/662;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_3e35bbe4519cd85f.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_3e35bbe4519cd85f.webp" alt="编辑环境变量" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">编辑环境变量</span></div></div><p>添加完成后，重新打开一个命令行终端窗口，使用<code>pio settings get</code>测试，能正常列出 PIO Core 配置信息说明配置正常。否则可能需要重启电脑使环境变量生效。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:2275/350;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_b4d03c99faf1c6ab.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_b4d03c99faf1c6ab.webp" alt="PIO 命令列出所有设置" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">PIO 命令列出所有设置</span></div></div><blockquote><p><code>pio</code>命令的其他<a href="https://docs.platformio.org/en/latest/core/userguide/cmd_settings.html">用法参考</a></p></blockquote><h3 id="32-设置默认项目目录"><a class="markdownIt-Anchor" href="#32-设置默认项目目录"></a> 3.2 设置默认项目目录</h3><p>插件安装后新建项目的默认目录设置在当前用户的文档<code>Documents</code>目录，如需切换到其他目录，需要使用<code>pio</code>命令设置默认项目目录。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置默认目录</span></span><br><span class="line">pio settings <span class="built_in">set</span> projects_dir <span class="string">&quot;D:\PIOProjects&quot;</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1606/216;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_47a4da01497a1d76.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_47a4da01497a1d76.webp" alt="PIO 命令设置默认项目目录" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">PIO 命令设置默认项目目录</span></div></div><p>方括号中以黄色字体显示的路径为默认路径。<code>pio settings</code>的其他设置项类似，如有修改，默认设置总是在当前设置后的方括号中显示。</p><h3 id="33-恢复代理设置的环境变量"><a class="markdownIt-Anchor" href="#33-恢复代理设置的环境变量"></a> 3.3 恢复代理设置的环境变量</h3><p>安装后应当恢复系统代理设置的环境变量，避免对其他程序产生影响。不过在首次创建项目和首次上传到开发板时仍需要下载一些资源，可以在创建项目、编译、下载整个流程运行成功后再恢复代理设置。恢复代理设置只需要清空或直接删除这三个环境变量即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 恢复代理设置（cmd）</span></span><br><span class="line">setx HTTP_PROXY <span class="string">&quot;&quot;</span></span><br><span class="line">setx HTTPS_PROXY <span class="string">&quot;&quot;</span></span><br><span class="line">setx PLATFORMIO_SETTING_ENABLE_PROXY_STRICT_SSL <span class="string">&quot;&quot;</span></span><br></pre></td></tr></table></figure><h2 id="4-构建使用-arduino-框架的-esp32-项目"><a class="markdownIt-Anchor" href="#4-构建使用-arduino-框架的-esp32-项目"></a> 4 构建使用 Arduino 框架的 ESP32 项目</h2><h3 id="41-新建项目"><a class="markdownIt-Anchor" href="#41-新建项目"></a> 4.1 新建项目</h3><p>使用的硬件为 ESP32-S3-WROOM-1-N16R8（8MB PSRAM + 16MB FLASH）开发板，由于此开发板不在 PlatformIO 的开发板库中，可以选用相近的硬件配置改造其配置文件支持此开发板。</p><p>在 PIO Home 中点击新建项目（New Project）</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1048/817;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_a1a9f6e562f8614e.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_a1a9f6e562f8614e.webp" alt="新建 ESP32 项目" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">新建 ESP32 项目</span></div></div><table><thead><tr><th style="text-align:center">项目名称 Name</th><th style="text-align:center">开发板模型 Board</th><th style="text-align:center">开发框架 Framework</th><th style="text-align:center">项目路径 Location</th></tr></thead><tbody><tr><td style="text-align:center">esp32_demo</td><td style="text-align:center">Espressif ESP32-S3-DevKitC-1-N8</td><td style="text-align:center">Arduino</td><td style="text-align:center">path/of/project</td></tr></tbody></table><blockquote><p>新建项目耗时较长，首次新建项目时，PIO 会下载选定的开发框架所需的库和所选开发板模型所属的开发平台的工具链文件等。在当前项目中，这些文件约为 1.9GB。下载这些文件时，仍需要按照安装 PIO IED 插件时的方法配置代理。</p><p>首次新建项目后，新建相同平台及相同框架下的项目则不再需要额外的下载，可以不配置代理。</p><p>如需新建其他平台或者其他框架下的项目，就需要下载新的库文件和工具链文件，需要再次配置代理以保证顺利下载。</p></blockquote><p>选用<code>esp32-s3-devkitc-1</code>作为开发板模型新建，修改<code>platformio.ini</code>为：(参考<a href="https://www.cnblogs.com/macrored/p/17357581.html">文章</a>)</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[env:esp32s3]</span></span><br><span class="line"><span class="attr">platform</span> = espressif32</span><br><span class="line"><span class="attr">board</span> = esp32-s3-devkitc-<span class="number">1</span></span><br><span class="line"><span class="attr">framework</span> = ardui<span class="literal">no</span></span><br><span class="line"><span class="comment">; 指定为16MB的FLASH分区表</span></span><br><span class="line"><span class="attr">board_build.arduino.partitions</span> = default_16MB.csv</span><br><span class="line"><span class="comment">; 指定FLASH和PSRAM的运行模式</span></span><br><span class="line"><span class="attr">board_build.arduino.memory_type</span> = qio_opi</span><br><span class="line"><span class="comment">; 预定义宏，启用PSRAM</span></span><br><span class="line"><span class="attr">build_flags</span> = -DBOARD_HAS_PSRAM</span><br><span class="line"><span class="comment">; 指定FLASH容量为16MB</span></span><br><span class="line"><span class="attr">board_upload.flash_size</span> = <span class="number">16</span>MB</span><br></pre></td></tr></table></figure><h3 id="42-安装需要的第三方库"><a class="markdownIt-Anchor" href="#42-安装需要的第三方库"></a> 4.2 安装需要的第三方库</h3><p>新版本的 PlatformIO 不建议使用全局方式安装库，并且直接在 IDE 中删除了安装到全局库的功能。在 PIO Home 中搜索到需要的库，只能添加到当前的项目中，库文件存储路径位于<code>.pio\libdeps\&lt;env-name&gt;</code>目录中。</p><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1232/741;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_3ef1e177ae291384.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_3ef1e177ae291384.webp" alt="搜索需要的第三方库" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">搜索需要的第三方库</span></div></div><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1046/645;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_39be77d1e10e00e5.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_39be77d1e10e00e5.webp" alt="将库添加到项目中" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">将库添加到项目中</span></div></div><p>将库直接存放于项目目录中，虽然有利于项目之间保持隔离，也可以放心的对第三方库进行修改。但是对于不需要修改的库，每次新建项目都需要重新安装或者复制文件，或者在想要更新所有项目中的某个库时，便显得比较麻烦。目前 PIO Core（CLI）还保留着<a href="https://docs.platformio.org/en/latest/core/userguide/lib/index.html#cmdoption-pio-lib-g">安装全局库的命令</a>，全局安装的路径为<a href="https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/globallib_dir.html#globallib-dir"><code>globallib-dir</code></a>，默认位置在<code>core_dir/lib</code>，已被标记为<strong>DEPRECATED</strong>。全局安装库的命令为：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pio lib -g install <span class="string">&quot;bblanchon/ArduinoJson@^6.21.3&quot;</span></span><br></pre></td></tr></table></figure><div class="tag-plugin image"><div class="image-bg" style="aspect-ratio:1261/328;width:600px;"><img class="lazy" src="https://cos.curiositynotes.dev/imgs/1206171053_577a368a4aa9c781.webp" data-src="https://cos.curiositynotes.dev/imgs/1206171053_577a368a4aa9c781.webp" alt="全局安装提示 WARNING" data-fancybox="true"onerror="this.src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2rem' height='2rem' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='%23F44336' d='M22 12.698c-.002 1.47-.013 2.718-.096 3.743c-.097 1.19-.296 2.184-.74 3.009a4.2 4.2 0 0 1-.73.983c-.833.833-1.893 1.21-3.237 1.39C15.884 22 14.2 22 12.053 22h-.106c-2.148 0-3.83 0-5.144-.177c-1.343-.18-2.404-.557-3.236-1.39c-.738-.738-1.12-1.656-1.322-2.795c-.2-1.12-.236-2.512-.243-4.241Q1.999 12.737 2 12v-.054c0-2.148 0-3.83.177-5.144c.18-1.343.557-2.404 1.39-3.236s1.893-1.21 3.236-1.39c1.168-.157 2.67-.175 4.499-.177a.697.697 0 1 1 0 1.396c-1.855.002-3.234.018-4.313.163c-1.189.16-1.906.464-2.436.994S3.72 5.8 3.56 6.99C3.397 8.2 3.395 9.788 3.395 12v.784l.932-.814a2.14 2.14 0 0 1 2.922.097l3.99 3.99a1.86 1.86 0 0 0 2.385.207l.278-.195a2.79 2.79 0 0 1 3.471.209l2.633 2.37c.265-.557.423-1.288.507-2.32c.079-.972.09-2.152.091-3.63a.698.698 0 0 1 1.396 0' opacity='.5'/%3E%3Cpath fill='%23F44336' fill-rule='evenodd' d='M17.5 11c-2.121 0-3.182 0-3.841-.659S13 8.621 13 6.5s0-3.182.659-3.841S15.379 2 17.5 2s3.182 0 3.841.659S22 4.379 22 6.5s0 3.182-.659 3.841S19.621 11 17.5 11m-1.47-7.03a.75.75 0 1 0-1.06 1.06l1.47 1.47l-1.47 1.47a.75.75 0 0 0 1.06 1.06l1.47-1.47l1.47 1.47a.75.75 0 1 0 1.06-1.06L18.56 6.5l1.47-1.47a.75.75 0 0 0-1.06-1.06L17.5 5.44z' clip-rule='evenodd'/%3E%3C/svg%3E&quot;"/><div class="lazy-icon" style="background-image:url(https://api.iconify.design/eos-icons:three-dots-loading.svg?color=%231cd0fd);"></div></div><div class="image-meta"><span class="image-caption center">全局安装提示 WARNING</span></div></div><p>可以看到，当前命令在下个版本中也会被移除，所以不在推荐使用这个方法安装全局库。</p><p>可选的“全局”安装方法可以参考官方文档中关于<a href="https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html#package-specifications">指定工具包</a>的示例，使用<code>file://</code>或者<code>symlink://</code>方式导入存储在本地的自定义全局目录中的库，二者区别在于</p><ul><li><code>file://</code>：使用复制的方式将自定义全局库目录中的库文件复制到了<code>.pio\libdeps\&lt;env-name&gt;</code>目录中，修改全局库不影响当前项目的库；</li><li><code>symlink://</code>：是对自定义全局库目录中的库文件进行引用，对全局库修改时，影响所有使用<code>symlink://</code>的项目。</li></ul><blockquote><p>使用导入库文件目录或者 ZIP 包的方式导入的库，在指定路径下必须包含 library.json、platform.json 或 package.json 等属性说明文件。<br />一般下载到的 ZIP 压缩包，会多一级目录，直接引用下载的压缩包会导致 IDE 不能识别。</p><p>可以利用<code>symlink://</code>方法让 PlatformIO IDE 使用 Arduino IDE 下载的库。</p><p>使用全局安装命令<code>pio lib -g</code>和使用导入本地文件的方式安装后，都可以在 PIO Home 中进行查看和管理，如果安装后没有刷新，需要重启 PIO Home。</p></blockquote><p>PIO 编译时的库<a href="https://docs.platformio.org/en/stable/librarymanager/ldf.html#storage">查找顺序</a>：</p><ul><li>lib_dir - own/private library storage per project</li><li>libdeps_dir - project dependency storage used by Library Management</li><li>“core_dir/lib” - global storage per all projects.</li><li>Library storages built into frameworks, SDKs.</li></ul><h3 id="43-设置串口"><a class="markdownIt-Anchor" href="#43-设置串口"></a> 4.3 设置串口</h3><p>在<code>platformio.ini</code>文件中添加串口配置，设置可选项参考<a href="https://docs.platformio.org/en/latest/projectconf/sections/env/options/upload/index.html">上传设置</a>和串口<a href="https://docs.platformio.org/en/latest/projectconf/sections/env/options/monitor/index.html">监视器设置</a>，设置的值可以参考 Arduino IDE 中同类型开发板的默认配置。</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">; Upload firmware to device</span></span><br><span class="line"><span class="attr">upload_port</span>=COM3</span><br><span class="line"><span class="attr">upload_speed</span>=<span class="number">921600</span></span><br><span class="line"><span class="comment">; Serial monitor</span></span><br><span class="line"><span class="attr">monitor_port</span>=COM3</span><br><span class="line"><span class="attr">monitor_speed</span>=<span class="number">115200</span></span><br></pre></td></tr></table></figure><h3 id="44-编译上传"><a class="markdownIt-Anchor" href="#44-编译上传"></a> 4.4 编译上传</h3><ul><li>Build：编译</li><li>Upload：上传（下载到开发板）。首次进行上传时，还需要下载几个用于处理文件系统的工具，可能<strong>仍然需要代理</strong>才能够流畅下载。</li><li>Clean：删除编译产生的文件</li><li>Full Clean：删除编译产生的文件和库文件，删除后库文件会重新下载，<strong>如果对第三方库进行了修改，要慎用此功能</strong>。</li></ul><h2 id="5-一些问题与解决方法"><a class="markdownIt-Anchor" href="#5-一些问题与解决方法"></a> 5 一些问题与解决方法</h2><h3 id="1编译出现createprocess-no-such-file-or-directory错误"><a class="markdownIt-Anchor" href="#1编译出现createprocess-no-such-file-or-directory错误"></a> 1.编译出现<code>CreateProcess: No such file or directory</code>错误</h3><p>在某次 Windows10 的安装中，安装后编译出现错误：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">xtensa-esp32s3-elf-gcc: error: CreateProcess: No such file or directory</span><br><span class="line">*** [.pio\build\esp32-s3-devkitc-1\FrameworkArduino\esp32-hal-rgb-led.c.o] Error 1</span><br></pre></td></tr></table></figure><p>反复点击编译，编译能够逐渐进行，出现类似的几次错误（每次在不同的文件上出现找不到文件）后能够编译完成，并且正确的下载到开发板上运行。尝试重新安装 VSCode，重新安装 PlatformIO IDE 插件，禁用除 PlatformIO IDE 和 C/C++ 插件以外的所有插件，重新安装 PlatformIO Core，重新下载开发平台工具包，修改 Core 目录（包括不同分区），修改项目目录（包括不同分区），以及搜索到的有关于命令长度超出 Windows 限制的相关 issues 中提及的方法，<strong>均不能</strong>修复这一问题。以相同步骤在相同版本的 Windows10 的虚拟机中再次安装，一切运行正常。此问题尚未找到原因，也未解决。</p><blockquote><p>2023-12-06 更新：通过重装系统解决了这个问题……原因仍然未知。<br />2023-12-22 更新：突然又出现了这个问题，排查最近安装的软件发现可能是一个网盘（有同步盘功能）客户端导致的问题，怀疑是编译产生的文件会被同步盘占用导致后续编译过程找不到文件。卸载该软件后恢复正常。</p></blockquote><h3 id="2-在-vscode-中打开-pio-home-卡在-loading"><a class="markdownIt-Anchor" href="#2-在-vscode-中打开-pio-home-卡在-loading"></a> 2. 在 VSCode 中打开 PIO Home 卡在 Loading</h3><p>关闭 VSCode，在任务管理器中结束所有和 VSCode、PlatformIO、pio 有关的应用和后台进程，重新打开 VSCode。若无效则重启电脑。</p><blockquote><p>PIO Home 在浏览器中的使用体验要好于在 VSCode 中直接打开，更好的使用方式是在终端中运行<code>pio home</code>，在弹出的浏览器中使用 PIO Home。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2023-12-04: 配置 PlatformIO IDE 环境，记录安装过程。&lt;/li&gt;
&lt;li&gt;2023</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="PlatformIO" scheme="https://curiositynotes.dev/tags/PlatformIO/"/>
    
    <category term="ESP32" scheme="https://curiositynotes.dev/tags/ESP32/"/>
    
    <category term="嵌入式" scheme="https://curiositynotes.dev/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>同时使用不同的 GitHub 账号管理仓库</title>
    <link href="https://curiositynotes.dev/posts/b7c24d2f/"/>
    <id>https://curiositynotes.dev/posts/b7c24d2f/</id>
    <published>2023-11-07T08:20:51.000Z</published>
    <updated>2026-05-20T14:08:16.421Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-更新记录"><a class="markdownIt-Anchor" href="#0-更新记录"></a> 0 更新记录</h2><ul><li>2023-11-07: 同时使用两个 GitHub 账号。</li><li>2023-12-27: 更新使用 GPG 密钥部分。</li><li>2024-10-16: 更新一些命令输出。</li></ul><p>有时需要在一台电脑中，同时使用不同的 GitHub 账号管理不同的仓库，解决方法是取消全局的用户名和邮箱设置，分别配置不同仓库的用户名和邮箱，并在 ssh 配置中，为每个 GitHub 远程仓库地址配置对应的私钥。</p><p>下面以用户<code>user_aaa</code>、邮箱<code>user_aaa@demo.com</code>和用户<code>user_bbb</code>、邮箱<code>user_bbb@demo.com</code>为例配置，分别在 Windows 10/11、Debian 12 及 Ubuntu 20/22 上测试正常。</p><h2 id="1-清除全局配置"><a class="markdownIt-Anchor" href="#1-清除全局配置"></a> 1 清除全局配置</h2><p>使用多个 Github 账号时需要给每个 git 仓库设置单独的账号，应当清除全局配置的用户名和邮箱：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 检查全局配置</span></span><br><span class="line">git config --global --list</span><br><span class="line"><span class="comment"># 删除全局用户名和邮箱</span></span><br><span class="line">git config --global --<span class="built_in">unset</span> user.name</span><br><span class="line">git config --global --<span class="built_in">unset</span> user.email</span><br></pre></td></tr></table></figure><h2 id="2-配置-ssh-密钥对"><a class="markdownIt-Anchor" href="#2-配置-ssh-密钥对"></a> 2 配置 SSH 密钥对</h2><h3 id="21-生成每个账号的-ssh-密钥对"><a class="markdownIt-Anchor" href="#21-生成每个账号的-ssh-密钥对"></a> 2.1 生成每个账号的 SSH 密钥对</h3><p>生成密钥时需修改路径及文件名，默认路径 <code>~/.ssh</code>，公钥的默认文件名为<code>id_ed25519.pub</code>，私钥的默认文件名为<code>id_ed25519</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># user_aaa SSH Key:id_ed25519_user_aaa, id_ed25519_user_aaa.pub</span></span><br><span class="line">ssh-keygen -t ed25519 -C <span class="string">&quot;user_aaa@demo.com&quot;</span></span><br><span class="line"><span class="comment"># 为了便于区分，在文件名后添加实际用户名</span></span><br><span class="line"><span class="built_in">mv</span> id_ed25519 id_ed25519_user_aaa</span><br><span class="line"><span class="built_in">mv</span> id_ed25519.pub id_ed25519_user_aaa.pub</span><br><span class="line"></span><br><span class="line"><span class="comment"># user_bbb SSH Key:id_ed25519_user_bbb, id_ed25519_user_bbb.pub</span></span><br><span class="line">ssh-keygen -t ed25519 -C <span class="string">&quot;user_bbb@demo.com&quot;</span></span><br><span class="line"><span class="comment"># 为了便于区分，在文件名后添加实际用户名</span></span><br><span class="line"><span class="built_in">mv</span> id_ed25519 id_ed25519_user_bbb</span><br><span class="line"><span class="built_in">mv</span> id_ed25519.pub id_ed25519_user_bbb.pub</span><br></pre></td></tr></table></figure><h3 id="22-配置-ssh-agent可选"><a class="markdownIt-Anchor" href="#22-配置-ssh-agent可选"></a> 2.2 配置 ssh-agent（可选）</h3><p>将所有<strong>私钥</strong>添加至 ssh-agent，并配置 ssh-agent 自启动，<a href="https://zhuanlan.zhihu.com/p/126117538">ssh-agent 详解</a>，计算机管理 - 服务-OpenSSH Authentication Agent-自动启动：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 启动 ssh-agent</span></span><br><span class="line"><span class="built_in">eval</span> <span class="string">&quot;<span class="subst">$(ssh-agent -s)</span>&quot;</span></span><br><span class="line"><span class="comment"># 添加私钥</span></span><br><span class="line">ssh-add ~/.ssh/id_ed25519_user_aaa</span><br><span class="line">ssh-add ~/.ssh/id_ed25519_user_bbb</span><br><span class="line"><span class="comment"># 检查是否添加成功</span></span><br><span class="line">ssh-add -l</span><br><span class="line"><span class="comment"># 256 SHA256:1G+KrSt3szM...2hs user_aaa@demo.com (ED25519)</span></span><br><span class="line"><span class="comment"># 256 SHA256:g0xZJcaUA93...HCY user_bbb@demo.com (ED25519)</span></span><br></pre></td></tr></table></figure><h3 id="23-将-ssh-公钥添加到-github"><a class="markdownIt-Anchor" href="#23-将-ssh-公钥添加到-github"></a> 2.3 将 SSH 公钥添加到 GitHub</h3><p>将各账号<strong>公钥</strong>（<code>cat ~/.ssh/id_ed25519_user_xxx.pub</code>）填写至 <a href="https://github.com/settings/ssh/new">GitHub Settings - SSH keys</a></p><h3 id="24-修改-ssh-配置文件"><a class="markdownIt-Anchor" href="#24-修改-ssh-配置文件"></a> 2.4 修改 ssh 配置文件</h3><p>修改 SSH 配置文件 <code>~/.ssh/config</code>，指定远程主机的别名，各自使用的账号和<strong>私钥</strong>文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Host user_aaa.github.com</span><br><span class="line">    HostName github.com</span><br><span class="line">    User user_aaa</span><br><span class="line">    IdentityFile ~/.ssh/id_ed25519_user_aaa</span><br><span class="line"></span><br><span class="line">Host user_bbb.github.com</span><br><span class="line">    HostName github.com</span><br><span class="line">    User user_bbb</span><br><span class="line">    IdentityFile ~/.ssh/id_ed25519_user_bbb</span><br></pre></td></tr></table></figure><div class="tag-plugin colorful note" color="cyan"><div class="body"><p><strong>参数说明：</strong></p><ul><li><em>Host</em> 设置主机别名，用于标识一个配置，可设置成任意字符串，并非必须使用域名形式。</li><li><em>HostName</em> 设置主机地址，即主机 IP 或域名。</li><li><em>User</em> 设置登录的用户名。</li><li><em>IdentifyFile</em> 设置对应用户的密钥文件。</li><li><em>Port</em> 设置 SSH 端口。</li></ul><p>在 GitHub 应用中，<em>User</em>参数可以不设置。</p><p>设置 Host 别名后，SSH/SCP 可使用主机别名代替<code>user@IP</code>：</p><ul><li>例如<code>scp a.txt demo@192.168.0.101:~</code> =&gt; <code>scp a.txt HOST:~</code>；</li><li>或者<code>ssh demo@192.168.0.101</code> =&gt; <code>ssh HOST</code>。</li></ul></div></div><h3 id="25-测试与-github-的连接"><a class="markdownIt-Anchor" href="#25-测试与-github-的连接"></a> 2.5 测试与 GitHub 的连接</h3><p>必须使用 git 作为用户名测试，<a href="https://docs.github.com/zh/authentication/troubleshooting-ssh/error-permission-denied-publickey#%E5%A7%8B%E7%BB%88%E4%BD%BF%E7%94%A8-git-%E7%94%A8%E6%88%B7">原因</a>，主机使用主机别名，首次与 GitHub 连接需要输入 yes 设置 known_hosts。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ssh -T git@user_aaa.github.com</span><br><span class="line"><span class="comment"># &quot;Hi user_aaa! You&#x27;ve successfully authenticated, but GitHub does not provide shell access.&quot;</span></span><br><span class="line">ssh -T git@user_bbb.github.com</span><br><span class="line"><span class="comment"># &quot;Hi user_bbb! You&#x27;ve successfully authenticated, but GitHub does not provide shell access.&quot;</span></span><br><span class="line"><span class="comment"># 连接出现问题时，使用 -v 参数进入调试模式，显示详细的连接过程</span></span><br><span class="line">ssh -v git@user_bbb.github.com</span><br></pre></td></tr></table></figure><h3 id="26-修改-git-仓库的远程地址"><a class="markdownIt-Anchor" href="#26-修改-git-仓库的远程地址"></a> 2.6 修改 Git 仓库的远程地址</h3><p>为仓库设置<strong>使用主机别名</strong>的远程地址。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>进入对应仓库目录后设置</p></div></div><ul><li>新 clone 的仓库在 clone 时可以直接修改远程地址：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用主机别名代替 GitHub 域名</span></span><br><span class="line"><span class="comment"># 原地址：git@github.com:user_aaa/git_repo.git</span></span><br><span class="line"><span class="comment"># 修改为：git@user_aaa.github.com:user_aaa/git_repo.git</span></span><br><span class="line">git <span class="built_in">clone</span> git@user_aaa.github.com:user_aaa/git_repo.git</span><br><span class="line"><span class="built_in">cd</span> git_repo/</span><br><span class="line"><span class="comment"># 检查远程地址</span></span><br><span class="line">git remote -v</span><br><span class="line"><span class="comment"># &quot;origin  git@user_aaa.github.com:user_aaa/git_repo.git (fetch)&quot;</span></span><br><span class="line"><span class="comment"># &quot;origin  git@user_aaa.github.com:user_aaa/git_repo.git (push)&quot;</span></span><br></pre></td></tr></table></figure><ul><li>本地已有的仓库/新建的仓库修改远程地址：</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> git_repo/</span><br><span class="line"><span class="comment"># 检查远程地址</span></span><br><span class="line">git remote -v</span><br><span class="line"><span class="comment"># &quot;origin  git@github.com:user_aaa/git_repo.git (fetch)&quot;</span></span><br><span class="line"><span class="comment"># &quot;origin  git@github.com:user_aaa/git_repo.git (push)&quot;</span></span><br><span class="line"><span class="comment"># 删除后添加</span></span><br><span class="line">git remote <span class="built_in">rm</span> origin</span><br><span class="line">git remote add origin git@user_aaa.github.com:user_aaa/git_repo.git</span><br></pre></td></tr></table></figure><h3 id="27-修改-git-仓库的账号信息"><a class="markdownIt-Anchor" href="#27-修改-git-仓库的账号信息"></a> 2.7 修改 Git 仓库的账号信息</h3><p>为仓库设置单独的用户名和邮箱。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>进入对应仓库目录后设置</p></div></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> git_repo/</span><br><span class="line">git config user.name <span class="string">&quot;user_aaa&quot;</span></span><br><span class="line">git config user.email <span class="string">&quot;user_aaa@demo.com&quot;</span></span><br></pre></td></tr></table></figure><p>至此，已经完成了在一台电脑中，同时使用不同的 GitHub 账号管理不同的仓库的设置。</p><p>以下内容是在 GitHub 中使用 GPG 签名的设置过程，GPG 是一种安全机制，可以确保提交的真实性，防止其他人使用你的邮箱和用户名“冒充”你提交代码。</p><h2 id="3-使用-gpg"><a class="markdownIt-Anchor" href="#3-使用-gpg"></a> 3 使用 GPG</h2><p>与设置 SSH 密钥的过程类似，每个账号都需要自己的 GPG 密钥，以下仅记录单个账号的设置过程：</p><h3 id="31-生成-gpg-密钥"><a class="markdownIt-Anchor" href="#31-生成-gpg-密钥"></a> 3.1 生成 GPG 密钥</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --full-generate-key</span><br></pre></td></tr></table></figure><p>生成密钥时需要填写用户名和邮箱，邮箱必须填写在 Git 仓库中设置的账号的邮箱，而且必须是 GitHub 账户中经过验证的邮箱地址。不同账号对应不同的邮箱，也就对应不同的 GPG 密钥。这一步无特殊需要可以不设置密码，否则后续使用中每次使用 GPG 签名时（如提交 commit 和 tag 时）都需要输入 GPG 密钥的密码进行验证。</p><p>使用<code>gpg --list-keys</code>可以查看本机所有的 GPG 密钥信息，指定参数<code>--keyid-format long</code>使用长 ID 格式，<code>pub</code>中的 40 位字符串是该 GPG 密钥指纹（Fingerprint），也是 GPG 密钥的 ID，用于标识这个 GPG 密钥；长 ID 是指纹的后 16 位字符，即<code>pub    rsa3072/</code>后面的 16 位字符，短 ID（用参数<code>--keyid-format short</code>查看）为指纹的后 8 位字符。</p><p>在不造成混淆的情况下，使用指纹、长 ID 或者短 ID 都可以。一般使用长 ID，以下命令中用<code>&lt;key-id&gt;</code>代替具体的长 ID。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">gpg --list-keys --keyid-format long</span><br><span class="line"><span class="comment"># /c/Users/abc/.gnupg/pubring.kbx</span></span><br><span class="line"><span class="comment"># ------------------------------</span></span><br><span class="line"><span class="comment"># pub   rsa3072/XXXXXXXXXXXXXEF1 2023-10-16 [SC]</span></span><br><span class="line"><span class="comment">#       466XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEF1</span></span><br><span class="line"><span class="comment"># uid                 [ultimate] user_aaa &lt;user_aaa@demo.com&gt;</span></span><br><span class="line"><span class="comment"># sub   rsa3072/XXXXXXXXXXXXX736 2023-10-16 [E]</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pub   rsa3072/XXXXXXXXXXXXX9E2 2023-12-27 [SC]</span></span><br><span class="line"><span class="comment">#       D71XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX9E2</span></span><br><span class="line"><span class="comment"># uid                 [ultimate] user_bbb &lt;user_bbb@demo.com&gt;</span></span><br><span class="line"><span class="comment"># sub   rsa3072/XXXXXXXXXXXXXF47 2023-12-27 [E]</span></span><br></pre></td></tr></table></figure><h3 id="32-将-gpg-公钥添加到-github"><a class="markdownIt-Anchor" href="#32-将-gpg-公钥添加到-github"></a> 3.2 将 GPG 公钥添加到 Github</h3><p>使用命令<code>gpg --armor --export &lt;key_id&gt;</code>可以根据密钥的 ID 显示其公钥，将对应账号的公钥信息在终端中导出并复制，添加到对应 GitHub 账户中，添加位置 <a href="https://github.com/settings/gpg/new">GitHub Settings - GPG keys</a>。</p><h3 id="33-使用-gpg-签名-commit-和-tag"><a class="markdownIt-Anchor" href="#33-使用-gpg-签名-commit-和-tag"></a> 3.3 使用 GPG 签名 commit 和 tag</h3><p>首先在 Git 仓库中指定使用的 GPG 密钥，通过密钥的 ID 指定。需要注意的是由于不同账号使用的是不同的 GPG 密钥，设置时应使用局部设置，不要使用全局设置。这里的<code>--local</code>可以不加，在不指定<code>--local</code>时默认进行的就是局部设置。</p><div class="tag-plugin colorful note" color="yellow"><div class="body"><p>进入对应仓库目录后设置</p></div></div><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --<span class="built_in">local</span> user.signingkey &lt;key-id&gt;</span><br></pre></td></tr></table></figure><p>在执行<code>git commit</code>时加入<code>-S</code>参数（注意为大写 S），可以使用 GPG 签名当前的 commit 提交：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit -S -m <span class="string">&quot;commit message&quot;</span></span><br></pre></td></tr></table></figure><p>方便起见可以设置默认使用 GPG 签名新 commit，注意是本地局部设置：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --<span class="built_in">local</span> commit.gpgsign <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>在执行<code>git tag</code>时使用<code>-s</code>参数替换<code>-a</code>（注意为小写 s），可以使用 GPG 签名当前的标签：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 带GPG签名的标签</span></span><br><span class="line">git tag -s &lt;tag_name&gt; -m <span class="string">&quot;tag_message&quot;</span> [optional:Hash]</span><br></pre></td></tr></table></figure><p>同样可以设置默认使用 GPG 签名新标签，设置后不论是否使用<code>-s</code>参数创建标签，均会使用指定的 GPG 密钥进行签名：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --<span class="built_in">local</span> tag.gpgsign <span class="literal">true</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;0-更新记录&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#0-更新记录&quot;&gt;&lt;/a&gt; 0 更新记录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2023-11-07: 同时使用两个 GitHub 账号。&lt;/li&gt;
&lt;li&gt;2023-12-27: 更新使</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Git" scheme="https://curiositynotes.dev/tags/Git/"/>
    
    <category term="Github" scheme="https://curiositynotes.dev/tags/Github/"/>
    
  </entry>
  
  <entry>
    <title>在 Ubuntu 中安装配置 Zsh</title>
    <link href="https://curiositynotes.dev/posts/d3ff5a5a/"/>
    <id>https://curiositynotes.dev/posts/d3ff5a5a/</id>
    <published>2023-11-07T07:10:35.000Z</published>
    <updated>2026-05-20T14:08:16.421Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装-zsh"><a class="markdownIt-Anchor" href="#安装-zsh"></a> 安装 Zsh</h2><p><a href="https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH">官方文档</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装 Zsh</span></span><br><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install zsh -y</span><br><span class="line"><span class="comment"># 设置为默认终端</span></span><br><span class="line">chsh -s $(<span class="built_in">which</span> zsh)</span><br><span class="line"><span class="comment"># 重启终端，ssh 连接则退出重新登录</span></span><br><span class="line"><span class="built_in">exit</span></span><br><span class="line"><span class="comment"># 检查是否设置成功</span></span><br><span class="line"><span class="built_in">echo</span> <span class="variable">$SHELL</span></span><br></pre></td></tr></table></figure><h2 id="安装-ohmyzsh"><a class="markdownIt-Anchor" href="#安装-ohmyzsh"></a> 安装 OhMyZsh</h2><ul><li>下载安装<br /><a href="https://github.com/ohmyzsh/ohmyzsh/wiki#getting-started">官方文档</a></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sh -c <span class="string">&quot;<span class="subst">$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>&quot;</span></span><br><span class="line"><span class="comment"># 或下载安装脚本后再运行</span></span><br><span class="line">wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh</span><br><span class="line">sh install.sh</span><br></pre></td></tr></table></figure><ul><li><p>添加插件</p><ul><li>语法高亮 zsh-syntax-highlighting<a href="https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/INSTALL.md#with-a-plugin-manager">参考</a></li></ul>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-syntax-highlighting.git <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure><ul><li>命令提示 zsh-autosuggestions<a href="https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh">参考</a></li></ul>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-autosuggestions <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-autosuggestions</span><br></pre></td></tr></table></figure></li><li><p>开启插件<br />修改<code>.zshrc</code>的<code>plugins=()</code>开启插件，git 默认开启</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">plugins=(</span><br><span class="line">    git</span><br><span class="line">    zsh-autosuggestions</span><br><span class="line">    zsh-syntax-highlighting</span><br><span class="line">)</span><br></pre></td></tr></table></figure><ul><li>重启终端</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exec</span> zsh</span><br></pre></td></tr></table></figure><h2 id="安装-powerlevel10k-主题可选"><a class="markdownIt-Anchor" href="#安装-powerlevel10k-主题可选"></a> 安装 powerlevel10k 主题（可选）</h2><p><a href="https://github.com/romkatv/powerlevel10k#getting-started">官方文档</a></p><ul><li>终端字体设置<ul><li>使用 Ubuntu 桌面版按照主题主页方法安装字体即可</li><li>使用 ssh 远程连接 Ubuntu 服务器需要在本地终端软件中设置字体</li></ul></li><li>安装主题</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> --depth=1 https://github.com/romkatv/powerlevel10k.git <span class="variable">$&#123;ZSH_CUSTOM:-<span class="variable">$HOME</span>/.oh-my-zsh/custom&#125;</span>/themes/powerlevel10k</span><br><span class="line"><span class="comment"># 或者使用gitee镜像</span></span><br><span class="line">git <span class="built_in">clone</span> --depth=1 https://gitee.com/romkatv/powerlevel10k.git <span class="variable">$&#123;ZSH_CUSTOM:-<span class="variable">$HOME</span>/.oh-my-zsh/custom&#125;</span>/themes/powerlevel10k</span><br></pre></td></tr></table></figure><ul><li>修改<code>.zshrc</code>的<code>ZSH_THEME</code>开启主题</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ZSH_THEME=<span class="string">&quot;powerlevel10k/powerlevel10k&quot;</span></span><br></pre></td></tr></table></figure><ul><li>重启 Zsh 进入主题配置</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exec</span> zsh</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;安装-zsh&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#安装-zsh&quot;&gt;&lt;/a&gt; 安装 Zsh&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-</summary>
      
    
    
    
    <category term="环境配置爱好者" scheme="https://curiositynotes.dev/categories/%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE%E7%88%B1%E5%A5%BD%E8%80%85/"/>
    
    
    <category term="Zsh" scheme="https://curiositynotes.dev/tags/Zsh/"/>
    
    <category term="Bash" scheme="https://curiositynotes.dev/tags/Bash/"/>
    
    <category term="Linux" scheme="https://curiositynotes.dev/tags/Linux/"/>
    
  </entry>
  
</feed>
