<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://lug.ustc.edu.cn/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lug.ustc.edu.cn/" rel="alternate" type="text/html" /><updated>2025-10-18T00:54:02+08:00</updated><id>https://lug.ustc.edu.cn/feed.xml</id><title type="html">LUG @ USTC</title><subtitle>中国科学技术大学 Linux 用户协会</subtitle><author><name>USTCLUG</name></author><entry><title type="html">2025 软件自由日中国科大站</title><link href="https://lug.ustc.edu.cn/news/2025/09/SFD/" rel="alternate" type="text/html" title="2025 软件自由日中国科大站" /><published>2025-09-21T20:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2025/09/SFD</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2025/09/SFD/"><![CDATA[<p>2025 年 9 月 20 日，LUG 举办了软件自由日 (Software Freedom Day) 活动。活动在 14:30-17:30 于西区 3C101 举行，并同步进行了线上直播。本次活动邀请到开源项目 Slidev 维护者 _Kerman 同学，来自少年班学院的田佳嘉同学，LUG 前会长罗嘉宏同学，以及 Pixi 项目的开发者秦慕作为演讲嘉宾。</p>
      <p>14 时 30 分，校学生 Linux 用户协会会长王永福同学简洁地介绍了活动流程安排和演讲嘉宾后，软件自由日活动正式开始。</p>
      <p>首先，_Kerman 同学为大家介绍了他参与维护的开源幻灯片制作工具 Slidev。他讲解了 Slidev 的设计理念、功能特点以及使用场景，并现场演示了幻灯片的制作，为同学们制作幻灯片提供了一种更高效的新选择。最后，他鼓励同学们加入到 Slidev 这样的开源项目中来。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0823.png" alt="" /></p>
      <p>接下来，少年班学院的田佳嘉同学带来了《从一到无穷大：Lean4 是如何用程序构建数学世界的》的分享。他从 Lean4 的基本概念入手，介绍了 Lean4 在数学证明中的应用，展示了如何通过编程来构建和验证数学定理。他深刻的讲解给大家带来了许多启发，激发了同学们对形式化数学的兴趣。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0828.png" alt="" /></p>
      <p>茶歇之后，LUG 前会长罗嘉宏为我们分享了开源命令行绘图工具 GNUPlot 的应用场景与使用方式。他详细且直观地介绍了 GNUPlot 的基本功能和常用命令，并通过实际案例展示了如何使用 GNUPlot 进行化学相关数据的作图，让大家感受到了命令行工具的强大与便利。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0835.png" alt="" /></p>
      <p>随后，Pixi 项目的开发者秦慕为我们介绍了包管理器 Pixi。她从各种 Python 包管理器的优缺点与发展历史入手，介绍了 Pixi 的设计理念、功能特点以及使用场景，为同学们在开发、学习与科研中高效可靠地管理代码依赖提供了新选择。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0837.png" alt="" /></p>
      <p>在这之后，闪电演讲拉开帷幕。首先由来自中科大业余无线电协会的缪渠成同学介绍了其领导的卫星项目。该项目旨在挑战技术难点，计划在两年后发射一颗业余无线电卫星。他非常欢迎软件背景的同学参与其中。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0840.png" alt="" /></p>
      <p>闪电演讲压轴登场的是龙芯开发者关系负责人、安同开源社区创始人白铭骢。他介绍了龙芯处理器的发展历程与现状，指出了龙芯生态建设中的成就与挑战，并为同学们提供了参与龙芯生态建设的具体途径。</p>
      <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/photos/IMG_0841.png" alt="" /></p>
      <p>17 时 35 分，2025 年的软件自由日活动圆满结束。活动中大家积极交流分享，我们希望通过这样的形式，让更多的同学了解到开源社区的魅力。</p>
      <blockquote>
        <p>每年的 SFD 活动都在 9 月的第三个周六举行，历年活动在 <a href="/wiki/lug/events/sfd">这里</a> 查看，如果您有意向在下一届的 SFD 活动中分享您的经验，欢迎 <a href="/wiki/lug/contact">联系我们</a>。</p>
        <p>本次活动幻灯片、录像、照片等资料已经上传 LUG FTP: <a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/">https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/</a></p>
      </blockquote>
      ]]></content><author><name>_Kerman</name></author><category term="LUG 活动" /><summary type="html"><![CDATA[2025 年 9 月 20 日，LUG 举办了软件自由日 (Software Freedom Day) 活动。活动在 14:30-17:30 于西区 3C101 举行，并同步进行了线上直播。本次活动邀请到开源项目 Slidev 维护者 _Kerman 同学，来自少年班学院的田佳嘉同学，LUG 前会长罗嘉宏同学，以及 Pixi 项目的开发者秦慕作为演讲嘉宾。]]></summary></entry><entry><title type="html">2025 秋季学期 新生活力课堂</title><link href="https://lug.ustc.edu.cn/news/2025/09/2025-freshmen-course/" rel="alternate" type="text/html" title="2025 秋季学期 新生活力课堂" /><published>2025-09-08T18:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2025/09/2025-freshmen-course</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2025/09/2025-freshmen-course/"><![CDATA[<p>中国科学技术大学学生 Linux 用户协会于 9 月 7 日 19:00-21:00 在西区 3A111 教室举办了新生活力课堂。本次 LUG 新生活力课堂参与人数创历史新高，感谢各位同学的热情参与！</p>
    <p>新生活力课堂主要面向刚入学的新生。在活动的上半场中，王永福同学为大家带来了《实用工具介绍》，并从科大学习生活中的需求出发，介绍了科大网络资源与一些实用工具，包括 Vlab、USTC LaTeX、LUG GitLab、科大网盘等。此外，还针对性地介绍了 LaTeX 与 Markdown，助力同学们更高效地编写作业和报告；以及 GitHub 与 Google Scholar 等平台，助力同学们更高效地进行科研。最后，王永福向新生们介绍了 LUG 与一些近期活动，欢迎大家加入 LUG，一起学习和交流。</p>
    <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/photos/photo_1_2025-09-08_16-03-12.jpg" alt="" /></p>
    <p>中场休息环节，同学们进行了自由交流与提问，气氛热烈。</p>
    <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/photos/photo_6_2025-09-08_16-03-12.jpg" alt="" /></p>
    <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/photos/photo_8_2025-09-08_16-03-12.jpg" alt="" /></p>
    <p>活动的下半场是王永福同学带来的《面向高效学习/科研/创作的 AI 工具入门》，他从 AI 工具的意义出发，介绍了多种聊天机器人、AI 编程工具、图像生成工具和视频生成工具。最后，王永福重点介绍了用于 AI 工具的提示工程，包括其重要性、原则与实用技巧，帮助同学们更有效地使用 AI 工具。</p>
    <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/photos/photo_10_2025-09-08_16-03-12.jpg" alt="" /></p>
    <p>活动照片、讲稿等资料可在 <a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/">LUG FTP</a> 上查看：</p>
    <ul>
      <li><a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/photos/">现场照片</a></li>
      <li><a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/video/">视频回放</a></li>
      <li><a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E7%A8%8B2025_0907.pdf">《实用工具介绍》幻灯片</a></li>
      <li><a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.09.07_%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E5%A0%82/%E6%96%B0%E7%94%9F%E6%B4%BB%E5%8A%9B%E8%AF%BE%E7%A8%8B_AI_v2.pdf">《面向高效学习/科研/创作的 AI 工具入门》幻灯片</a></li>
    </ul>
    ]]></content><author><name>_Kerman</name></author><category term="LUG 活动" /><summary type="html"><![CDATA[中国科学技术大学学生 Linux 用户协会于 9 月 7 日 19:00-21:00 在西区 3A111 教室举办了新生活力课堂。本次 LUG 新生活力课堂参与人数创历史新高，感谢各位同学的热情参与！]]></summary></entry><entry><title type="html">2025 软件自由日 (SFD) 讲座征集</title><link href="https://lug.ustc.edu.cn/news/2025/08/SFD-Prep/" rel="alternate" type="text/html" title="2025 软件自由日 (SFD) 讲座征集" /><published>2025-08-10T11:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2025/08/SFD-Prep</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2025/08/SFD-Prep/"><![CDATA[<p><a href="https://en.wikipedia.org/wiki/Software_Freedom_Day">软件自由日</a>（Software Freedom Day，SFD）是在 Digital Freedom Foundation 的主办下，由自由/开源软件爱好者自发筹集组织非盈利性活动，旨在为自由/开源软件爱好者提供一个信息交流平台、联合各社区举办各种自由/开源软件活动。</p>
  <p>作为合肥地区最大的 Linux 用户组织，中国科大 Linux 用户协会致力于通过 SFD 普及软件自由思想，凝聚自由软件社区，将优秀的自由软件介绍给更多开源社区的成员，最大限度帮助自由软件作者实现服务于开源社群的价值。</p>
  <p>今年的软件自由日活动预计于 2025 年 9 月 20 日（九月第三个星期六）于中国科学技术大学（合肥）举行，<strong>现面向校内外所有社区成员公开征集软件自由日讲座或其他与主题有关的活动</strong>，有意愿的社区成员可尽快联系我们。</p>
  <p><a href="/wiki/lug/contact">联系方式</a></p>
  <p><a href="/wiki/lug/events/sfd">往届活动和详细介绍</a></p>
  <ul>
    <li><a href="/news/2024/09/SFD/">2024 SFD</a></li>
    <li><a href="/news/2023/09/SFD/">2023 SFD</a></li>
    <li><a href="/news/2022/09/SFD/">2022 SFD</a></li>
    <li><a href="/news/2021/09/SFD/">2021 SFD</a></li>
  </ul>
  ]]></content><author><name>_Kerman</name></author><category term="LUG 活动" /><summary type="html"><![CDATA[软件自由日（Software Freedom Day，SFD）是在 Digital Freedom Foundation 的主办下，由自由/开源软件爱好者自发筹集组织非盈利性活动，旨在为自由/开源软件爱好者提供一个信息交流平台、联合各社区举办各种自由/开源软件活动。]]></summary></entry><entry><title type="html">提升命令行使用体验──tmux 终端复用</title><link href="https://lug.ustc.edu.cn/planet/2025/07/how-to-use-tmux/" rel="alternate" type="text/html" title="提升命令行使用体验──tmux 终端复用" /><published>2025-07-06T00:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/planet/2025/07/how-to-use-tmux</id><content type="html" xml:base="https://lug.ustc.edu.cn/planet/2025/07/how-to-use-tmux/"><![CDATA[<p>本文会介绍 tmux 的基础使用方法，以及长期使用下来的一些常用配置。</p>
  <h2 id="背景--原理">背景 &amp; 原理</h2>
  <p>在使用命令行的过程中，我们经常遇到一个需求，那就是让程序在退出当前终端后保持运行。在以往，我们可能会使用 <code class="language-plaintext highlighter-rouge">nohup</code> 加让程序后台运行的方式，比如：</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>➜  ~ <span class="nb">nohup </span>ping 127.0.0.1 &amp;
<span class="o">[</span>1] 89083
<span class="nb">nohup</span>: ignoring input and appending output to <span class="s1">'nohup.out'</span>
➜  ~ ps <span class="nt">-elf</span> |grep ping
0 S yfy        89083   88672  0  85   5 -  3352 -      11:39 pts/0    00:00:00 ping 127.0.0.1
</code></pre>
    </div>
  </div>
  <p>这样在断开终端连接后，重新登陆到服务器，可以发现 ping 进程仍然在运行。nohup 默认将程序输出追加到 <code class="language-plaintext highlighter-rouge">nohup.out</code> 文件中，可以通过 tail 检验这一点。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>➜  ~ <span class="nb">tail</span> <span class="nt">-f</span> nohup.out
64 bytes from 127.0.0.1: <span class="nv">icmp_seq</span><span class="o">=</span>7 <span class="nv">ttl</span><span class="o">=</span>64 <span class="nb">time</span><span class="o">=</span>0.024 ms
64 bytes from 127.0.0.1: <span class="nv">icmp_seq</span><span class="o">=</span>8 <span class="nv">ttl</span><span class="o">=</span>64 <span class="nb">time</span><span class="o">=</span>0.027 ms
64 bytes from 127.0.0.1: <span class="nv">icmp_seq</span><span class="o">=</span>9 <span class="nv">ttl</span><span class="o">=</span>64 <span class="nb">time</span><span class="o">=</span>0.023 ms
64 bytes from 127.0.0.1: <span class="nv">icmp_seq</span><span class="o">=</span>10 <span class="nv">ttl</span><span class="o">=</span>64 <span class="nb">time</span><span class="o">=</span>0.035 ms
➜  ~ ps <span class="nt">-elf</span> |head <span class="nt">-n1</span>
F S UID          PID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
➜  ~ ps <span class="nt">-elf</span> |grep ping
0 S yfy        89083       1  0  85   5 -  3352 -      11:39 ?        00:00:00 ping 127.0.0.1
</code></pre>
    </div>
  </div>
  <p>正常情况下，当终端退出时（如关闭终端窗口，或者 ssh 网络中断），shell 进程会给所有子进程发送一个 SIGHUP 信号，从而导致进程结束。而 nohup 可以让进程忽略掉 SIGHUP 信号，从而在后台保持运行。</p>
  <blockquote>
    <p>Tip: ps 输出还可以观察到 ping 进程父进程变为了 1，这是因为原本的父进程 shell 已经退出，Linux 内核会将孤儿进程交由 init/systemd 进程托管。</p>
  </blockquote>
  <p>nohup 在简单场景比较有用，但是不适用更复杂的场景。比如用户无法再对后台进程进行输入操作，并且同时维护多个进程也很不方便。不过我们其实还有更优雅、更强大的方案，那就是 tmux。</p>
  <p>tmux 是一个终端复用器（<strong>T</strong>erminal <strong>MU</strong>ltiple<strong>X</strong>er）。简单来说，tmux 的功能可以概括成一句话：在一个终端里创建多个窗口，并且将窗口分割成多个窗格，每个窗格都运行独立的 shell 进程。这样你就可以让多个应用程序同时运行，而无需打开多个终端模拟器窗口（terminal emulator，如 Xshell、PuTTY、Windows Terminal 等）。下图展示了使用 tmux 创建了一个窗口，并将窗口上下分屏成两个窗格，每个窗格都运行一个 zsh。</p>
  <p><img src="/static/planet/20250705151056.png" alt="tmux example" /></p>
  <p>更重要的是，tmux 将终端和会话（session）进行了分离。我们把创建的窗口和窗格（和其中创建的所有 shell 进程及子进程）看成一个会话，用户可以随时退出（detach）当前会话，会话会在服务器后台保持运行。在之后，用户可以重新连接上（attach）这些会话，从而继续之前的工作。</p>
  <p>原理上，tmux 采用了客户端和服务器分离的架构（C/S 架构）。用户看到和交互的是 tmux client，tmux client 中创建的窗口（和 shell 进程）连接在 tmux server 进程上。因此，当用户退出 tmux client 后，会话内容可以在后台运行。
    通过 pstree 命令，可以看到上图中运行的两个 zsh 的父进程是 <code class="language-plaintext highlighter-rouge">tmux: server</code>。而和用户交互的其实是 <code class="language-plaintext highlighter-rouge">tmux: client</code>，其下面并无其他子进程。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>➜  ~ pstree <span class="nt">-u</span> yfy|less
sshd---zsh---tmux: client
tmux: server-+-zsh-+-less
             |     <span class="sb">`</span><span class="nt">-pstree</span>
             <span class="sb">`</span><span class="nt">-zsh---ping</span>
</code></pre>
    </div>
  </div>
  <p>在简单了解了下 tmux 的原理后，马上开始 tmux 的上手体验吧。tmux 基础使用非常简单，花 10 分钟就可以完全了解。</p>
  <h2 id="tmux-quick-start">tmux quick start</h2>
  <p>大部分 Linux 发行版都提供 <code class="language-plaintext highlighter-rouge">tmux</code> 包，可以通过包管理器安装。其他安装方式（如编译安装）也可以参考官方文档：<a href="https://github.com/tmux/tmux/wiki/Installing">Installing · tmux/tmux Wiki</a>。</p>
  <p>在安装 tmux 后的第一步，我们首先创建一个 tmux 会话。运行 <code class="language-plaintext highlighter-rouge">tmux</code> 命令后，会进入 tmux 并创建第一个窗口，在这里你可以像往常一样执行命令。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>tmux
</code></pre>
    </div>
  </div>
  <p>接着我们介绍最重要的退出话会话和重连会话的方法。按下 <code class="language-plaintext highlighter-rouge">Ctrl-B</code> 后，再按 <code class="language-plaintext highlighter-rouge">d</code> 可以退出（detach）当前会话。这样会回到输入 tmux 前的 shell。</p>
  <p>通过 <code class="language-plaintext highlighter-rouge">tmux list-sessions</code> 或者 <code class="language-plaintext highlighter-rouge">tmux ls</code> 可以查看当前所有的会话信息。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>➜  ~ tmux <span class="nb">ls
</span>0: 1 windows <span class="o">(</span>created Thu Jul  3 14:06:05 2025<span class="o">)</span>
</code></pre>
    </div>
  </div>
  <p>通过 <code class="language-plaintext highlighter-rouge">tmux attach</code> 可以连接回第一个会话。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>tmux a  <span class="c"># attach</span>
</code></pre>
    </div>
  </div>
  <p>接下来介绍 tmux 最关键的多窗口管理功能，有了它就可以大大提升多任务操作的效率。
    tmux 有 session（会话）, window（窗口）, pane（窗格）三个粒度。session 是 tmux 最大的一个粒度，session 下可以创建多个 window，window 可以分割成多个 pane。这里重点介绍窗口和窗格相关的快捷键。</p>
  <h3 id="窗口操作">窗口操作</h3>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># 创建</span>
Ctrl-B c <span class="c"># 创建新的窗口</span>
Ctrl-B &amp; <span class="c"># 删除当前窗口</span>

<span class="c"># 切換</span>
Ctrl-B Tab        <span class="c"># 切换到刚刚的窗口</span>
Ctrl-B p          <span class="c"># 切换上一个</span>
Ctrl-B n          <span class="c"># 切换下一个</span>
Ctrl-B 数字编号    <span class="c"># 切换到指定一个窗口</span>

<span class="c"># 修改窗口名字</span>
Ctrl-B ,          <span class="c"># 修改当前窗口名字</span>
</code></pre>
    </div>
  </div>
  <h3 id="窗格操作">窗格操作</h3>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># 创建</span>
Ctrl-B <span class="se">\"</span>     <span class="c"># 上下切分</span>
Ctrl-B %      <span class="c"># 左右切分</span>
Ctrl-B x      <span class="c"># 删除</span>

<span class="c"># 切换</span>
Ctrl-B 方向键      <span class="c"># 方向键上下左右</span>
Ctrl-B <span class="o">[</span>hjkl]     <span class="c"># 使用 vi 风格的 hjkl 键切换，分别对应左上下右</span>

Ctrl-B z      <span class="c">#  切换全屏</span>
</code></pre>
    </div>
  </div>
  <p>相信你操作了这些快捷键后，已经可以感觉到 tmux 的方便和强大了。tmux 也是一个像 vim 那样灵活性很高的软件，很多时候一个你不知道的快捷键就能极大体验。因此需要去更多尝试。<code class="language-plaintext highlighter-rouge">tmux list-keys</code> 可以查看 tmux 的所有快捷键绑定。</p>
  <p>本文只介绍了 tmux 的快捷键使用方法，tmux 的软件设计非常简洁高效，以上所有快捷键都有对应它的一个子命令，比如垂直划分窗格对应 <code class="language-plaintext highlighter-rouge">tmux split-window -v</code> 命令。如果记不住快捷键，可以直接使用 tmux 命令。tmux man 手册的 <code class="language-plaintext highlighter-rouge">COMMAND</code> 章节提供了所有命令的详细解释。另外本文没有介绍 tmux 的 vi copy 模式，使用该模式可以方便查找、复制程序输出内容，该内容比较进阶，感兴趣的读者可以自行了解。</p>
  <h2 id="常用配置分享">常用配置分享</h2>
  <p>tmux 像 vim 一样支持高度定制化，可以通过修改 <code class="language-plaintext highlighter-rouge">~/.tmux.conf</code> 配置各种快捷键，网络上也有 <a href="https://github.com/gpakosz/.tmux">oh-my-tmux</a> 这样的项目来帮你做一些配置。接下来分享一些我长期使用下来，感觉很有必要的配置，完整配置见我的 <a href="https://github.com/TheRainstorm/my-vim-config/blob/master/tmux/.tmux.conf">github</a> 仓库。</p>
  <blockquote>
    <p>Bonus：在 <code class="language-plaintext highlighter-rouge">~/.bashrc</code> 或 <code class="language-plaintext highlighter-rouge">~/.zshrc</code> 中设置 <code class="language-plaintext highlighter-rouge">alias t="tmux"</code>，tmux attach 效率立刻提升 300%！</p>
  </blockquote>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>t      <span class="c"># 创建 sessin</span>
t a    <span class="c"># attach 到第一个 session</span>
</code></pre>
    </div>
  </div>
  <h3 id="修改快捷键前缀">修改快捷键前缀</h3>
  <p><code class="language-plaintext highlighter-rouge">Ctrl-B</code> 按起来距离比较远，很不方便。可以修改成 <code class="language-plaintext highlighter-rouge">Ctrl-X</code>。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># prefix</span>
<span class="nb">set</span> <span class="nt">-g</span> prefix C-x
unbind-key C-b        <span class="c"># disable default prefix</span>
<span class="nb">bind </span>C-x send-prefix
</code></pre>
    </div>
  </div>
  <h3 id="划分窗格快捷键">划分窗格快捷键</h3>
  <p>窗格左右划分和上下划分快捷键比较难记。修改为下划线 <code class="language-plaintext highlighter-rouge">_</code> 是上下分屏，<code class="language-plaintext highlighter-rouge">-</code> 是左右分屏。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># @ pane</span>
<span class="c"># split current window horizontally</span>
<span class="nb">bind </span>_ split-window <span class="nt">-v</span> <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>

<span class="c"># split current window vertically</span>
<span class="nb">bind</span> - split-window <span class="nt">-h</span> <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>
</code></pre>
    </div>
  </div>
  <h3 id="鼠标操作">鼠标操作</h3>
  <p>tmux 默认没有启用鼠标，导致无法使用鼠标滚动历史记录。启用鼠标后，还可以直接点击切换不同窗格，以及拖动选择文字复制。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>set-option <span class="nt">-g</span> mouse on <span class="c"># open mouse scroll</span>
</code></pre>
    </div>
  </div>
  <h3 id="打开窗口时默认路径">打开窗口时默认路径</h3>
  <p>tmux 打开新窗口时，shell 的默认路径是启动 tmux 客户端时的路径。可以通过以下配置实现：</p>
  <ul>
    <li><code class="language-plaintext highlighter-rouge">Ctrl-X Alt-C</code> ：更改默认路径为当前路径</li>
    <li>创建 panel 时，使用当前路径（<code class="language-plaintext highlighter-rouge">-c</code> 参数）</li>
  </ul>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">bind </span>M-c attach-session <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span> <span class="c"># Alt-C, to change current path</span>

<span class="c"># @ pane</span>
<span class="c"># split current window horizontally</span>
<span class="nb">bind </span>_ split-window <span class="nt">-v</span> <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>

<span class="c"># split current window vertically</span>
<span class="nb">bind</span> - split-window <span class="nt">-h</span> <span class="nt">-c</span> <span class="s2">"#{pane_current_path}"</span>
</code></pre>
    </div>
  </div>
  <h3 id="移动窗口顺序">移动窗口顺序</h3>
  <p><code class="language-plaintext highlighter-rouge">Ctrl-Shift</code> 加方向键左右，可以调整 window 的顺序。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="nb">bind</span> <span class="nt">-n</span> C-S-Left swap-window <span class="nt">-t</span> <span class="nt">-1</span><span class="se">\;</span> <span class="k">select</span><span class="nt">-window</span> <span class="nt">-t</span> <span class="nt">-1</span>
<span class="nb">bind</span> <span class="nt">-n</span> C-S-Right swap-window <span class="nt">-t</span> +1<span class="se">\;</span> <span class="k">select</span><span class="nt">-window</span> <span class="nt">-t</span> +1
</code></pre>
    </div>
  </div>
  <h3 id="session-操作">session 操作</h3>
  <p>虽然 window 和 pane 已经足够进行多任务管理了。但是 tmux 提供的多 session 操作也有其用处。适合管理若干完全不相关的任务。</p>
  <p>修改快捷键，使其和 window 快捷键类似：<code class="language-plaintext highlighter-rouge">Ctrl-C</code> 创建新 session，<code class="language-plaintext highlighter-rouge">N</code>/<code class="language-plaintext highlighter-rouge">P</code> （大写）切换上一个和下一个 session。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># create session</span>
<span class="nb">bind </span>C-c new-session

<span class="c"># session navigation</span>
<span class="c"># (/)   #move to prev/next session</span>
<span class="nb">bind</span> <span class="nt">-r</span> BTab switch-client <span class="nt">-l</span>  <span class="c"># move to last session</span>
<span class="nb">bind</span> <span class="nt">-r</span> N switch-client <span class="nt">-n</span>
<span class="nb">bind</span> <span class="nt">-r</span> P switch-client <span class="nt">-p</span>
</code></pre>
    </div>
  </div>
  <h3 id="复制文字后不自动跳到结尾">复制文字后不自动跳到结尾</h3>
  <p>tmux 鼠标选中文字会自动复制，但是会自动跳到结尾。有时在查看一些很长的输出历史时，并不想回到结尾，可以通过以下配置禁用跳转：</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># copy select text, and don't jump to end</span>
<span class="c"># https://stackoverflow.com/questions/32374907/tmux-mouse-copy-mode-jumps-to-bottom</span>
<span class="nb">bind</span> <span class="nt">-T</span> copy-mode-vi MouseDragEnd1Pane send-keys <span class="nt">-X</span> copy-selection
</code></pre>
    </div>
  </div>
  <blockquote>
    <p>鼠标选中文字后会进入 tmux 的 copy 模式，无法输入命令，此时可以按 <code class="language-plaintext highlighter-rouge">q</code> 退出。</p>
  </blockquote>
  <h3 id="tmux-in-tmux">tmux in tmux</h3>
  <p>有时候想要在 tmux 内再连接另一台服务器，然后也使用 tmux。正常情况下 tmux 快捷键只会传递给外层的 tmux 因此无法工作，但是可以使用以下配置实现按 F12 在内外 tmux 间切换。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># 按 F12 切换到内嵌 tmux，在 macos 里需要系统设置中取消 F12 占用</span>
<span class="c"># 1. prefix 为 None，不再拦截快捷键</span>
<span class="c"># 2. key-table 为 off，下面再绑定 off 下的 F12，使之能退出内嵌模式</span>
<span class="c"># 3. 改变 statusbar 颜色，以便知道已进入内嵌模式</span>
<span class="c"># 4. 如果处于特殊模式，退出</span>
unbind <span class="nt">-T</span> root F12
<span class="nb">bind</span> <span class="nt">-T</span> root F12 <span class="se">\</span>
  <span class="nb">set </span>prefix None <span class="se">\;\</span>
  <span class="nb">set </span>key-table off <span class="se">\;\</span>
  <span class="nb">set </span>status-style <span class="nb">bg</span><span class="o">=</span>colour235 <span class="se">\;\</span>
  <span class="k">if</span> <span class="nt">-F</span> <span class="s1">'#{pane_in_mode}'</span> <span class="s1">'send-keys -X cancel'</span> <span class="se">\;\</span>
  refresh-client <span class="nt">-S</span>

<span class="c"># 在 off 表里绑定 F12，恢复之前的设置，以退出该模式</span>
<span class="nb">bind</span> <span class="nt">-T</span> off F12 <span class="se">\</span>
  <span class="nb">set</span> <span class="nt">-u</span> prefix <span class="se">\;\</span>
  <span class="nb">set</span> <span class="nt">-u</span> key-table <span class="se">\;\</span>
  <span class="nb">set</span> <span class="nt">-u</span> status-style <span class="se">\;\</span>
  refresh-client <span class="nt">-S</span>
</code></pre>
    </div>
  </div>
  <p>另一个小技巧是：连续按两次 <code class="language-plaintext highlighter-rouge">Ctrl-B</code> 时快捷键会传递给内部 tmux。</p>
  <h2 id="参考资料">参考资料</h2>
  <ul>
    <li><a href="https://www.ruanyifeng.com/blog/2019/10/tmux.html">Tmux 使用教程 - 阮一峰的网络日志</a></li>
    <li><a href="https://hamvocke.com/blog/a-quick-and-easy-guide-to-tmux/">Ham Vocke — A Quick and Easy Guide to tmux - Ham Vocke</a></li>
    <li><a href="https://fatfatson.github.io/2019/08/11/tmux%E5%86%85%E8%81%94%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95/">tmux 内联使用方法</a></li>
  </ul>
  ]]></content><author><name>TheRainstorm</name></author><category term="Tech Tutorial" /><category term="tmux" /><category term="netcat" /><summary type="html"><![CDATA[本文会介绍 tmux 的基础使用方法，以及长期使用下来的一些常用配置。]]></summary></entry><entry><title type="html">Linux Install Party 2025</title><link href="https://lug.ustc.edu.cn/news/2025/03/Linux101-LIP-2025/" rel="alternate" type="text/html" title="Linux Install Party 2025" /><published>2025-03-16T15:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2025/03/Linux101-LIP-2025</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2025/03/Linux101-LIP-2025/"><![CDATA[<p>2025 年 3 月 16 日下午，中国科学技术大学学生 Linux 用户协会在西区 3C101 教室成功举办 “Linux 101” 系列课程首讲——Linux Install Party。这是 LUG 第八年举办 Linux 101 系列课程、第九年举办 Linux Install Party。本次活动通过理论讲解与实践操作相结合的方式，帮助参与者初步了解 Linux 操作系统并完成安装实践。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.03.16_Linux_101_%E7%AC%AC%E4%B8%80%E8%AF%BE_Linux_Install_Party/lip-post.jpg" alt="" /></p>
  <p>在活动中，LUG 会长罗嘉宏同学系统讲解了 Linux 操作系统的基础知识，涵盖其开源特性、技术优势、应用场景及主流发行版本。通过详实的案例分析与技术对比，帮助在场同学对 Linux 系统形成了基本认知框架。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.03.16_Linux_101_%E7%AC%AC%E4%B8%80%E8%AF%BE_Linux_Install_Party/photo_2025-03-16%2023.46.11.jpeg" alt="" /></p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.03.16_Linux_101_%E7%AC%AC%E4%B8%80%E8%AF%BE_Linux_Install_Party/IMG_7520.jpeg" alt="" /></p>
  <p>在随后的实践环节中，社团成员分组指导参与者进行系统安装。无论是通过校内 Vlab 平台远程体验，还是在虚拟机环境进行安装，亦或是实体设备的双系统部署，工作人员均针对不同需求提供个性化指导。现场同学通过互助协作，亲身体验 Linux 系统的安装流程。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2025.03.16_Linux_101_%E7%AC%AC%E4%B8%80%E8%AF%BE_Linux_Install_Party/IMG_7524.jpeg" alt="" /></p>
  <p>本次活动通过理论普及与动手实践的有效结合，帮助参与者建立起对 Linux 系统的初步认知。相信在未来的活动中，会有更多精彩的内容等待着大家。</p>
  <p>相关课程资料已同步上传至 FTP：<a href="https://ftp.lug.ustc.edu.cn/活动/2025.03.16_Linux_101_第一课_Linux_Install_Party/101-2025-1.pdf">Linux 101 第一课 &amp; Linux Install Party</a></p>
  ]]></content><author><name>_Kerman</name></author><category term="LUG 活动" /><category term="linux-install-party" /><category term="Linux 101" /><category term="LUG 活动" /><summary type="html"><![CDATA[Linux Install Party 2025]]></summary></entry><entry><title type="html">Rootless Docker in Docker 在 Hackergame 中的实践</title><link href="https://lug.ustc.edu.cn/planet/2025/02/hackergame-rootless-docker/" rel="alternate" type="text/html" title="Rootless Docker in Docker 在 Hackergame 中的实践" /><published>2025-02-08T00:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/planet/2025/02/hackergame-rootless-docker</id><content type="html" xml:base="https://lug.ustc.edu.cn/planet/2025/02/hackergame-rootless-docker/"><![CDATA[<p>本文介绍了 2024 年 USTC Hackergame 中 Rootless Docker in Docker 在 Web 类题目容器隔离中的实践。</p>
  <h2 id="背景">背景</h2>
  <p>USTC Hackergame 长期以来使用 Docker 及 Docker Compose 来部署和管理各种题目。对于 <code class="language-plaintext highlighter-rouge">nc</code> 类题目，我们使用一个<a href="https://github.com/USTC-Hackergame/hackergame-challenge-docker">简单的 Python 管理程序</a>来为每个已验证的传入连接创建一个单独的题目运行环境容器，以保证选手之间的隔离。为了动态创建容器，我们将 <code class="language-plaintext highlighter-rouge">/var/run/docker.sock</code> 暴露给这个 Python 程序，以便它可以调用 Docker API 来创建容器。由于这个 Python 程序足够简单，我们认为这样做是安全的。而对于 Web 类题目，我们要求出题人在题目内部做好隔离，然而这样带来了额外的心智负担，而且容易出错，可能导致非预期解或出现能够干扰其他选手的情况。</p>
  <p>在 2024 年 Hackergame 中，我们决定实现 Web 类题目的容器隔离方案，北京大学 GeekGame 基于我们的 nc 容器管理方案实现了一个<a href="https://github.com/PKU-GeekGame/web-docker-manager">简单的 Web 类题目容器管理方案</a>，该方案同样通过透传 Docker Socket 实现对 Web 题目的容器管理。然而，Web 题目的反向代理比 nc 复杂得多，这使得我们对该方案的安全性存在较大的担忧，一旦该管理程序被攻破，攻击者可以直接控制整个服务器。因此，我们需要一个更安全的方案，使得即使 Web 题目的容器管理程序被攻破，攻击者也无法轻易控制整个服务器，这就需要在隔离的、低权限的环境中运行 Docker Daemon。</p>
  <h2 id="rootless-docker-in-docker">Rootless Docker-in-Docker</h2>
  <p>Rootless Docker 是在低权限环境中运行 Docker 的一种方案。Docker 官方提供了一种 <a href="https://docs.docker.com/engine/security/rootless/#rootless-docker-in-docker">Rootless Docker in Docker</a> 的方案，只需要一行命令即可启动：</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>docker run <span class="nt">-d</span> <span class="nt">--name</span> dind-rootless <span class="nt">--privileged</span> docker:25.0-dind-rootless
</code></pre>
    </div>
  </div>
  <p>该方案在容器中以非 <code class="language-plaintext highlighter-rouge">root</code> 用户（UID 1000）创建用户命名空间，并分离其他命名空间和运行 Docker Daemon。然而，该方案存在一些问题：</p>
  <ol>
    <li>该方案需要 <code class="language-plaintext highlighter-rouge">--privileged</code> 选项，以禁用 seccomp、AppArmor 和 mount masks，但这意味着容器将获得更高的权限，可能导致安全问题。</li>
    <li>该方案无法使用 cgroup 来限制容器资源使用，因为 Rootless Docker 需要 systemd 将 cgroup 路径委托给 Docker Daemon 才能执行资源限制。虽然可以用 <code class="language-plaintext highlighter-rouge">rlimit</code> 等方案来限制资源，但其工作在进程粒度而非容器粒度，而且可以被轻松禁用。由于 Hackergame 需要强制执行容器资源限制，该问题是致命的。</li>
  </ol>
  <h2 id="基于-systemd-user-instance-的-rootless-docker-in-docker">基于 Systemd User Instance 的 Rootless Docker-in-Docker</h2>
  <p>为了解决上述问题，我们采用了一个基于 Systemd User Instance 的 Rootless Docker-in-Docker 方案。该方案在 Systemd User Instance 中运行 Rootless Docker Daemon，以实现资源限制和更好的安全性。</p>
  <h3 id="systemd-in-docker">Systemd in Docker</h3>
  <p>为了实现该方案，首先需要在容器中运行 <code class="language-plaintext highlighter-rouge">systemd</code>。systemd 的网站上列出了<a href="https://systemd.io/CONTAINER_INTERFACE/">在容器中运行 systemd 的要求</a>，对于 Docker 来说，主要有以下几点：</p>
  <ol>
    <li>保留 <code class="language-plaintext highlighter-rouge">CAP_SYS_ADMIN</code> 特权。</li>
    <li>启用私有 <code class="language-plaintext highlighter-rouge">cgroup</code> 命名空间，并将 <code class="language-plaintext highlighter-rouge">/sys/fs/cgroup</code> 挂载为可写。</li>
    <li>将 <code class="language-plaintext highlighter-rouge">/tmp</code>, <code class="language-plaintext highlighter-rouge">/run</code>, <code class="language-plaintext highlighter-rouge">/run/lock</code>, <code class="language-plaintext highlighter-rouge">/var/lib/journal</code> 挂载为 <code class="language-plaintext highlighter-rouge">tmpfs</code>。</li>
    <li>将 <code class="language-plaintext highlighter-rouge">stop_signal</code> 设置为 <code class="language-plaintext highlighter-rouge">SIGRTMIN+3</code>，以便 systemd 可以正确关闭。</li>
    <li>禁用 AppArmor 或 SELinux。</li>
  </ol>
  <p>我们在 <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> 中添加了以下内容来实现这些要求：</p>
  <div class="language-yaml highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="na">cap_add</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">SYS_ADMIN</span>
  <span class="pi">-</span> <span class="s">NET_ADMIN</span>
<span class="na">cgroup</span><span class="pi">:</span> <span class="s">private</span>
<span class="na">devices</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">/dev/net/tun:/dev/net/tun</span>
<span class="na">tmpfs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">/tmp</span>
  <span class="pi">-</span> <span class="s">/run</span>
  <span class="pi">-</span> <span class="s">/run/lock</span>
  <span class="pi">-</span> <span class="s">/var/lib/journal</span>
<span class="na">stop_signal</span><span class="pi">:</span> <span class="s">SIGRTMIN+3</span>
<span class="na">tty</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">security_opt</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">seccomp=seccomp.json</span>
  <span class="pi">-</span> <span class="s">apparmor=unconfined</span>
  <span class="pi">-</span> <span class="s">systempaths=unconfined</span>
</code></pre>
    </div>
  </div>
  <p>其中 <code class="language-plaintext highlighter-rouge">seccomp.json</code> 可以从<a href="https://github.com/USTC-Hackergame/web-docker-manager/raw/refs/heads/main/rootless/seccomp.json">这里</a>获取，该文件在 Docker <a href="https://github.com/moby/moby/raw/refs/heads/master/profiles/seccomp/default.json">默认 seccomp 配置</a>的基础上允许了 <code class="language-plaintext highlighter-rouge">keyctl</code> 和 <code class="language-plaintext highlighter-rouge">pivot_root</code> 系统调用，这些系统调用是 Docker 所需要的。此外，该配置还一并禁用了 mount masks (<code class="language-plaintext highlighter-rouge">systempaths=unconfined</code>)，因为 Docker 启动容器时需要重新挂载 <code class="language-plaintext highlighter-rouge">/sys</code>。此外，我们还保留了 <code class="language-plaintext highlighter-rouge">NET_ADMIN</code> 特权，因为一些 systemd 的组件需要该特权。</p>
  <p>需要注意的是，<code class="language-plaintext highlighter-rouge">cgroup: private</code> 并不会将 <code class="language-plaintext highlighter-rouge">/sys/fs/cgroup</code> 挂载为可写，因此我们需要在容器启动时手动处理，可以通过一个自定义的 <code class="language-plaintext highlighter-rouge">entrypoint.sh</code> 来实现：</p>
  <div class="language-bash highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nb">set</span> <span class="nt">-euo</span> pipefail

mount <span class="nt">--make-rshared</span> /

<span class="c"># Remount cgroup</span>
umount /sys/fs/cgroup
mount <span class="nt">-t</span> cgroup2 <span class="nt">-o</span> rw,relatime,nsdelegate cgroup2 /sys/fs/cgroup

<span class="nb">exec</span> /lib/systemd/systemd
</code></pre>
    </div>
  </div>
  <h3 id="rootless-docker-daemon-in-systemd-user-instance">Rootless Docker Daemon in Systemd User Instance</h3>
  <p>在容器中以非 <code class="language-plaintext highlighter-rouge">root</code> 身份运行 Docker Daemon 非常简单，只需要在容器中安装带 Rooeless 支持的 Docker：</p>
  <div class="language-dockerfile highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="k">RUN </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> ca-certificates curl <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">install</span> <span class="nt">-m</span> 0755 <span class="nt">-d</span> /etc/apt/keyrings <span class="o">&amp;&amp;</span> <span class="se">\
</span>    curl <span class="nt">-fsSL</span> https://download.docker.com/linux/debian/gpg <span class="nt">-o</span> /etc/apt/keyrings/docker.asc <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chmod </span>a+r /etc/apt/keyrings/docker.asc <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">echo</span> <span class="s2">"deb [arch=</span><span class="si">$(</span>dpkg <span class="nt">--print-architecture</span><span class="si">)</span><span class="s2"> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable"</span> <span class="o">&gt;</span> /etc/apt/sources.list.d/docker.list <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin uidmap <span class="o">&amp;&amp;</span> <span class="se">\
</span>    systemctl disable docker.service docker.socket containerd.service
</code></pre>
    </div>
  </div>
  <p>并创建一个用户，假设名为 <code class="language-plaintext highlighter-rouge">rootless</code>，然后使用 <code class="language-plaintext highlighter-rouge">machinectl shell rootless@</code> 切换到该用户，并运行 <code class="language-plaintext highlighter-rouge">dockerd-rootless-setuptool.sh install</code> 即可安装 Rootless Docker Daemon，安装程序会自动创建 <code class="language-plaintext highlighter-rouge">~/.config/systemd/user/docker.service</code> 并将其安装到 <code class="language-plaintext highlighter-rouge">default.target</code>。设置环境变量 <code class="language-plaintext highlighter-rouge">DOCKER_HOST=unix:///run/user/$UID/docker.sock</code> 即可通过 Docker CLI 工具访问该 Rootless Docker Daemon。</p>
  <h3 id="wrapping-up">Wrapping Up</h3>
  <p>将 Rootless Docker Daemon 安装脚本生成的 <code class="language-plaintext highlighter-rouge">~/.config/systemd/user/docker.service</code> 文件复制出来，并创建 <code class="language-plaintext highlighter-rouge">~/.config/systemd/user/default.target.wants</code> 中的相对路径符号链接，并在 Dockerfile 中添加创建用户和将这些文件到用户目录中的内容，最后通过在 <code class="language-plaintext highlighter-rouge">/var/lib/systemd/linger</code> 目录下创建名为用户名的空文件来使对应的 Systemd User Instance 自动启动，即可实现自动启动 Rootless Docker Daemon。</p>
  <p>该方案最终版本代码位于 <a href="https://github.com/USTC-Hackergame/web-docker-manager">USTC-Hackergame/web-docker-manager</a> 仓库的 <code class="language-plaintext highlighter-rouge">rootless</code> 目录，其中添加了一些用于持久化数据和暴露一些目录到主机的内容。</p>
  <h3 id="安全性分析">安全性分析</h3>
  <p>Rootless Docker 通过 <a href="https://github.com/rootless-containers/rootlesskit">RootlessKit</a> 分离用户命名空间，来实现非特权用户的特权操作以创建容器，因此该方案的容器（命名空间）层次结构如下：</p>
  <div class="language-plaintext highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>Host (Full Privilege)
└── systemd in Docker (UID 0, CAP_SYS_ADMIN, CAP_NET_ADMIN)
        └── RootlessKit (UID 0 in inner user namespace, with ALL capabilities; UID 1000 in outer user namespace, with no capabilities)
            └── Manager Container
            └── Web Challenge Container 1
            └── Web Challenge Container 2
</code></pre>
    </div>
  </div>
  <p>若管理程序被攻破，则攻击者可以取得 RootlessKit 所创建的用户命名空间中的特权，但由于 RootlessKit 在外层用户命名空间是非特权用户，且运行在容器中，因此攻击者无法直接控制整个服务器，也无法直接访问 Host 的文件系统其他部分。</p>
  <h2 id="总结">总结</h2>
  <p>通过基于 Systemd User Instance 的 Rootless Docker in Docker 方案，我们成功地实现了在隔离的、低权限的环境中运行 Docker Daemon，以实现 Web 类题目的容器隔离。该方案强制执行了容器的资源限制，并通过隔离 Docker Daemon 提高了安全性，使得即使 Web 题目的容器管理程序被攻破，攻击者也无法轻易控制整个服务器。该方案已经在 2024 年 USTC Hackergame 中应用于数道 Web 类题目并稳定运行。</p>
  <p>尽管如此，该方案仍有改进空间，如当前外层容器仍需 <code class="language-plaintext highlighter-rouge">SYS_ADMIN</code> 特权，这实际上是相当大的权限，而 <a href="https://github.com/nestybox/sysbox">Sysbox</a> 容器运行时可以在创建容器时直接分离用户命名空间，提供了一种不需要这些特权的替代方案，是一个可以探索的方向。此外，我们的方案为了方便直接禁用了 AppArmor Profile，但实际上只需要创建自定义的 AppArmor Profile 放松一些限制（如允许 <code class="language-plaintext highlighter-rouge">mount</code> 和放松一些路径下的限制），而不需要将其完全置于 <code class="language-plaintext highlighter-rouge">unconfined</code> 状态，这样可以进一步提升安全性，但仍需要进一步探索。</p>
  ]]></content><author><name>RTXUX</name></author><category term="Hackergame" /><category term="Hackergame" /><category term="Docker" /><category term="Container" /><summary type="html"><![CDATA[本文介绍了 2024 年 USTC Hackergame 中 Rootless Docker in Docker 在 Web 类题目容器隔离中的实践。]]></summary></entry><entry><title type="html">快速建立本地 HTTP 文件服务</title><link href="https://lug.ustc.edu.cn/planet/2025/01/local-file-serving/" rel="alternate" type="text/html" title="快速建立本地 HTTP 文件服务" /><published>2025-01-03T00:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/planet/2025/01/local-file-serving</id><content type="html" xml:base="https://lug.ustc.edu.cn/planet/2025/01/local-file-serving/"><![CDATA[<p>USTCLUG 的一个重要服务是它的 <a href="https://mirrors.ustc.edu.cn">开源软件镜像</a>。但是我们这里要讨论的，是另一种自己在本地搭建的文件服务，目的是加速对远程静态文件的访问。这篇文章来源于帮同学解决的一个小问题。</p>
  <h2 id="目的">目的</h2>
  <p>某些课程作业附带的代码示例会通过 HTTP 的方式获取运行需要的输入数据，但是如果这个数据不巧放在某台访问速率很低的服务器上（例如不少用 <code class="language-plaintext highlighter-rouge">.edu</code> 域名的国外大学的课程网站上），则会使得作业的运行时间大大加长——通过只有几 KB/s 的网络下载数十 MB 的数据总不是个好主意。更有可能的是，根据作业内容，样例代码还需要做一些修改，这时候每次调试运行代码时都要去下载一次输入数据，简直难以忍受。本文将针对 Windows 用户，讲解如何在手工下载一次相关的文件后，在本地快速搭建一个 HTTP 服务器提供数据文件，以避免反复访问慢速的源地址。对于不能修改访问地址的情况，也提供了相应的对策。</p>
  <blockquote>
    <p>为何不写针对 Linux 用户的教程呢？这是因为，由于有 Apache 和 Nginx 这类成熟好用的服务器软件，再加上 Linux 操作系统优秀的包管理器，结合网上的教程，用几分钟的时间在 Linux 平台上快速建立并运行一个 HTTP 服务器应该不是难事。而在 Windows 上，配置 IIS 这类大而笨重的服务器非常麻烦，使用 Apache/Nginx 之类的服务器软件又由于操作习惯的不同，也不方便。</p>
  </blockquote>
  <h2 id="使用-python-httpserver">使用 Python <code class="language-plaintext highlighter-rouge">http.server</code></h2>
  <p><strong>对比下面的（老旧的）<code class="language-plaintext highlighter-rouge">EasyWebSvr</code>，使用 Python 很容易完成一个简单的 HTTP 服务器搭建。</strong></p>
  <p>如果你已经安装了 Python，则可以直接使用</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>python3 <span class="nt">-m</span> http.server &lt;port&gt;
</code></pre>
    </div>
  </div>
  <p>来以当前目录为根目录，<code class="language-plaintext highlighter-rouge">&lt;port&gt;</code> 为端口运行一个 HTTP 服务器。</p>
  <p>如果需要指定别的目录 <code class="language-plaintext highlighter-rouge">&lt;path&gt;</code> 作为 HTTP 服务的根目录，可以使用</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>python3 <span class="nt">-m</span> http.server &lt;port&gt; <span class="nt">--directory</span> &lt;path&gt;
</code></pre>
    </div>
  </div>
  <p>如果需要 bind 到特定的地址（对于本地服务而言，应该使用 <code class="language-plaintext highlighter-rouge">127.0.0.1</code>），可以使用下面的命令。<code class="language-plaintext highlighter-rouge">bind</code> 是指将一个网络地址（IP 和端口）与服务器软件中的套接字关联的过程，使得该服务器能接收指定地址上的网络数据。<strong>Python 的 http.server 默认值为 <code class="language-plaintext highlighter-rouge">0.0.0.0</code>，代表所有人都可以访问</strong>。如果只需要本地访问，则可以使用本地回环地址 <code class="language-plaintext highlighter-rouge">127.0.0.1</code>。</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>python3 <span class="nt">-m</span> http.server &lt;port&gt; <span class="nt">--bind</span> 127.0.0.1
</code></pre>
    </div>
  </div>
  <h2 id="使用-easywebserver">使用 EasyWebServer</h2>
  <h3 id="获取一个-http-服务器">获取一个 HTTP 服务器</h3>
  <p>首先当然是要获取一个 HTTP 服务器了，这里我们选择的是 <code class="language-plaintext highlighter-rouge">EasyWebSvr</code>。这个服务器是一个体积超小（只有数十 KB）、单文件、几乎不需要配置的小型 HTTP 服务器，甚至支持 CGI 和 PHP。<code class="language-plaintext highlighter-rouge">EasyWebSvr</code> 是一个历史久远的<a href="https://github.com/baojianjob/EasyWebSvr">开源项目</a>，使用 MSVC 作为开发环境，而且很容易下载到它的编译好的版本（搜索 <code class="language-plaintext highlighter-rouge">EasyWebSvr</code> 就可以了）。下载后你将得到一个 <code class="language-plaintext highlighter-rouge">EasyWebSvr.exe</code> 和一些其他的文件。不用理会其他的文件（事实上它们是不必要的），直接将 <code class="language-plaintext highlighter-rouge">EasyWebSvr.exe</code> 复制到一个空文件夹里面就可以了。</p>
  <blockquote>
    <p>提示：根据<a href="https://github.com/baojianjob/EasyWebSvr/blob/796448f6b312ad0676add90024940316dfa34299/EasyWebSvr/Socket.cpp#L439">代码</a>，EasyWebSvr 会 bind 到 <code class="language-plaintext highlighter-rouge">0.0.0.0</code>，这可能会导致<strong>安全问题</strong>。如果需要 bind 到 <code class="language-plaintext highlighter-rouge">127.0.0.1</code>，可能需要修改代码并重新构建。</p>
  </blockquote>
  <h3 id="准备数据">准备数据</h3>
  <ul>
    <li>从原始的地址上手工下载一份要被访问的数据，放到上面所说的那个空文件夹里面。</li>
    <li>运行 <code class="language-plaintext highlighter-rouge">EasyWebSvr.exe</code>，可以看到一个小窗口。</li>
    <li>点击右下角的锤子按钮（菜单），选择最下面的 <code class="language-plaintext highlighter-rouge">设置</code> 一项。</li>
    <li>在弹出的设置对话框里面，选择 <code class="language-plaintext highlighter-rouge">主目录</code> 为当前 <code class="language-plaintext highlighter-rouge">EasyWebSvr.exe</code> 所在的目录。</li>
    <li>在 <code class="language-plaintext highlighter-rouge">文档</code> 选项卡里面勾选 <code class="language-plaintext highlighter-rouge">允许目录浏览</code> 和 <code class="language-plaintext highlighter-rouge">总是显示目录内容</code>。</li>
    <li>点击 <code class="language-plaintext highlighter-rouge">确定</code> 结束并保存配置。</li>
    <li>然后在主窗口里面点击右下方的红色按钮（在菜单按钮的边上），正常应该看到它变成蓝绿色（这时候如果有 Windows 防火墙提示，请选择允许）。</li>
    <li>此时服务器就配置好了，打开浏览器输入 <code class="language-plaintext highlighter-rouge">http://localhost</code> 即可看到文件列表。</li>
  </ul>
  <h3 id="修改原先的代码">修改原先的代码</h3>
  <ul>
    <li>例如原先提供的代码中数据文件的 URL 是 <code class="language-plaintext highlighter-rouge">http://some.site.edu/path/to/file/data.csv</code>，而这份数据就和 <code class="language-plaintext highlighter-rouge">EasyWebSvr.exe</code> 在同一文件夹下，则将代码中的地址修改为 <code class="language-plaintext highlighter-rouge">http://localhost/data.csv</code> 就可以使用本地的文件服务了！</li>
    <li>服务器最小化后会显示一个小托盘图标，右键点击它可以显示各种选项，包括重新显示主窗口。</li>
    <li>使用完后，点击蓝绿色图标，它会变成红色，此时服务器就关闭了。</li>
  </ul>
  <h3 id="特殊情况">特殊情况</h3>
  <h4 id="代码不支持修改地址">代码不支持修改地址</h4>
  <p>如果代码不支持修改地址（这很少见），那么也可以按照原来的 URL 在本地建立文件夹，类似下面这样：</p>
  <div class="language-text highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>EasyWebSvr.exe
path/                -&gt; 建立这个目录
    to/              -&gt; 建立这个目录
        file/        -&gt; 建立这个目录
            data.csv -&gt; 数据文件在这里
</code></pre>
    </div>
  </div>
  <p>然后修改 <code class="language-plaintext highlighter-rouge">hosts</code> 文件（一般在 <code class="language-plaintext highlighter-rouge">C:\Windows\System32\drivers\etc</code> 里面，需要管理员权限，具体请看相关教程），加上一句</p>
  <div class="language-text highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>127.0.0.1 some.site.edu
</code></pre>
    </div>
  </div>
  <p>然后以管理员权限运行</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>ipconfig /flushdns
</code></pre>
    </div>
  </div>
  <p>即可。可以通过浏览器访问 <code class="language-plaintext highlighter-rouge">http://some.site.edu/path/to/file/data.csv</code> 来确认。这一操作会修改所有对 <code class="language-plaintext highlighter-rouge">some.site.edu</code> 的请求，因此用完后请删去在 <code class="language-plaintext highlighter-rouge">hosts</code> 中加入的那一行，然后再运行一次 <code class="language-plaintext highlighter-rouge">ipconfig /flushdns</code>。</p>
  <h4 id="原地址有非-80-的端口号">原地址有非 80 的端口号</h4>
  <p>如果你拿到的地址使用了别的端口（例如 <code class="language-plaintext highlighter-rouge">some.site.edu:8080</code>）且不能修改，那么请在 EasyWebServer 的设置页面 <code class="language-plaintext highlighter-rouge">端口号</code> 处输入对应的端口号 <code class="language-plaintext highlighter-rouge">8080</code>，对应的访问地址就是 <code class="language-plaintext highlighter-rouge">localhost:8080</code>。</p>
  <h4 id="提示端口已被占用">提示端口已被占用</h4>
  <p>这可能是因为有其他应用在使用 80 端口。通过 <code class="language-plaintext highlighter-rouge">netstat -ano</code> 命令可以查看机器上正被使用的端口号和对应的进程 ID。如果不希望关闭占用 80 端口的程序，那么和上面非 80 端口一样，将我们的 HTTP 文件服务器换到一个别的端口就好了，例如 <code class="language-plaintext highlighter-rouge">8080</code> 或者 <code class="language-plaintext highlighter-rouge">8000</code>，然后再次点红色圆球图标开启服务器。这时候记得访问地址也要加上对应的端口号。</p>
  ]]></content><author><name>luojh</name></author><category term="Tech Tutorial" /><category term="Windows" /><category term="HTTP" /><summary type="html"><![CDATA[USTCLUG 的一个重要服务是它的 开源软件镜像。但是我们这里要讨论的，是另一种自己在本地搭建的文件服务，目的是加速对远程静态文件的访问。这篇文章来源于帮同学解决的一个小问题。]]></summary></entry><entry><title type="html">中国科学技术大学第十一届信息安全大赛圆满落幕</title><link href="https://lug.ustc.edu.cn/news/2024/12/hackergame2024/" rel="alternate" type="text/html" title="中国科学技术大学第十一届信息安全大赛圆满落幕" /><published>2024-12-11T12:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2024/12/hackergame2024</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2024/12/hackergame2024/"><![CDATA[<p>2024 年 11 月 9 日中午 12:00，Hackergame 网站答案提交窗口准时关闭，历时七天的中国科学技术大学第十一届信息安全大赛（Hackergame 2024）圆满落幕。</p>
  <p>2024 年 12 月 11 日 16:00 至 17:00，中国科学技术大学第十一届信息安全大赛颁奖仪式在西区 3B103 教室举行。网络空间安全学院李卫海副书记、USTC-NEBULA 战队指导老师吴文涛老师、本届比赛负责人、USTC-NEBULA 战队成员、本届比赛参赛选手出席了本次颁奖典礼。</p>
  <p>李卫海副书记发表了精彩的颁奖致辞。他强调了信息安全在当今社会中的重要性，并对大赛的顺利举办表示由衷的祝贺。李副书记鼓励学子们在未来的道路上不断追求卓越，希望同学们能够结合实践，将在信息安全大赛中所学到的理论知识应用到实际工作中，为保护网络安全贡献自己的力量。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/whli.jpg" alt="" /></p>
  <p>根据得分结果，中国科学技术大学第十一届信息安全大赛校内组评选出一等奖 1 名、二等奖 5 名、三等奖 25 名、新生奖 2 名、分类奖 5 名以及一血奖 17 名。李卫海副书记和吴文涛老师为获奖同学颁发了奖项。</p>
  <p>叶升宇同学荣获一等奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/first-prize.jpg" alt="" /></p>
  <p>叶知易、吕思源、樊一展、王凯健、杨涛同学荣获二等奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/second-prize.jpg" alt="" /></p>
  <p>何煜恒、吴远韬、潘兆远、彭博、陈庆禾、向红静、杨岳、李远航、朱一鸣、熊桐睿、张天赫、刘睿博、黄嘉玮、李照阳、曹东皓、于泳昊、周恩帅、吴维卿、杨思汗、赵谱棋、陈锦锐、叶力天同学荣获三等奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/third-prize-1.jpg" alt="" /></p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/third-prize-2.jpg" alt="" /></p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/third-prize-3.jpg" alt="" /></p>
  <p>李培锋、许傲施同学荣获新生特别奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/freshman.jpg" alt="" /></p>
  <p>叶知易、蒋皓、杨锦东、叶升宇、熊桐睿分别在综合技能（general）、人工智能（ai）、程序逆向与漏洞利用（binary）、密码学与数学（math）、网站安全（web）获得分类奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/category.jpg" alt="" /></p>
  <p>于泳昊、许傲施、吴书让、黄境、田佳嘉、熊桐睿、周瓯翔、陈庆禾、刘成龙、何煜恒、叶升宇、史可、樊一展、袁保杰、叶知易、杨锦东、蒋皓为各题目的首位答对者，荣获一血奖。</p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/first-solve-1.jpg" alt="" /></p>
  <p><img src="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2024.12.11_Hackergame2024%E9%A2%81%E5%A5%96/first-solve-2.jpg" alt="" /></p>
  <p>中国科学技术大学第十一届信息安全大赛由中国科学技术大学网络空间安全学院、中国科学技术大学团委、中国科学技术大学学生社团管理指导委员会主办，中国科学技术大学学生 Linux 用户协会、中国科学技术大学微软学生俱乐部、中国科学技术大学 NEBULA 战队和中国科学技术大学信息安全俱乐部承办。此外，复旦大学六星战队、广东海洋大学 Hor1zon 战队、哈尔滨工业大学 Linux 开源学生俱乐部、哈尔滨工业大学紫丁香 CTF 俱乐部、南京航空航天大学 Asuri 战队、吉林大学 Linux 用户协会、吉林大学 Spirit 战队、东北大学 NEX 信息安全创新团队、南方科技大学计算机研究协会、南方科技大学 COMPASS 实验室、中山大学 W4terDr0p 战队、中山大学互联网与开源技术协会、同济大学 CatTrainingForce 战队、西安电子科技大学信息安全协会、厦门理工学院信息安全协会、浙江大学 AAA 战队、上海大学开源社区、南阳理工学院计算机与信息化处理协会、上海交通大学 0ops 战队也参与了此次比赛的协办。</p>
  <p>本届比赛于 11 月 2 日中午 12:00 正式开启，历时七天，参赛人数达到 2460 人。比赛共设置了约 35 道题目，涵盖了综合技能、程序逆向与漏洞利用、密码学与数学、Web 安全、人工智能等多个方向。命题延续了往届的优良特色，在具有一定教育意义的同时也不失趣味性，同时结合了科大特色、平衡了题目难度，适度增加了题目数量，争取让新生也能领会网络安全的魅力，在更多的方向上探索网络安全的知识、锻炼相关能力。</p>
  <p>中国科学技术大学第十一届信息安全大赛的成功举办，不仅在学校内部激发了师生们对信息安全的浓厚兴趣，更为整个信息技术社区提供了一个展示实力、交流经验的重要平台，无疑将对同学们未来在信息安全领域的发展产生深远而积极的影响。</p>
  <p>官方题解和参赛选手的解答公布于 <a href="https://github.com/USTC-Hackergame/hackergame2024-writeups/">hackergame2024-writeups</a> ，欢迎大家学习交流。</p>
  ]]></content><author><name>luojh</name></author><category term="LUG 活动" /><summary type="html"><![CDATA[2024 年 11 月 9 日中午 12:00，Hackergame 网站答案提交窗口准时关闭，历时七天的中国科学技术大学第十一届信息安全大赛（Hackergame 2024）圆满落幕。]]></summary></entry><entry><title type="html">镜像站 ZFS 实践</title><link href="https://lug.ustc.edu.cn/planet/2024/12/ustc-mirrors-zfs-rebuild/" rel="alternate" type="text/html" title="镜像站 ZFS 实践" /><published>2024-12-09T00:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/planet/2024/12/ustc-mirrors-zfs-rebuild</id><content type="html" xml:base="https://lug.ustc.edu.cn/planet/2024/12/ustc-mirrors-zfs-rebuild/"><![CDATA[<p>A.K.A. 如何让 2000 元的机械硬盘跑得比 3000 元的固态硬盘还快（</p>
  <p>本文另有<a href="https://ibug.io/p/74">英文版</a>及在南京大学分享时使用的<a href="https://ibug.io/p/72">幻灯片</a>。</p>
  <h2 id="background">背景</h2>
  <p>由中科大 Linux 用户协会维护的<a href="https://mirrors.ustc.edu.cn/">中科大开源软件镜像站</a>是中国大陆高校访问量最大、收录最全的开源软件镜像之一。
    在 2024 年 5 月至 6 月期间，我们的镜像站服务的流量大约是每天 36 TiB，主要分为以下两大类：</p>
  <ul>
    <li>HTTP/HTTPS 流量 19 TiB，请求量 1700 万</li>
    <li>Rsync 流量 10.3 TiB，请求量 2.18 万（如果算上一个异常的客户端，那么总数是 14.78 万）</li>
  </ul>
  <p>多年以来，随着现有镜像仓库容量的增加和新镜像仓库的加入，我们的服务器硬盘容量已经十分紧张了。目前<sup id="fnref:as-of"><a href="#fn:as-of" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>提供镜像服务的两台服务器的磁盘容量都已经接近极限了：</p>
  <ul>
    <li>主（HTTP）服务器采用 XFS 文件系统，在 2023 年 12 月 18 日达到了 63.3 TiB（总容量 66.0 TiB，使用率 96%）；</li>
    <li>副（Rsync）服务器采用 ZFS 文件系统，在 2023 年 11 月 21 日达到了 42.4 TiB（总容量 43.2 TiB，使用率 98%）。</li>
  </ul>
  <p>两台服务器的配置分别如下：</p>
  <dl>
    <dt>HTTP 服务器</dt>
    <dd>
      <ul>
        <li>2020 年秋季搭建</li>
        <li>第二代至强可扩展处理器（Cascade Lake）和 256 GB DDR4 内存</li>
        <li>12 块 10 TB HDD + 一块 2 TB SSD</li>
        <li>在硬件 RAID 上使用 LVM 和 XFS</li>
        <li>由于 XFS（截至本次重建时）不支持压缩，因此为了应对其他分区的潜在的扩容需求，我们在 LVM VG 层面保留了 free PE</li>
      </ul>
    </dd>
    <dt>Rsync 服务器</dt>
    <dd>
      <ul>
        <li>2016 年底搭建</li>
        <li>至强 E5 v4 处理器（Broadwell）和 256 GB DDR4 内存</li>
        <li>12 块 6 TB HDD 和一些小容量 SSD 用来装系统和当缓存</li>
        <li>组建了 ZFS RAID-Z3 阵列，分为 8 块数据盘 + 3 块校验盘，最后一块留作热备</li>
        <li>全默认参数（仅修改了 <code class="language-plaintext highlighter-rouge">zfs_arc_max</code>）</li>
      </ul>
    </dd>
  </dl>
  <p>这两台服务器的磁盘负载非常高，日常维持在 90% 以上，以至于即使从科大校园网内下载镜像，速度也很难达到 50 MB/s。
    显然对于镜像站这种专用于存储用途的机器来说，这样的性能表现是不尽人意的。</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors-io-utilization-may-2024.png" class="image-popup" title="2024 年 5 月期间镜像站两台服务器的 I/O 负载
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors-io-utilization-may-2024.png" alt="2024 年 5 月期间镜像站两台服务器的 I/O 负载" /></a>
    <figcaption>
      2024 年 5 月期间镜像站两台服务器的 I/O 负载
    </figcaption>
  </figure>
  <h2 id="zfs">ZFS</h2>
  <p>ZFS 以“单机存储的终极解决方案”著称。
    它集 RAID、逻辑卷管理和文件系统于一体，具有包括快照、克隆、发送/接收等高级功能。
    ZFS 内的所有数据都有校验，可以在硬盘出现比特翻转等极端情况下尽可能确保文件系统的完整性。
    对于专用于存储的服务器来说，ZFS 看起来是个可以“开箱即用”的解决方案，但当你看到它有如此多的可调节参数之后，你马上就不会这么想了。</p>
  <p>作为前期学习和实验，我在自己的工作站上增加了一批额外的硬盘并把它们组成了两个 ZFS pool，然后注册了一些 PT 站<s>开始刷流</s>来制造一些磁盘负载以便学习研究。
    在 PT 站的<s>刷流</s>成果十分可观：这个单机的 seed box 在两年半间产生了 1.20 PiB 的上传量。</p>
  <p>这两年刷 PT 站刷下来，我总结出来几个重要的 ZFS 学习资料来源：</p>
  <ul>
    <li>UToronto 的 Chris Siebenmann 的博客：<a href="https://utcc.utoronto.ca/~cks/space/blog/">https://utcc.utoronto.ca/~cks/space/blog/</a></li>
    <li>OpenZFS 的官方文档：<a href="https://openzfs.github.io/openzfs-docs/">https://openzfs.github.io/openzfs-docs/</a></li>
    <li>我自己攒出的一篇博客：<a href="https://ibug.io/p/62">Understanding ZFS block sizes</a>
      <ul>
        <li>以及这篇博客底部列出的参考文献</li>
      </ul>
    </li>
  </ul>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/2024-06-05.png" class="image-popup" title="学习 ZFS 过程中的副产物：一个为 qBittorrent 定制的 Grafana 面板（xs
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/2024-06-05.png" alt="一个为 qBittorrent 定制的 Grafana 面板" /></a>
    <figcaption>
      学习 ZFS 过程中的副产物：一个为 qBittorrent 定制的 Grafana 面板（xs
    </figcaption>
  </figure>
  <p>经过多年的 ZFS 学习，我意识到镜像站服务器上的配置其实有很大的优化空间，方法就是 all-in ZFS 并正确地调节一些参数。</p>
  <h2 id="镜像站">镜像站</h2>
  <p>在开工重建 ZFS pool 之前，我们需要正确地理解和分析镜像站的负载类型。简而言之，镜像站的特点是：</p>
  <ul>
    <li>提供文件下载服务</li>
    <li>
      <s>也（被迫）提供“家庭宽带上下行流量平衡服务”</s>
      <p>（责任全在 PCDN 方）</p>
    </li>
    <li>读多写少，且大部分读取都是全文件顺序读取</li>
    <li>能够容忍少量的数据丢失，毕竟镜像内容可以轻易地从上游重新同步回来</li>
  </ul>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors-file-size-distribution-2024-08.png" class="image-popup" title="2024 年 8 月镜像站上的文件大小分布
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors-file-size-distribution-2024-08.png" alt="2024 年 8 月镜像站上的文件大小分布" /></a>
    <figcaption>
      2024 年 8 月镜像站上的文件大小分布
    </figcaption>
  </figure>
  <p>基于以上思考，我们分析了镜像站上存储的内容。从上图中可以看出，镜像站上总文件数超过 4000 万，其中一半的文件大小不到 10 KiB，并且 90% 的文件大小不到 1 MiB。
    尽管如此，所有文件的平均大小仍然达到了 1.6 MiB。</p>
  <h2 id="mirrors2">重建 Rsync 服务器</h2>
  <p>Rsync 服务器的流量较少，但磁盘使用率较为极端，加上我们认定 Rsync 服务的重要性较低，因此在今年 6 月，我们先动手重建了这台服务器。
    我们制定了如下的重建计划：</p>
  <ul>
    <li>首先，考虑到镜像站上一半的文件都不到 10 KiB（注意我们的磁盘的物理扇区大小是 4 KiB），RAID-Z3 的开销过高，因此我们决定将其重建为 RAID-Z2 并且拆成两组 vdev。这样做还有一个额外的好处，即期望情况下我们还可以在这个 ZFS pool 中获得两倍的 IOPS，毕竟文件的每个“块”只存储在一个 vdev 上。</li>
    <li>
      <p>然后我们仔细研究了如何为镜像站场景调优 ZFS dataset 参数：</p>
      <ul>
        <li><code class="language-plaintext highlighter-rouge">recordsize=1M</code>：尽可能优化顺序读写性能，同时减少碎片化</li>
        <li>
          <p><code class="language-plaintext highlighter-rouge">compression=zstd</code>：开点压缩来试试能节约多少磁盘空间</p>
          <ul>
            <li>
              <p>OpenZFS 2.2 开始将 early-abort 机制引入了 Zstd 压缩算法（Zstd-3 以上的等级）。该机制会首先尝试使用 LZ4 和 Zstd-1 来压缩数据以便评估数据的可压缩性，如果数据不可压缩（熵太大），则不再尝试用设定的 Zstd 等级压缩，而是直接原样写入磁盘上，避免在不可压缩的数据上浪费 CPU。</p>
              <p>我们已知镜像站上的大部分内容都是已经压缩过的，因此 early-abort 算是给我们兜了个底，让我们可以放心地开 Zstd。</p>
            </li>
          </ul>
        </li>
        <li><code class="language-plaintext highlighter-rouge">xattr=off</code>：镜像站上的文件不需要扩展属性</li>
        <li><code class="language-plaintext highlighter-rouge">atime=off</code>：镜像站上的文件不需要记录，也不需要更新 atime，可以省掉不少写入</li>
        <li><code class="language-plaintext highlighter-rouge">setuid=off</code>、<code class="language-plaintext highlighter-rouge">exec=off</code>、<code class="language-plaintext highlighter-rouge">devices=off</code> 也是我们不需要的挂载选项（也是一个更安全的做法）</li>
        <li><code class="language-plaintext highlighter-rouge">secondarycache=metadata</code> 让 L2ARC 仅缓存 ZFS 内部的元数据。这是因为 Rsync 服务器上的文件访问模式更加均匀，而不像面向终端用户的 HTTP 服务器上冷热分明，因此仅缓存元数据可以节约 SSD 寿命。</li>
      </ul>
    </li>
    <li>
      <p>以及一些可能有潜在（但我们认为我们可以容忍的）风险的选项：</p>
      <ul>
        <li><code class="language-plaintext highlighter-rouge">sync=disabled</code>：禁用同步写入语义（<code class="language-plaintext highlighter-rouge">open(O_SYNC)</code>、<code class="language-plaintext highlighter-rouge">sync()</code> 和 <code class="language-plaintext highlighter-rouge">fsync()</code> 等）以让 ZFS 能够充分发挥写缓冲区的意义，如降低碎片率等</li>
        <li><code class="language-plaintext highlighter-rouge">redundant_metadata=some</code>：（OpenZFS 2.2）减少元数据的冗余度来获得更好的写入性能。</li>
      </ul>
      <p>我们认为这两个选项符合我们对镜像站仓库内容的数据安全和完整性需求的理解，它们在其他场景下不一定“安全”。</p>
    </li>
    <li>
      <p>对于 ZFS 模块层面的参数，光是 290+ 的数量就已经很劝退了。
        此处感谢 Debian ZFS 维护者兼北京外国语大学镜像站管理员 @happyaron 的帮助，我们快速找出了十几个常用的参数进行针对性调节。</p>
      <div class="language-shell highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code><span class="c"># 设置 ARC 大小范围为 160-200 GiB，并为操作系统保留 16 GiB 空闲</span>
options zfs <span class="nv">zfs_arc_max</span><span class="o">=</span>214748364800
options zfs <span class="nv">zfs_arc_min</span><span class="o">=</span>171798691840
options zfs <span class="nv">zfs_arc_sys_free</span><span class="o">=</span>17179869184

<span class="c"># 设置元数据对用户数据优先级的权重为 20x (OpenZFS 2.2+)</span>
options zfs <span class="nv">zfs_arc_meta_balance</span><span class="o">=</span>2000

<span class="c"># 允许 dnode 占用至多 80% 的 ARC 容量</span>
options zfs <span class="nv">zfs_arc_dnode_limit_percent</span><span class="o">=</span>80

<span class="c"># 以下几行参见 man page 中的 "ZFS I/O Scheduler" 一节</span>
options zfs <span class="nv">zfs_vdev_async_read_max_active</span><span class="o">=</span>8
options zfs <span class="nv">zfs_vdev_async_read_min_active</span><span class="o">=</span>2
options zfs <span class="nv">zfs_vdev_scrub_max_active</span><span class="o">=</span>5
options zfs <span class="nv">zfs_vdev_max_active</span><span class="o">=</span>20000

<span class="c"># 避免因内存压力降低 ARC 读写速度</span>
options zfs <span class="nv">zfs_arc_lotsfree_percent</span><span class="o">=</span>0

<span class="c"># L2ARC 参数</span>
options zfs <span class="nv">l2arc_headroom</span><span class="o">=</span>8
options zfs <span class="nv">l2arc_write_max</span><span class="o">=</span>67108864
options zfs <span class="nv">l2arc_noprefetch</span><span class="o">=</span>0
</code></pre>
        </div>
    </div>
      <p>另外还有 <code class="language-plaintext highlighter-rouge">zfs_dmu_offset_next_sync</code>，但由于它从 OpenZFS 2.1.5 开始已经默认启用了，因此我们将其从本列表中略去。</p>
    </li>
  </ul>
  <p>将 Rsync 服务暂时转移到由 HTTP 服务器兼任之后，我们 destroy 了原有的 ZFS pool 并重新组建了一个新的 pool，然后再从外面（上游或 TUNA、BFSU 等友校镜像站）把原有的仓库同步回来。
    令我们感到惊讶的是，把总共近 40 TiB 的仓库同步回来只花了 3 天，比我们预想的要快得多。
    其他的一些数据看起来也令人振奋：</p>
  <ul>
    <li>
      <p>ZFS 压缩率：39.5T / 37.1T (1.07x)</p>
      <p>需要特别指出的是，ZFS 只显示压缩率小数点后两位，所以更高的精度，需要通过原始数据自己计算：</p>
      <div class="language-shell highlighter-rouge">
        <div class="highlight">
          <pre class="highlight"><code>zfs list <span class="nt">-po</span> name,logicalused,used
</code></pre>
        </div>
    </div>
      <p>我们更精确的压缩率是 1 + 6.57%，即压掉了 2.67 TB（2.43 TiB），约等于 <a href="/static/planet/ustc-mirrors-zfs-rebuild/lenovo-legion-wechat-data.jpg">9 份微信数据</a>（不是</p>
    </li>
    <li>
      <p>最关键的是更合理的 I/O 负载：</p>
      <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-io-utilization-and-free-space-june-july-2024.png" class="image-popup" title="mirrors2 机器在重建前后的 I/O 负载
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-io-utilization-and-free-space-june-july-2024.png" alt="mirrors2 机器在重建前后的 I/O 负载" /></a>
        <figcaption>
          mirrors2 机器在重建前后的 I/O 负载
        </figcaption>
      </figure>
    </li>
  </ul>
  <p>可以看出，经过几天的预热之后，I/O 负载维持在了 20% 左右，而在重建之前一直维持在 90% 以上。</p>
  <h2 id="mirrors4">重建 HTTP 服务器</h2>
  <p>我们的 HTTP 服务器是在 2020 年秋季搭建的，并且当时也有一些不同的背景。
    申请这台服务器正是因为 Rsync 服务器容量过满且性能不佳，加上当时社团内也没有熟悉 ZFS 的同学，我们对 ZFS 的印象很差，所以我们决定完全避开 ZFS，使用硬件 RAID、LVM 和 XFS，其中使用 LVM 的原因是 RAID 卡不支持跨两个控制器组 RAID。
    对于“内存做缓存”这部分，我们决定直接使用内核的 page cache；而对于 SSD 缓存，我们则率先吃了 LVMcache 的螃蟹。</p>
  <p>然而这些过于“新鲜”的技术并没有带来比（现在的 ZFS）更好的体验：</p>
  <ul>
    <li>XFS 无法缩小，因此我们不得不在 LVM VG 层面保留了 free PE。同时我们也不能把 XFS 文件系统用满，因此这里就有了两层无法利用的空闲空间。</li>
    <li>我们最初分配了 1.5 TB 的 SSD 缓存，但 LVMcache 又建议我们不要超过 100 万个 chunk，我们当时也没有足够的精力和知识水平去研究这个建议背后的技术细节，因此我们最终只分配了 1 TiB（1 MiB chunk size * 1 Mi chunks）的 SSD 缓存。</li>
    <li>SSD 缓存策略不可调，多年以后我们翻了 kernel 源码才发现它是一个 64 级的 LRU。</li>
    <li>配好 cache 之后 GRUB 立刻挂了（难绷），我们调查发现原因是 GRUB 有一套自己的解析 LVM metadata 的代码，它并没有正确处理（或者说根本没处理）VG 中有 cache volume 的情况，我们不得不自己 <a href="https://github.com/taoky/grub/commit/85b260baec91aa4f7db85d7592f6be92d549a0ae">patch</a> 了 GRUB 才能正常开机。</li>
    <li>由于我们对 LVMcache 的 chunk 不够了解，我们的 SSD 在不到 2 年的时间里就严重超过了写入寿命，我们被迫申请换新。</li>
  </ul>
  <p>在 SSD 换新之后，即使我们认为我们对 LVMcache 做出了稍微合理一点的调参，坚持忽略警告采用 128 KiB 的 chunk size 和 800 万个 chunk 之后，它的性能（命中率）也并不可观：</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors4-dmcache-may-june-2024.png" class="image-popup" title="2024 年 5 月至 6 月期间 LVMcache 的命中率
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors4-dmcache-may-june-2024.png" alt="2024 年 5 月至 6 月期间 LVMcache 的命中率" /></a>
    <figcaption>
      2024 年 5 月至 6 月期间 LVMcache 的命中率
    </figcaption>
  </figure>
  <p>这些年来我们已经踩够了 LVMcache 的坑了，加上 Rsync 服务器重建的巨大成功，我们重新开始相信 ZFS 是天下第一的存储方案了。
    所以一个月之后，我们又制定了一个相似的重建计划准备重建 HTTP 服务器，但是有一些微小的差别：</p>
  <ul>
    <li>我们的 Rsync 服务器采用原生的 Debian kernel + <code class="language-plaintext highlighter-rouge">zfs-dkms</code>，但根据我们使用 PVE 的经验，我们准备在 HTTP 服务器上直接用 <code class="language-plaintext highlighter-rouge">6.8.8-3-pve</code> kernel，它打包了 <code class="language-plaintext highlighter-rouge">zfs.ko</code>，这样我们就不用在 DKMS 上浪费时间了。</li>
    <li>由于磁盘数目相同（12 块），我们也采用了两个 6 盘 RAID-Z2 vdev 的组合。
      <ul>
        <li>考虑到这台服务器直接向用户提供 HTTP 服务，磁盘的访问模式会比 Rsync 服务器更加冷热分明，因此我们保持了 <code class="language-plaintext highlighter-rouge">secondarycache=all</code> 的设置（采用默认值，不动）。</li>
        <li>这台新服务器的 CPU 更新更好，因此我们把压缩等级提高到了 <code class="language-plaintext highlighter-rouge">zstd-8</code> 来试试能否获得更好的压缩比。</li>
      </ul>
    </li>
    <li>我们在 Rsync 服务器上已经有了一个完整的、经过 ZFS 优化过的仓库，因此我们可以直接用 <code class="language-plaintext highlighter-rouge">zfs send -Lcp</code> 把数据倒过来。我们最终只花了 36 小时就把超过 50 TiB 的数据都倒回来了。</li>
    <li>由于两台服务器上存储的镜像仓库有所不同，HTTP 服务器上的压缩比略低一些，为 1 + 3.93%（压掉了 2.42 TiB / 2.20 TiB）。</li>
  </ul>
  <p>我们把两台服务器的 I/O 负载放在一张图里对比：</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-io-utilization-june-july-2024.png" class="image-popup" title="镜像站两台服务器在重建前后的 I/O 负载
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-io-utilization-june-july-2024.png" alt="镜像站两台服务器在重建前后的 I/O 负载" /></a>
    <figcaption>
      镜像站两台服务器在重建前后的 I/O 负载
    </figcaption>
  </figure>
  <p>上图左半部分为重建前的情况，中间部分为仅重建了 Rsync 服务器的情况，右半部分为两台服务器都重建完毕后的情况。</p>
  <p>ZFS ARC 的命中率也十分可观：</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-zfs-arc-hit-rate.png" class="image-popup" title="两台服务器的 ZFS ARC 命中率
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-zfs-arc-hit-rate.png" alt="两台服务器的 ZFS ARC 命中率" /></a>
    <figcaption>
      两台服务器的 ZFS ARC 命中率
    </figcaption>
  </figure>
  <p>稳定下来之后，两台服务器的 I/O 负载还更低了：</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-disk-io-after-rebuild.png" class="image-popup" title="两台服务器重建后磁盘 I/O 的稳定情况
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-disk-io-after-rebuild.png" alt="两台服务器重建后磁盘 I/O 的稳定情况" /></a>
    <figcaption>
      两台服务器重建后磁盘 I/O 的稳定情况
    </figcaption>
  </figure>
  <h2 id="杂项">杂项</h2>
  <h3 id="zfs-透明压缩">ZFS 透明压缩</h3>
  <p>我们并没有想到这么多仓库的压缩率都还不错：</p>
  <table>
    <thead>
      <tr>
        <th style="text-align: left">NAME</th>
        <th style="text-align: right">LUSED</th>
        <th style="text-align: right">USED</th>
        <th style="text-align: right">RATIO</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="text-align: left">pool0/repo/crates.io-index</td>
        <td style="text-align: right">2.19G</td>
        <td style="text-align: right">1.65G</td>
        <td style="text-align: right">3.01x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/elpa</td>
        <td style="text-align: right">3.35G</td>
        <td style="text-align: right">2.32G</td>
        <td style="text-align: right">1.67x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/rfc</td>
        <td style="text-align: right">4.37G</td>
        <td style="text-align: right">3.01G</td>
        <td style="text-align: right">1.56x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/debian-cdimage</td>
        <td style="text-align: right">1.58T</td>
        <td style="text-align: right">1.04T</td>
        <td style="text-align: right">1.54x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/tldp</td>
        <td style="text-align: right">4.89G</td>
        <td style="text-align: right">3.78G</td>
        <td style="text-align: right">1.48x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/loongnix</td>
        <td style="text-align: right">438G</td>
        <td style="text-align: right">332G</td>
        <td style="text-align: right">1.34x</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/rosdistro</td>
        <td style="text-align: right">32.2M</td>
        <td style="text-align: right">26.6M</td>
        <td style="text-align: right">1.31x</td>
      </tr>
    </tbody>
  </table>
  <p>有些数字看着不太对劲（比如第一个），我们认为是这个问题造成的：<a href="https://github.com/openzfs/zfs/issues/7639"><i class="fab fa-github"></i> openzfs/zfs#7639</a></p>
  <p>如果我们按照压缩量排序，结果如下：</p>
  <table>
    <thead>
      <tr>
        <th style="text-align: left">NAME</th>
        <th style="text-align: right">LUSED</th>
        <th style="text-align: right">USED</th>
        <th style="text-align: right">DIFF</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="text-align: left">pool0/repo</td>
        <td style="text-align: right">58.3T</td>
        <td style="text-align: right">56.1T</td>
        <td style="text-align: right">2.2T</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/debian-cdimage</td>
        <td style="text-align: right">1.6T</td>
        <td style="text-align: right">1.0T</td>
        <td style="text-align: right">549.6G</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/opensuse</td>
        <td style="text-align: right">2.5T</td>
        <td style="text-align: right">2.3T</td>
        <td style="text-align: right">279.7G</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/turnkeylinux</td>
        <td style="text-align: right">1.2T</td>
        <td style="text-align: right">1.0T</td>
        <td style="text-align: right">155.2G</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/loongnix</td>
        <td style="text-align: right">438.2G</td>
        <td style="text-align: right">331.9G</td>
        <td style="text-align: right">106.3G</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/alpine</td>
        <td style="text-align: right">3.0T</td>
        <td style="text-align: right">2.9T</td>
        <td style="text-align: right">103.9G</td>
      </tr>
      <tr>
        <td style="text-align: left">pool0/repo/openwrt</td>
        <td style="text-align: right">1.8T</td>
        <td style="text-align: right">1.7T</td>
        <td style="text-align: right">70.0G</td>
      </tr>
    </tbody>
  </table>
  <p><code class="language-plaintext highlighter-rouge">debian-cdimage</code> 一个仓库就占了总压缩量的 1/4。</p>
  <h3 id="grafana-for-zfs-io">Grafana for ZFS I/O</h3>
  <p>重建后，我们也修了一个显示 ZFS I/O 的 Grafana 面板。
    因为 ZFS 的 I/O 统计数据是通过 <code class="language-plaintext highlighter-rouge">/proc/spl/kstat/zfs/$POOL/objset-$OBJSETID_HEX</code> 获取的，并且是分“object set”（即 dataset）累计统计的，所以我们需要先对每个 dataset 的数据做差分，再按 pool 加起来。
    也就是说，一个 InfluxQL subquery 是跑不掉的。</p>
  <div class="language-sql highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="k">SELECT</span>
  <span class="n">non_negative_derivative</span><span class="p">(</span><span class="k">sum</span><span class="p">(</span><span class="nv">"reads"</span><span class="p">),</span> <span class="mi">1</span><span class="n">s</span><span class="p">)</span> <span class="k">AS</span> <span class="nv">"read"</span><span class="p">,</span>
  <span class="n">non_negative_derivative</span><span class="p">(</span><span class="k">sum</span><span class="p">(</span><span class="nv">"writes"</span><span class="p">),</span> <span class="mi">1</span><span class="n">s</span><span class="p">)</span> <span class="k">AS</span> <span class="nv">"write"</span>
<span class="k">FROM</span> <span class="p">(</span>
  <span class="k">SELECT</span>
    <span class="k">first</span><span class="p">(</span><span class="nv">"reads"</span><span class="p">)</span> <span class="k">AS</span> <span class="nv">"reads"</span><span class="p">,</span>
    <span class="k">first</span><span class="p">(</span><span class="nv">"writes"</span><span class="p">)</span> <span class="k">AS</span> <span class="nv">"writes"</span>
  <span class="k">FROM</span> <span class="nv">"zfs_pool"</span>
  <span class="k">WHERE</span> <span class="p">(</span><span class="nv">"host"</span> <span class="o">=</span> <span class="s1">'taokystrong'</span> <span class="k">AND</span> <span class="nv">"pool"</span> <span class="o">=</span> <span class="s1">'pool0'</span><span class="p">)</span> <span class="k">AND</span> <span class="err">$</span><span class="n">timeFilter</span>
  <span class="k">GROUP</span> <span class="k">BY</span> <span class="nb">time</span><span class="p">(</span><span class="err">$</span><span class="n">interval</span><span class="p">),</span> <span class="nv">"host"</span><span class="p">::</span><span class="n">tag</span><span class="p">,</span> <span class="nv">"pool"</span><span class="p">::</span><span class="n">tag</span><span class="p">,</span> <span class="nv">"dataset"</span><span class="p">::</span><span class="n">tag</span> <span class="n">fill</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">WHERE</span> <span class="err">$</span><span class="n">timeFilter</span>
<span class="k">GROUP</span> <span class="k">BY</span> <span class="nb">time</span><span class="p">(</span><span class="err">$</span><span class="n">interval</span><span class="p">),</span> <span class="nv">"pool"</span><span class="p">::</span><span class="n">tag</span> <span class="n">fill</span><span class="p">(</span><span class="n">linear</span><span class="p">)</span>
</code></pre>
    </div>
  </div>
  <p>由于 subquery 的存在，这个 query 确实有点慢，但我们也没啥能优化的。</p>
  <p>如果要显示读写速率的话，直接把内层查询的 <code class="language-plaintext highlighter-rouge">reads</code> 和 <code class="language-plaintext highlighter-rouge">writes</code> 换成 <code class="language-plaintext highlighter-rouge">nread</code> 和 <code class="language-plaintext highlighter-rouge">nwritten</code> 就行了。</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-zfs-io-count.png" class="image-popup" title="ZFS IOPS 和带宽
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/mirrors2-4-zfs-io-count.png" alt="ZFS IOPS 和带宽" /></a>
    <figcaption>
      ZFS IOPS 和带宽
    </figcaption>
  </figure>
  <p>令 UC 震惊部出动的是，一个机械盘阵列竟然能跑出平均 15k、最高 50k 的 IOPS。
    我们发现这个统计数字算上了 ARC hit，也就是只有一小部分 I/O 请求是真正落盘的，那就好解释了。</p>
  <h3 id="apparmor">AppArmor</h3>
  <p>换上先进的 PVE kernel 之后，我们很快就发现同步任务全挂了（）
    排查发现 <code class="language-plaintext highlighter-rouge">rsync</code> 在调用 <code class="language-plaintext highlighter-rouge">socketpair(2)</code> 的时候冒出了 <code class="language-plaintext highlighter-rouge">EPERM</code>，这是我们从来没遇到过的情况。
    实际上这些系统调用都被 AppArmor 拦下来了，最终查到是 Ubuntu 在 kernel 里加的私货 <code class="language-plaintext highlighter-rouge">security/apparmor/af_unix.c</code> 导致的。
    由于 Proxmox VE 的 kernel 是从 Ubuntu fork 过来的，这个私货也就被夹带到我们服务器上了。</p>
  <p>我们发现 PVE 也打包了自己的 AppArmor <code class="language-plaintext highlighter-rouge">features</code> 配置，就把它直接拉过来用：</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>dpkg-divert <span class="nt">--package</span> lxc-pve <span class="nt">--rename</span> <span class="nt">--divert</span> /usr/share/apparmor-features/features.stock <span class="nt">--add</span> /usr/share/apparmor-features/features
wget <span class="nt">-O</span> /usr/share/apparmor-features/features https://github.com/proxmox/lxc/raw/master/debian/features
</code></pre>
    </div>
  </div>
  <h3 id="file-deduplication">文件级去重</h3>
  <p>我们注意到个别仓库有大量的重复的、内容相同的目录，怀疑可能是同步方法（HTTP）的限制导致目录的符号链接变成了完整内容的拷贝。</p>
  <figure class=""><a href="/static/planet/ustc-mirrors-zfs-rebuild/ls-zerotier-redhat-el.png" class="image-popup" title="ZeroTier 仓库中的一些目录
"><img src="/static/planet/ustc-mirrors-zfs-rebuild/ls-zerotier-redhat-el.png" alt="ZeroTier 仓库中的一些目录" /></a>
    <figcaption>
      ZeroTier 仓库中的一些目录
    </figcaption>
  </figure>
  <p>我们想到了 ZFS 的 deduplication，于是在 ZeroTier 仓库上做了一个初步的测试：</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code>zfs create <span class="nt">-o</span> <span class="nv">dedup</span><span class="o">=</span>on pool0/repo/zerotier
<span class="c"># 导入数据</span>
</code></pre>
    </div>
  </div>
  <div class="language-console highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="gp">#</span><span class="w"> </span>zdb <span class="nt">-DDD</span> pool0
<span class="go">dedup = 4.93, compress = 1.23, copies = 1.00, dedup * compress / copies = 6.04
</span></code></pre>
    </div>
  </div>
  <p>结果十分可观，但考虑到 ZFS dedup 一向来糟糕的名声，我们还是不太想在镜像站上启用。
    所以我们重新找了个更灵车的方案：</p>
  <div class="language-shell highlighter-rouge">
    <div class="highlight">
      <pre class="highlight"><code><span class="c"># post-sync.sh</span>
<span class="c"># Do file-level deduplication for select repos</span>
<span class="k">case</span> <span class="s2">"</span><span class="nv">$NAME</span><span class="s2">"</span> <span class="k">in
  </span>docker-ce|influxdata|nginx|openresty|proxmox|salt|tailscale|zerotier<span class="p">)</span>
    jdupes <span class="nt">-L</span> <span class="nt">-Q</span> <span class="nt">-r</span> <span class="nt">-q</span> <span class="s2">"</span><span class="nv">$DIR</span><span class="s2">"</span> <span class="p">;;</span>
<span class="k">esac</span>
</code></pre>
    </div>
  </div>
  <p>这个用户态的文件去重工具十分好用，效果堪比 ZFS，而且没有性能损失。
    我们对几个明显有重复内容的仓库跑了一下 jdupes，结果如下：</p>
  <table>
    <thead>
      <tr>
        <th>Name</th>
        <th>Orig</th>
        <th>Dedup</th>
        <th>Diff</th>
        <th>Ratio</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>proxmox</td>
        <td>395.4G</td>
        <td>162.6G</td>
        <td>232.9G</td>
        <td>2.43x</td>
      </tr>
      <tr>
        <td>docker-ce</td>
        <td>539.6G</td>
        <td>318.2G</td>
        <td>221.4G</td>
        <td>1.70x</td>
      </tr>
      <tr>
        <td>influxdata</td>
        <td>248.4G</td>
        <td>54.8G</td>
        <td>193.6G</td>
        <td>4.54x</td>
      </tr>
      <tr>
        <td>salt</td>
        <td>139.0G</td>
        <td>87.2G</td>
        <td>51.9G</td>
        <td>1.59x</td>
      </tr>
      <tr>
        <td>nginx</td>
        <td>94.9G</td>
        <td>59.7G</td>
        <td>35.2G</td>
        <td>1.59x</td>
      </tr>
      <tr>
        <td>zerotier</td>
        <td>29.8G</td>
        <td>6.1G</td>
        <td>23.7G</td>
        <td>4.88x</td>
      </tr>
      <tr>
        <td>mysql-repo</td>
        <td>647.8G</td>
        <td>632.5G</td>
        <td>15.2G</td>
        <td>1.02x</td>
      </tr>
      <tr>
        <td>openresty</td>
        <td>65.1G</td>
        <td>53.4G</td>
        <td>11.7G</td>
        <td>1.22x</td>
      </tr>
      <tr>
        <td>tailscale</td>
        <td>17.9G</td>
        <td>9.0G</td>
        <td>9.0G</td>
        <td>2.00x</td>
      </tr>
    </tbody>
  </table>
  <p>参考上述表格，我们排除了 <code class="language-plaintext highlighter-rouge">mysql-repo</code>，因为它的去重比例太低，不值得花费跑一遍去重产生的 I/O 负载。</p>
  <h2 id="conclusion">总结</h2>
  <p>ZFS 解决了我们镜像站上的一大堆问题，并且有了此次调参经验，我们现在宣布 <strong>ZFS 天下第一</strong>（不是）</p>
  <p>有了 ZFS 之后：</p>
  <ul>
    <li>我们不再担心分区问题，ZFS 可以灵活分配。</li>
    <li>我们的机械盘比别人的固态盘跑得还快，这非常 excited！
      <ul>
        <li>我们成为了第一个不再<strong>羡慕</strong> TUNA 的全闪服务器的镜像站！</li>
      </ul>
    </li>
    <li>零成本获得额外容量，由 ZFS 透明压缩和去重联合赞助！</li>
  </ul>
  <h3 id="considerations">思考</h3>
  <p>虽然我们的 ZFS 配置看起来非常高效，但我们也知道 ZFS 在长期运行中可能会因为碎片化而导致性能下降的问题。
    我们会持续关注我们的服务器，监控长期的性能变化。</p>
  <div class="footnotes" role="doc-endnotes">
    <ol>
      <li id="fn:as-of">
        <p>指本文所述的重建工程之前 <a href="#fnref:as-of" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
      </li>
    </ol>
  </div>
  ]]></content><author><name>iBug</name></author><category term="mirrors" /><category term="linux" /><category term="服务器" /><category term="zfs" /><summary type="html"><![CDATA[A.K.A. 如何让 2000 元的机械硬盘跑得比 3000 元的固态硬盘还快（]]></summary></entry><entry><title type="html">Slidev 幻灯片制作</title><link href="https://lug.ustc.edu.cn/news/2024/12/slidev/" rel="alternate" type="text/html" title="Slidev 幻灯片制作" /><published>2024-12-04T15:00:00+08:00</published><updated>2025-10-18T00:48:56+08:00</updated><id>https://lug.ustc.edu.cn/news/2024/12/slidev</id><content type="html" xml:base="https://lug.ustc.edu.cn/news/2024/12/slidev/"><![CDATA[<p>2024 年 12 月 4 日，校学生 Linux 用户协会在西区第三教学楼 3A306 教室举行小聚。本次小聚是由 Slidev 团队成员 _Kerman 同学带来的 Slidev 幻灯片制作工具介绍。Slidev.js 是一款为开发者设计的开源的幻灯片制作工具，通过简单的 Markdown 语法即可制作出精美的幻灯片，在此基础上也很方便添加交互式的内容、添加动画效果等。它非常适合用于学术报告、技术分享、演讲等场景。</p>
  <p>_Kerman 同学首先向大家介绍了 Slidev 的基本使用方法，包括如何安装、创建项目、添加内容等。他随后演示了如何通过 Markdown 语法添加文本、图片、代码块、表格等内容及一些高级功能，例如添加动画、如何添加交互式的内容等。最后，他还向大家展示了如何将制作好的幻灯片导出为 PDF 文件。在现场，许多同学尝试了 Slidev，创建了自己的第一个 Slidev 幻灯片项目。</p>
  <p>小聚的最后，_Kerman 同学分享了他自己参与相关开源社区的感受，并推荐大家多参与开源社区。本次小聚的幻灯片可以在 <a href="https://ftp.lug.ustc.edu.cn/活动/2024.12.04_Slidev_小聚/slides/">LUG FTP</a> 查看。</p>
  ]]></content><author><name>tiankaima</name></author><category term="LUG 活动" /><category term="LUG 小聚" /><summary type="html"><![CDATA[2024 年 12 月 4 日，校学生 Linux 用户协会在西区第三教学楼 3A306 教室举行小聚。本次小聚是由 Slidev 团队成员 _Kerman 同学带来的 Slidev 幻灯片制作工具介绍。Slidev.js 是一款为开发者设计的开源的幻灯片制作工具，通过简单的 Markdown 语法即可制作出精美的幻灯片，在此基础上也很方便添加交互式的内容、添加动画效果等。它非常适合用于学术报告、技术分享、演讲等场景。]]></summary></entry></feed>