<?xml version="1.0" encoding="utf-8" standalone="no"?><feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://u.sb/</id>
    <title>烧饼博客</title>
    <updated>2026-07-14T08:09:33.278Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Showfom</name>
        <email>i@m.ac</email>
        <uri>https://u.sb/</uri>
    </author>
    <link href="https://u.sb/" rel="alternate"/>
    <link href="https://u.sb/atom.xml" rel="self"/>
    <subtitle>烧饼博客是一个专注于服务器运维、专业域名投资分析的网站。</subtitle>
    <rights>© 2026 烧饼博客</rights>
    <entry>
        <title type="html"><![CDATA[博客大联盟：一个给独立博客的收录与聚合站]]></title>
        <id>https://u.sb/boke/</id>
        <link href="https://u.sb/boke/"/>
        <updated>2026-07-13T12:56:00.000Z</updated>
        <summary type="html"><![CDATA[介绍博客大联盟，一个独立博客目录和 RSS 聚合站，帮助大家找到更多有趣的独立博客。]]></summary>
        <content type="html"><![CDATA[<p>近几年我越来越不爱用搜索引擎找博客了，搜一个技术问题，前几页经常是采集站、营销软文和批量生成的内容。</p>
<p>社交平台也差不多。算法觉得您喜欢什么，就追着喂什么。刷起来很省事，想顺着一个人的文章慢慢读下去却不容易。</p>
<p>我认识的那些博客作者其实还在写，只是不太容易在搜索和信息流里碰到他们。于是，我做了一个叫「<a href="https://bo.ke/">博客大联盟</a>」的网站。</p>
<h2>1、博客大联盟是什么</h2>
<p><a href="https://bo.ke/">博客大联盟</a> 是一个独立博客目录，顺便聚合每个博客的 RSS。</p>
<p>它的前身是我手工维护的朋友博客友链聚合 <a href="https://peng.you/">peng.you</a>。后来实在嫌手工维护太麻烦，干脆写了个小系统，把博客名片和 RSS 聚合都自动化了。</p>
<p>每个博客都有一张名片，上面是名称、作者、简介、标签和最新文章。系统定时读取 RSS，新文章按照发布时间排列。没有猜你喜欢，也没有什么神奇的推荐算法，您可以翻目录、看标签，或者直接搜博客名称和作者。</p>
<p><img src="https://s.bh.sb/2026/07/11/6m3xp/image.webp" alt="博客大联盟中的烧饼博客详情页，展示已验证标记、博客简介与标签、首页预览和最新文章"></p>
<p>目前暂时没打算把这货做成另一个信息流平台。先把各处的独立博客放到一个目录里，让大家能互相找到，这就够了。</p>
<h2>2、怎么加入博客大联盟</h2>
<p>整个流程是登录、填写资料、自动检查、验证域名，最后等管理员审核。</p>
<h3>2.1 登录</h3>
<p>打开 <a href="https://bo.ke/">bo.ke</a>，使用 GitHub 或 Google 一键登录即可，不需要另外注册账号和记一套新密码。第一次登录时，系统会自动创建账号，只读取昵称、头像和邮箱这些基本资料。您也可以通过<a href="https://bo.ke/invite/a60a56483c30efb7d61f2f4a457bb03e/">我的邀请链接</a>注册哦。</p>
<h3>2.2 提交博客</h3>
<p>登录后点击「提交博客」，填写下面这些信息：</p>
<ul>
<li>博客名称和链接；</li>
<li>RSS / Atom 订阅地址，没有也可以不填，不过建议独立博客还是配一个；</li>
<li>作者和博客简介；</li>
<li>从现有分类中选择 1 – 5 个标签；</li>
<li>上传博客头像，或者使用 Gravatar、GitHub、Google 头像；</li>
<li>如果已经放了博客大联盟的链接，也可以填写链接所在页面。</li>
</ul>
<p>一个账号最多可以收录 5 个博客。</p>
<p>目前加入的条件如下：</p>
<ul>
<li>博客必须是独立的原创博客，有自己的独立域名，比如 <code>example.com</code> 或 <code>blog.example.com</code>，或者使用 PSL 列表的二三级域名，比如 <code>example.github.io</code>。</li>
<li>博客内容必须是中文为主，不能是纯其他语言的博客；</li>
<li>博客内容必须是公开的，不能是私密或需要登录才能访问的博客；</li>
<li>博客必须有一定的更新频率，至少每一年能更新一次，特殊情况可以豁免；</li>
<li>博客内容必须积极向上，不能包含违法、低俗、恶意攻击、政治敏感等内容；</li>
<li>不能是采集站、资源下载站、营销软文或批量生成的内容。</li>
</ul>
<h3>2.3 自动检查</h3>
<p>提交后，系统会检查博客链接能不能打开、RSS 能不能解析、头像能不能加载。页面会列出每一项的结果，有问题就改完再提交。全部通过以后，才会进入域名所有权验证。</p>
<h3>2.4 验证域名并等待审核</h3>
<p>自动检查通过后，需要证明这个域名确实属于您。完成验证，提交才会进入管理员审核。这一步稍微麻烦一点，下面仔细说。</p>
<h2>3、域名所有权验证</h2>
<p>DNS TXT、DNS CNAME 和文件验证三选一。页面会生成一个 token，下面的写法只是示例，实际操作时直接复制页面给出的内容。</p>
<p>假设您的域名是 <code>example.com</code>，那么有三种验证方式：</p>
<h3>3.1 DNS TXT 验证</h3>
<p>在域名的 DNS 解析中给您博客的域名添加一条 TXT 记录，内容如下：</p>
<pre><code class="language-text">bo-ke-verify=&lt;您的 token&gt;
</code></pre>
<p>不同 DNS 服务商的填写方式不太一样，有些要给 TXT 内容加引号，有些不需要，添加后记得看一眼服务商显示的完整记录哦。</p>
<p>加完解析记录后可以用 <code>dig</code> 或 <code>nslookup</code> 检查解析结果，确认 TXT 记录已经生效。</p>
<p>DNS 记录生效可能需要几分钟到几小时。等解析生效后，回到验证页面点击「开始验证」即可。</p>
<h3>3.2 DNS CNAME 验证</h3>
<p>为域名添加下面这条 CNAME 记录：</p>
<pre><code class="language-text">bo-ke-verify.example.com.  CNAME  verify-&lt;token&gt;.bo.ke.
</code></pre>
<p>不同 DNS 服务商的填写方式不太一样。有些只需要填 <code>bo-ke-verify</code>，有些要填完整域名，有些要给 CNAME 域名最后加点号，添加后记得看一眼服务商显示的完整记录哦。</p>
<p>加完以后可以用 <code>dig</code> 或 <code>nslookup</code> 检查解析结果，确认 CNAME 指向了 <code>verify-&lt;token&gt;.bo.ke.</code>。等解析生效后，回到验证页面点击「开始验证」即可。</p>
<h3>3.3 文件验证</h3>
<p>如果不方便修改 DNS，也可以使用文件验证。在网站根目录放置下面这个纯文本文件：</p>
<pre><code class="language-text">bo-ke-verify-&lt;token&gt;.txt
</code></pre>
<p>文件内容就是 token 本身。验证页面会直接生成文件供您下载，上传到网站根目录后，确认下面这样的地址可以公开访问：</p>
<pre><code class="language-text">https://example.com/bo-ke-verify-&lt;token&gt;.txt
</code></pre>
<p><strong>如果您的博客开了 WAF、防火墙或反爬虫的话，记得放行带有 <code>bo.ke</code> 关键字的 User-Agent，不然验证请求可能直接被挡在门外。</strong></p>
<h3>3.4 验证记录不要删除</h3>
<p><strong>无论选择哪一种方式，验证通过后都不要删除 DNS 记录或验证文件哦。</strong></p>
<p>系统每 7 天重新检查一次。连续 3 次找不到验证记录，博客就会和账号解除关联。这个设计主要是防止域名转手以后，旧站长还能修改新站的资料。</p>
<p>所以不要在验证成功后顺手删掉记录，不然三个星期后还得重新认领。</p>
<h2>4、认领已经存在的博客</h2>
<p>以前 <code>peng.you</code> 里的数据有一部分导入了新站，管理员偶尔也会手工添加博客。这些条目还没有绑定账号，页面上会显示「认领此博客」。</p>
<p>如果发现自己的博客已经在里面，点击认领，再走一遍域名验证。通过后条目就会归到您的账号名下，以后可以自己修改资料。</p>
<h2>5、互链不是门槛，但会加权</h2>
<p>您可以在博客首页、友链页面或者任意内页，放一个指向 <a href="https://bo.ke/">https://bo.ke/</a> 的链接。</p>
<p>互链不是收录条件，不放也能正常提交。系统会定期检查，找到链接后会提高博客的展示权重。</p>
<p>可以参考我们的<a href="https://u.sb/links/">友情链接</a>页面，里面也放了博客大联盟。</p>
<h2>6、收录以后能做什么</h2>
<p>审核通过后，博客会出现在目录和标签页里，也可以按照名称、域名或作者搜索。博客名片下面会显示最新一篇文章。</p>
<p>如果填写了 RSS / Atom 地址，系统会每 6 小时抓取一次，每个博客保留最近 20 篇。全站文章也有提供 <a href="https://bo.ke/rss.xml">RSS</a> 和 <a href="https://bo.ke/atom.xml">Atom</a> 订阅，丢进任何一个支持 RSS 的阅读器就能订阅。您也可以<a href="https://bo.ke/subscribe/">定制自己的专属订阅</a>，屏蔽不想看到的博客。</p>
<p>如果你博客的资料有变化可以直接在控制台编辑，不用重新审核。</p>
<p>如果不想使用这个平台，在控制台删除博客即可，博客资料、聚合文章和认领记录会一起清除。</p>
<h2>7、给喜欢折腾的人</h2>
<p>博客大联盟有一套<a href="https://bo.ke/api/">公开 JSON API</a>，可以读取博客列表、全站最新文章和单个博客的最近文章。接口不用认证，支持跨域，做展示页或小工具都够用。数据每 6 小时才更新一次，别按秒轮询哦。</p>
<p><a href="https://bo.ke/brand/">品牌资源页面</a>放了深浅两色的 Logo 和徽标，提供 SVG、PNG、WebP 格式。颜色也可以在页面上改，做友链图标时比较省事。</p>
<p>访问统计用的是自建 Plausible，没有统计 Cookie，也不做跨站跟踪。</p>
<h2>8、加入博客大联盟</h2>
<p>如果您也在写博客，欢迎来加入<a href="https://bo.ke/">博客大联盟</a>。</p>
<p>登录、填资料、验证域名，几分钟就能弄完，剩下的交给管理员审核。</p>
<p>如有问题可以随时<a href="https://bo.ke/contact/">联系我们</a>。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2026-07-11T12:39:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[回归初心，文字为王]]></title>
        <id>https://u.sb/writing-first/</id>
        <link href="https://u.sb/writing-first/"/>
        <updated>2026-07-07T02:00:00.000Z</updated>
        <summary type="html"><![CDATA[这篇文章从一次博客重构说起，反思这些年从 WordPress、静态博客到 AI vibe coding 的不断折腾。技术和工具当然能让博客更快、更漂亮，但它们终究只是载体。真正能留下来的，不是框架、封面图或动画，而是一个人真实写下的文字和思考。与其追逐每一个新工具，不如回归初心，慢慢写博客，让文字重新成为博客的核心。]]></summary>
        <content type="html"><![CDATA[<p>这篇文章从一次博客重构说起，反思这些年从 WordPress、静态博客到 AI vibe coding 的不断折腾。技术和工具当然能让博客更快、更漂亮，但它们终究只是载体。真正能留下来的，不是框架、封面图或动画，而是一个人真实写下的文字和思考。与其追逐每一个新工具，不如回归初心，慢慢写博客，让文字重新成为博客的核心。</p>
<h2>前言</h2>
<p>从大学开始算起，本人写博客也快 20 年了。</p>
<p>这 20 年里，我见过各种各样的博客站点，也见过不少小伙伴从入坑到弃坑，再入坑，再弃坑。有的人写博客，是为了记录技术和生活上的点滴；有的人写博客，是为了 SEO，提高排名，顺便推广自己的产品；有的人写博客，是为了赚钱；更多的人，可能和我一样，纯属折腾。</p>
<p>从折腾 WordPress 主题和插件，到折腾静态化 HTML 博客程序，再到如今用 AI vibe coding 定制自己的博客系统，工具一代一代换，框架一轮一轮火。等夜深人静的时候，你大概也应该停下来问问自己：写博客的目的到底是什么？</p>
<p>是为了把网站做得更炫吗？是为了用上最新的框架吗？是为了让 Lighthouse 跑满分吗？还是为了把脑子里那些乱七八糟的想法，尽量诚实地留下来？</p>
<h2>折腾</h2>
<p>前段时间，我又开始折腾自己的博客首页。</p>
<p>再加上最近 AI 作图流行，我还给每篇文章都做了统一风格的封面图。刚开始看着还挺像那么回事，时间一久，越看越不顺眼。</p>
<p>一个好端端的分享技术的博客网站，要那么多图片干嘛？</p>
<p>更何况我又没什么艺术细胞，AI 的确能作图，但没艺术细胞的人用 AI 做出来的图，基本上一眼也能看出是 AI 做的。它可能很精致，也可能很「正确」，但就是少了点人味。甚至有些时候，那些图并不是在帮文章表达什么，而是在证明我也跟上了某种潮流。</p>
<p>于是趁着这个周末，我又一次折腾了自己的博客。</p>
<p>这次从 <a href="https://nextjs.org/">Next.js</a> 换到了构建速度更快、也更适合纯静态内容的 <a href="https://astro.build/">Astro</a>。同时，我撤掉了所有文章封面图，OG 图片也改成由程序自动生成。至于 SEO、文章内页 ToC 这些东西，交给 AI 来处理就行了，不再让它们喧宾夺主。</p>
<p>最后选定的架构大概如下：</p>
<table>
<thead>
<tr>
<th>层</th>
<th>选型</th>
<th>版本</th>
</tr>
</thead>
<tbody><tr>
<td>运行时 / 包管理</td>
<td>Bun</td>
<td>1</td>
</tr>
<tr>
<td>框架</td>
<td>Astro（纯静态输出，零运行时框架）</td>
<td>7</td>
</tr>
<tr>
<td>样式</td>
<td>Tailwind CSS（<code>@tailwindcss/vite</code> 插件，CSS-first 配置）</td>
<td>4</td>
</tr>
<tr>
<td>内容格式</td>
<td>MDX（<code>@astrojs/mdx</code>，<code>optimize: true</code>）</td>
<td>7</td>
</tr>
<tr>
<td>交互</td>
<td>原生 JS（<code>&lt;script&gt;</code> 块，无 React/Vue 运行时）</td>
<td>-</td>
</tr>
</tbody></table>
<p>放在以前，这种事情不折腾几个星期大概是做不完的。现在有了 AI，一晚上就能搞定。</p>
<p>这当然很爽。</p>
<p>但爽完以后，我又不得不问自己：这么折腾来折腾去，目的到底是什么？</p>
<p>是为了体验最新科技？是为了追逐更快的 build 速度？是为了证明自己还没有被前端时代抛弃？还是只是因为我们这些老网民，总觉得网站不改点什么就浑身难受？</p>
<p>技术当然重要。一个轻量、快速、结构清晰的博客系统，可以让写作这件事变得更舒服。但技术终究只是水管，不是水本身。框架再新，页面再快，动画再丝滑，如果文章本身没有内容，那也只是一个加载速度很快的空壳。</p>
<p>博客最重要的东西，始终不是首页长什么样，不是封面图够不够统一，也不是用了哪个新框架，而是文字本身有没有留下你的思考。</p>
<h2>文字</h2>
<p>现在的互联网越来越像信息流水线。</p>
<p>社交平台追求即时反馈，短视频追求几秒钟的刺激，AI 负责把内容变得更快、更满、更像样。每个人都可以很快生成一篇文章、一张图、一个视频，甚至一个看起来完整的个人网站。</p>
<p>但也正因为如此，真正稀缺的东西反而变了。</p>
<p>以前稀缺的是发布能力。你得会建站，会写 HTML，会折腾服务器，才能拥有自己的一个小角落。现在稀缺的不是发布，而是判断；不是表达能力，而是诚实表达；不是内容数量，而是你到底有没有真正想过。</p>
<p>AI 可以帮你写得更顺，帮你修语法，帮你生成摘要，甚至帮你做图。但 AI 不能替你活过那些日子，也不能替你在某个深夜突然想明白一件小事。</p>
<p>博客的价值，恰恰在这里。</p>
<p>它不一定要服务算法，也不一定要讨好读者。它可以很慢，可以很个人，可以有点啰嗦，甚至可以有点笨。它像是你在互联网上留给自己的一个锚点：过了几年再回头看，你会知道当时的自己在想什么，关心什么，又为什么要这样折腾。</p>
<p>这也是我越来越觉得，博客不应该被做成一个小型媒体站，也不一定非要变成作品集、流量入口或者商业落地页。</p>
<p>博客首先应该是一个人说话的地方。</p>
<h2>总结</h2>
<p>我们这些互联网老人的折腾精力和速度，已经越来越跟不上科技的发展了。</p>
<p>现在 AI 大爆发，几乎每周，甚至每天都有新的模型、新的工具、新的框架、新的工作流冒出来。你当然可以一直追，也可以一直改，一直重构，一直把自己的博客当成前端实验田。</p>
<p>但到最后，真正能留下来的，大概率还是那些你亲手写下来的文字。</p>
<p>所以，就让我们回归初心吧。</p>
<p>少一点花里胡哨的 JS、CSS 动画和 AI 作图，少一点为了折腾而折腾的冲动，多一点安静写字的耐心。</p>
<p>毕竟，博客不是展示工具链的地方，而是留下思考的地方。</p>
<p>文字，才是自己真正的原创。</p>
<p>不是嘛？</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2026-07-07T02:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ubuntu 24.04 Noble 升级 Ubuntu 26.04 Resolute]]></title>
        <id>https://u.sb/ubuntu-upgrade-2604/</id>
        <link href="https://u.sb/ubuntu-upgrade-2604/"/>
        <updated>2026-05-19T05:55:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何升级 Ubuntu 24.04 Noble Numbat 到 Ubuntu 26.04 Resolute Raccoon。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何升级 Ubuntu 24.04 Noble Numbat 到 Ubuntu 26.04 Resolute Raccoon。</p>
<p>相关教程：<a href="https://u.sb/ubuntu-upgrade-2404/">Ubuntu 22.04 Jammy 升级 Ubuntu 24.04 Noble</a>。</p>
<h2>准备工作</h2>
<p>除非你是物理服务器，以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机，否则升级大版本更新内核是有一定机率导致 Grub 加载失败的，切记备份重要数据！</p>
<p><em>OpenVZ 和 LXC 构架的 VPS 是无法升级的，因为他们没有自己独立的内核</em></p>
<p>再强调一遍，一定要备份重要数据！</p>
<p>Ubuntu 26.04 LTS 已经正式发布，不过 Ubuntu LTS 版本的自动升级提示通常会等到第一个小版本发布后才会开放。如果你在 Ubuntu 26.04.1 LTS 发布前从 Ubuntu 24.04 LTS 升级，<code>do-release-upgrade</code> 需要加上 <code>-d</code> 参数。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>更新系统</h2>
<p>首先需要更新你当前的系统</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>如果内核更新了，可以重启让最新的内核生效，也可以直接进行升级。</p>
<h2>升级系统</h2>
<p>这里有两种升级系统的方法，第一种是使用 <code>do-release-upgrade</code> 命令，第二种是手动更新 <code>apt</code> 源文件。</p>
<h3>方法一：使用 <code>do-release-upgrade</code> 命令</h3>
<p>首先需要安装 <code>ubuntu-release-upgrader-core</code> 包：</p>
<pre><code class="language-bash">sudo apt install ubuntu-release-upgrader-core
</code></pre>
<p>然后修改 <code>/etc/update-manager/release-upgrades</code> 文件，确保 <code>Prompt</code> 值为 <code>lts</code>：</p>
<pre><code class="language-bash">cat /etc/update-manager/release-upgrades | grep lts
</code></pre>
<p>显示如下内容即可：</p>
<pre><code class="language-bash">root@ubuntu ~ # cat /etc/update-manager/release-upgrades | grep lts
#  lts    - Check to see if a new LTS release is available.  The upgrader
Prompt=lts
</code></pre>
<p>最后执行以下命令升级系统：</p>
<pre><code class="language-bash">sudo do-release-upgrade -d
</code></pre>
<p>等 Ubuntu 26.04.1 LTS 发布并开放 LTS 自动升级后，可以直接使用：</p>
<pre><code class="language-bash">sudo do-release-upgrade
</code></pre>
<h3>方法二：手动更新 <code>apt</code> 源文件</h3>
<p>Ubuntu 24.04 的默认软件源配置文件已经变更为 <code>DEB822</code> 格式，路径为 <code>/etc/apt/sources.list.d/ubuntu.sources</code>。我们可以直接替换 <code>noble</code> 为 <code>resolute</code>：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/noble/resolute/g&#39; /etc/apt/sources.list.d/ubuntu.sources
</code></pre>
<p>如果你的系统里仍然有传统 One-Line-Style 的源文件，也可以一并替换：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/noble/resolute/g&#39; /etc/apt/sources.list
sudo sed -i &#39;s/noble/resolute/g&#39; /etc/apt/sources.list.d/*.list
</code></pre>
<p>如果没有对应文件会提示诸如 <code>sed: can&#39;t read /etc/apt/sources.list: No such file or directory</code> 的错误，忽略即可。</p>
<p>或者直接一行命令：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/noble/resolute/g&#39; /etc/apt/sources.list /etc/apt/sources.list.d/*.{list,sources} 2&gt;/dev/null
</code></pre>
<p>使用 <code>DEB822</code> 格式的源文件 <code>/etc/apt/sources.list.d/ubuntu.sources</code> 应该是类似这样的：</p>
<pre><code class="language-bash">Types: deb
URIs: https://archive.ubuntu.com/ubuntu
Suites: resolute resolute-updates resolute-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: resolute-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
</code></pre>
<p><em>国内服务器可以替换 <code>archive.ubuntu.com</code> 和 <code>security.ubuntu.com</code> 为 <code>mirrors.tuna.tsinghua.edu.cn</code></em></p>
<p>如果你有第三方源或 PPA，建议先备份并根据情况临时禁用，升级完成后再确认是否支持 Ubuntu 26.04：</p>
<pre><code class="language-bash">sudo mkdir -p /root/apt-sources-backup
sudo cp -a /etc/apt/sources.list.d /root/apt-sources-backup/
</code></pre>
<p>第三方源通常需要单独检查，不能简单把 <code>noble</code> 替换成 <code>resolute</code>。</p>
<p>然后我们再次执行更新系统：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
</code></pre>
<p><em>更新过程中会提示一些软件是否需要自动重启，选 Yes 即可，以及一些软件的配置文件是否需要更新，按照自己的情况选择即可，默认回车即视为使用旧的配置文件，一般会出现在 OpenSSH 等软件的更新上。</em></p>
<p>更新后删除不必要的软件和依赖：</p>
<pre><code class="language-bash">sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>然后我们使用 <code>sudo reboot</code> 命令重启系统，耐心等待后，查看最新的系统版本：</p>
<pre><code class="language-bash">root@ubuntu ~ # lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 26.04 LTS
Release:	26.04
Codename:	resolute
</code></pre>
<pre><code class="language-bash">root@ubuntu ~ # uname -a
Linux Nana 7.0.0-15-generic #15-Ubuntu SMP PREEMPT_DYNAMIC Wed Apr 22 16:06:43 UTC 2026 x86_64 GNU/Linux
</code></pre>
<p>这时我们就已经更新到了最新的 Ubuntu 26.04 Resolute 和内核了。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2026-05-19T19:12:04.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[WSL 2 使用 Docker 桥接模式网络访问 HTTPS 超时的解决方法]]></title>
        <id>https://u.sb/fix-wsl-2-docker-network/</id>
        <link href="https://u.sb/fix-wsl-2-docker-network/"/>
        <updated>2026-01-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文介绍在 WSL 2 中使用 Docker 的桥接模式（bridge network）访问 HTTPS 时出现超时问题的解决方法。]]></summary>
        <content type="html"><![CDATA[<p>本文介绍在 WSL 2 中使用 Docker 的桥接模式（bridge network）访问 HTTPS 时出现超时问题的解决方法。</p>
<p>最近一直在 Windows 下使用基于 WSL 2 的 Debian 进行开发。许多场景下需要使用 Docker 构建镜像，但过程中遇到一个奇怪的问题，在 Docker 容器内部访问 HTTP 站点时一切正常：</p>
<pre><code class="language-bash">$ docker run --rm curlimages/curl time curl -s http://ip.gs
192.0.2.2
real    0m 0.02s
user    0m 0.00s
sys     0m 0.00s
</code></pre>
<p>一旦切换为 HTTPS，访问就会明显变慢，并且有一定概率出现超时：</p>
<pre><code class="language-bash">$ docker run --rm curlimages/curl time curl -s https://ip.gs
192.0.2.2
real    0m 6.52s
user    0m 0.00s
sys     0m 0.00s
</code></pre>
<p>然而，如果将 Docker 切换到 Host 模式（<code>--network=host</code>），访问又恢复正常：</p>
<pre><code class="language-bash">$ docker run --rm --network=host curlimages/curl time curl -s https://ip.gs
192.2.0.2
real    0m 0.28s
user    0m 0.00s
sys     0m 0.00s
</code></pre>
<p>在排查了 WSL 2 的 Debian 系统配置后，最终请教 ChatGPT 得到结论：</p>
<blockquote>
<blockquote>
<p>Path MTU 与防火墙流量检查（inspection）不兼容导致问题</p>
</blockquote>
</blockquote>
<p>解决方法很简单：将 WSL 2 虚拟网络的 MTU 下调为 <code>1400</code> 即可。</p>
<h2>1、添加 Docker 配置</h2>
<p>在 WSL 2 的系统里，使用 root 权限（<code>sudo</code>）修改 <code>/etc/docker/daemon.json</code> 文件：</p>
<pre><code class="language-json">{
    &quot;log-driver&quot;: &quot;json-file&quot;,
    &quot;log-opts&quot;: {
        &quot;max-size&quot;: &quot;20m&quot;,
        &quot;max-file&quot;: &quot;3&quot;
    },
    &quot;mtu&quot;: 1400,
    &quot;dns&quot;: [
      &quot;8.8.8.8&quot;,
      &quot;1.1.1.1&quot;
    ]
}
</code></pre>
<p>其中 <code>&quot;mtu&quot;: 1400,</code> 是关键配置。</p>
<p>然后在 Windows 下把 WSL 2 关闭：</p>
<pre><code class="language-powershell">wsl --shutdown
</code></pre>
<h2>2、修改 WSL 2 网卡配置</h2>
<p>在 Windows 下使用管理员身份运行 Powershell，检查网卡名称：</p>
<pre><code class="language-powershell">PS C:\Users\showfom&gt; Get-NetAdapter | Where-Object { $_.Name -like &quot;*WSL*&quot; }

Name                      InterfaceDescription                    ifIndex Status       MacAddress             LinkSpeed
----                      --------------------                    ------- ------       ----------             ---------
vEthernet (WSL (Hyper-V … Hyper-V Virtual Ethernet Adapter #3          41 Up           12-34-56-78-AB-CD        10 Gbps

PS C:\Users\showfom&gt; Get-NetAdapter | Format-Table -AutoSize

Name                               InterfaceDescription                      ifIndex Status       MacAddress        Lin
                                                                                                                    kSp
                                                                                                                    eed
----                               --------------------                      ------- ------       ----------        ---
vEthernet (Default Switch)         Hyper-V Virtual Ethernet Adapter               35 Up           12-34-56-78-AB-AB …ps
vEthernet (WSL (Hyper-V firewall)) Hyper-V Virtual Ethernet Adapter #3            41 Up           12-34-56-78-AB-CD …ps
</code></pre>
<p>输出可以看到完整名称为 <code>vEthernet (WSL (Hyper-V firewall))</code>，然后为其设置 MTU：</p>
<pre><code class="language-powershell">netsh interface ipv4 set subinterface &quot;vEthernet (WSL (Hyper-V firewall))&quot; mtu=1400 store=persistent
</code></pre>
<p>出现 <code>Ok.</code> 字样即代表设置成功。</p>
<h2>3、测试 WSL 2 中的 Docker 网络</h2>
<p>然后重新运行 WSL 2 虚拟机：</p>
<pre><code class="language-powershell">wsl -d debian
</code></pre>
<p>再次测试：</p>
<pre><code class="language-bash">$ docker run --rm curlimages/curl time curl -s https://ip.gs
192.0.2.2
real    0m 0.28s
user    0m 0.00s
sys     0m 0.00s
</code></pre>
<p>问题已完美解决，可以继续愉快地 Vibe Coding 了！ <em>★,°</em>:.☆(￣▽￣)/$:<em>.°★</em> 。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2026-01-21T04:53:46.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 下使用 nginx-acme 自动签发并配置 SSL 证书]]></title>
        <id>https://u.sb/debian-nginx-acme/</id>
        <link href="https://u.sb/debian-nginx-acme/"/>
        <updated>2026-07-07T01:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将介绍在 Debian 或 Ubuntu 下使用 nginx-acme 自动签发并配置 SSL 证书的方法。]]></summary>
        <content type="html"><![CDATA[<p>本文将介绍在 Debian 或 Ubuntu 下使用 nginx-acme 自动签发并配置 SSL 证书的方法。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>1、什么是 nginx-acme</h2>
<p><a href="https://github.com/nginx/nginx-acme">nginx-acme</a> 是 Nginx 官方开发的基于 ACME 协议自动签发 SSL 证书的模块，隔壁 Caddy 都出了几百年的功能， Nginx 也终于赶上了。</p>
<p>和 Nginx 使用 C 语言开发不同，这个模块是用 Rust 语言开发的，这里就不做评价。</p>
<p>这个模块支持 <a href="https://datatracker.ietf.org/doc/html/rfc8555">RFC8555</a>、<a href="https://datatracker.ietf.org/doc/html/rfc8737">RFC8737</a>、<a href="https://datatracker.ietf.org/doc/html/rfc8738">RFC8738</a> 和 <a href="https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/">draft-ietf-acme-profiles</a> 等规范，目前我实际测试下来已经基本可以用于生产环境。</p>
<h2>2、安装 N.WTF</h2>
<p>这里我们使用<a href="https://u.sb/">烧饼博客</a>打包的 <a href="https://n.wtf/">N.WTF</a>，这个项目已经集成了 <code>nginx-acme</code> 模块，可以做到开箱即用。</p>
<p>首先，安装一些必要的软件包：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates
</code></pre>
<p>然后加入 N.WTF 的 GPG 公钥和 apt 源：</p>
<pre><code class="language-bash">curl -sSL https://n.wtf/public.key | sudo bash -c &#39;gpg --dearmor &gt; /usr/share/keyrings/n.wtf.gpg&#39;
sudo bash -c &#39;echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror-cdn.xtom.com/sb/nginx/ $(lsb_release -sc) main&quot; &gt; /etc/apt/sources.list.d/n.wtf.list&#39;
</code></pre>
<p>国内机器可以用<a href="https://mirrors.tuna.tsinghua.edu.cn/">清华 TUNA</a> 的国内源：</p>
<pre><code class="language-bash">curl -sSL https://mirrors.tuna.tsinghua.edu.cn/n.wtf/public.key | sudo bash -c &#39;gpg --dearmor &gt; /usr/share/keyrings/n.wtf.gpg&#39;
sudo bash -c &#39;echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirrors.tuna.tsinghua.edu.cn/n.wtf/ $(lsb_release -sc) main&quot; &gt; /etc/apt/sources.list.d/n.wtf.list&#39;
</code></pre>
<p>Debian 下也可以直接使用 <a href="https://u.sb/debian-extrepo/">extrepo</a>:</p>
<pre><code class="language-bash">sudo apt update
sudo apt install extrepo -y
sudo extrepo enable n.wtf
</code></pre>
<p>接着更新系统并安装 Nginx：</p>
<pre><code class="language-bash">sudo apt update
sudo apt install nginx-extras -y
</code></pre>
<h2>3、nginx-acme 准备工作</h2>
<p>准备工作很简单，我们需要建立一个目录来存放 SSL 证书并给予正确的权限，这里我们以 <code>/var/cache/nginx/letsencrypt</code> 为演示：</p>
<pre><code class="language-bash">sudo mkdir -p /var/cache/nginx
sudo mkdir -p /var/cache/nginx/letsencrypt
sudo chown 33:33 /var/cache/nginx -R
</code></pre>
<p>然后别忘了把域名解析到你的服务器哦！</p>
<h2>4、配置 Nginx 站点</h2>
<p>我们以 <code>example.com</code> 为例，假设你的邮箱是 <code>user@example.com</code>，需要配置的域名是 <code>example.com</code> 和 <code>www.example.com</code>，并且希望访问 <code>www.example.com</code> 跳转到 <code>example.com</code>：</p>
<p>直接修改 <code>/etc/nginx/sites-enabled/default</code> 文件：</p>
<pre><code class="language-nginx">resolver 8.8.8.8:53 ipv6=off valid=5s;

acme_issuer letsencrypt {
    uri         https://acme-v02.api.letsencrypt.org/directory;
    contact     user@example.com;
    state_path  /var/cache/nginx/letsencrypt;
    accept_terms_of_service;
    ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
    ssl_verify on;
}

acme_shared_zone zone=ngx_acme_shared:1M;

server {
    # Listen on port 80 for all IPv4 and IPv6 addresses
    listen 80 default_server;
    listen [::]:80 default_server;
   
    # Match all domain names
    server_name _;

    location /.well-known/ {
        return 404;
    }

    location / {
        # Redirect all other HTTP requests to HTTPS using 301 permanent redirect
        return 301 https://$host$request_uri;
    }
}

server {
    # Standard TLS listening
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    # HTTP/2 protocol support
    http2 on;
   
    # HTTP/3 QUIC protocol support
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    add_header Alt-Svc &#39;h3=&quot;:443&quot;; ma=86400&#39; always;
    add_header X-Protocol $server_protocol always;

    server_name example.com;
    root /var/www/html;
    index index.html;

    # modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    acme_certificate letsencrypt;

    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;

    # do not parse the certificate on each request
    ssl_certificate_cache max=2;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    http2 on;

    listen 443 quic;
    listen [::]:443 quic;

    add_header Alt-Svc &#39;h3=&quot;:443&quot;; ma=86400&#39; always;
    add_header X-Protocol $server_protocol always;

    server_name www.example.com;
    return 301 https://example.com$request_uri;

    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    acme_certificate letsencrypt;

    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;

    # do not parse the certificate on each request
    ssl_certificate_cache max=2;
}
</code></pre>
<p>然后验证 Nginx 配置并重新加载：</p>
<pre><code class="language-bash">sudo nginx -t
sudo nginx -s reload
</code></pre>
<p>此时，在默认的 Nginx 日志文件 <code>/var/log/nginx/access.log</code> 中可以看到 Let&#39;s Encrypt 验证服务器的请求记录：</p>
<pre><code class="language-ini">23.178.112.210 - - [15/Jan/2026:16:08:18 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
34.212.137.78 - - [15/Jan/2026:16:08:18 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
18.222.179.58 - - [15/Jan/2026:16:08:18 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
16.171.19.61 - - [15/Jan/2026:16:08:18 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
13.228.72.222 - - [15/Jan/2026:16:08:18 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
2600:3000:2710:200::81 - - [15/Jan/2026:16:08:20 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
2600:1f14:804:fd02:f7fd:3c68:dec7:a062 - - [15/Jan/2026:16:08:20 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
2600:1f16:269:da00:c9ce:508c:ea69:9c2 - - [15/Jan/2026:16:08:20 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
2a05:d016:39f:3101:8b83:62b8:2603:d15d - - [15/Jan/2026:16:08:20 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
2406:da18:85:1401:1f69:967a:a988:cdc8 - - [15/Jan/2026:16:08:20 +0000] &quot;GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1&quot; 200 87 &quot;-&quot; &quot;Mozilla/5.0 (compatible; Let&#39;s Encrypt validation server; +https://www.letsencrypt.org)&quot;
</code></pre>
<p>等待数秒后即可访问 <code>https://example.com/</code> 了。</p>
<p>如果要给 IP 地址签发证书，则需要在 <code>acme_issuer letsencrypt {}</code> 段里添加一行 <code>profile shortlived;</code>，并且 <code>server_name</code> 必须写入完整的 IP 地址，不能直接用 <code>server_name _</code> 哦。</p>
<p><del>不过目前 nginx-acme 最新版本 0.3.1 签发 IP 证书还是会失败，开发版已经修复，应该需要等下一个版本才能使用。</del></p>
<p>目前最新版本已经支持 IP 地址证书的签发，可以在 <code>accept_terms_of_service;</code> 下方加入一条 <code>profile shortlived;</code> 并且自行修改 <code>server_name example.com;</code> 为你的 IP 地址，比如 <code>server_name 192.0.2.2 2001:db8::2;</code> 即可。</p>
<p>读者们在使用过程中如果遇到问题，可以在 V2EX 交流讨论：</p>
<p><a href="https://be.st/Mv7j">https://be.st/Mv7j</a></p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2026-01-16T03:05:21.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 acme.sh 配置 Let's Encrypt 签发的 IP 地址 SSL 证书]]></title>
        <id>https://u.sb/acme-sh-ip-ssl/</id>
        <link href="https://u.sb/acme-sh-ip-ssl/"/>
        <updated>2025-12-17T01:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将介绍使用 acme.sh 配置 Let's Encrypt 为 IP 地址签发 SSL 证书。]]></summary>
        <content type="html"><![CDATA[<p>本文将介绍使用 acme.sh 配置 Let&#39;s Encrypt 为 IP 地址签发 SSL 证书。</p>
<p>acme.sh 本身不强制 root 运行（<a href="https://github.com/acmesh-official/acme.sh/wiki/How-to-install#4-advanced-installation">官方</a>也写了 &quot;You don&#39;t have to be root then, although it is recommended&quot;），但本文的证书需要写入 <code>/etc</code> 且 <code>--reloadcmd</code> 需要重载服务，均需要 root 权限，所以建议先使用 <code>sudo -i</code> 切换到 root 用户再执行本文命令。</p>
<p>之前写过一篇使用 <a href="https://github.com/acmesh-official/acme.sh">acme.sh</a> 签发证书的<a href="https://u.sb/acme-sh-ssl/">教程</a>，但在很长一段时间里，Let&#39;s Encrypt 只能给<strong>域名</strong>签发证书。  </p>
<p>经过几个月的<a href="https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate">测试</a>之后，现在终于可以对 <strong>IP 地址</strong> 下手了。</p>
<h2>为什么要给 IP 签发证书</h2>
<p>在很多场景下，我们并不一定需要域名，但<strong>确实需要 HTTPS</strong>。比如：</p>
<ol>
<li>DNS over HTTPS（DoH）服务无需依赖域名解析</li>
</ol>
<p>直接通过 IP 提供 DoH 服务，避免「为了安全先做一次不安全的域名解析」这种哲学问题。</p>
<ol start="2">
<li>Web 服务默认站点隐藏真实域名</li>
</ol>
<p>默认站点只暴露 IP，不暴露真实域名，顺便还能挡掉一部分不太礼貌的爬虫。</p>
<ol start="3">
<li>临时服务或测试环境</li>
</ol>
<p>临时起个服务，只想加个锁，不想再去 DNS 那边折腾。</p>
<ol start="4">
<li>避免证书透明日志（Certificate Transparency Log）暴露域名，保护隐私</li>
</ol>
<p>有些域名不太想出现在公开日志里，低调一点总是好的。</p>
<h2>准备工作</h2>
<p>首先更新 acme.sh 到最新版本：</p>
<pre><code class="language-bash">acme.sh --upgrade
</code></pre>
<p>因为 IP 证书目前只能通过 <a href="https://letsencrypt.org/docs/challenge-types/#http-01-challenge">http-01</a> 和 <a href="https://letsencrypt.org/docs/challenge-types/#tls-alpn-01">tls-alpn-01</a> 方式进行验证，所以你需要检查服务器的防火墙，设置允许 TCP 80 和 TCP / UDP 443 端口在公网可以访问。</p>
<h2>配置 Nginx 80 端口的默认站点</h2>
<p>这里我只介绍在 Nginx 下的配置吧，我们可以直接写入 80 端口的默认配置：</p>
<p>如果你在 Debian 或 Ubuntu 下安装 Nginx，可以直接覆盖 <code>/etc/nginx/sites-available/default</code> 文件：</p>
<pre><code class="language-nginx">server {
    # Listen on port 80 for all IPv4 and IPv6 addresses
    listen 80 default_server;
    listen [::]:80 default_server;
   
    # Match all domain names
    server_name _;

    # Merge Let&#39;s Encrypt and SSL verification path configuration
    location ~ ^/.well-known/(acme-challenge|pki-validation)/ {
        add_header Content-Type text/plain;
        root /var/www/letsencrypt;
    }

    # Redirect all other HTTP requests to HTTPS using 301 permanent redirect
    location / {
        return 301 https://$host$request_uri;
    }
}
</code></pre>
<p>然后创建两个目录并重新加载 Nginx：</p>
<pre><code class="language-bash">mkdir -p /var/www/letsencrypt
mkdir -p /etc/nginx/ssl
nginx -t
nginx -s reload
</code></pre>
<h2>使用 acme.sh 签发 IP 证书</h2>
<p>假设你服务器的 IP 地址是 <code>192.0.2.2</code> 和 <code>2001:db8::2</code>：</p>
<pre><code class="language-bash">acme.sh --issue --server letsencrypt -d 192.0.2.2 -d 2001:db8::2 \
  -w /var/www/letsencrypt \
  --certificate-profile shortlived \
  --days 3
</code></pre>
<p>注意这里我们必须使用 <a href="https://letsencrypt.org/docs/profiles/#shortlived">shortlived</a> 这个 Profile，因为 Let&#39;s Encrypt 的 IP 证书有效期只有 6.66666 天（160 小时），同时 acme.sh 需要更短的时间来进行检查更新证书，所以可以设置 <code>--days 3</code> 参数，让它 3 天检查并更新一次，你也可以设置 4 或 5，但是不要设置 6，否则可能证书过期了都没更新哦。</p>
<p>执行命令以后会看到类似的申请成功返回：</p>
<pre><code>[Wed Dec 17 05:46:28 AM UTC 2025] Using CA: https://acme-v02.api.letsencrypt.org/directory
[Wed Dec 17 05:46:28 AM UTC 2025] Multi domain=&#39;IP:192.0.2.2,IP:2001:db8::2&#39;
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain=&#39;192.0.2.2&#39;
[Wed Dec 17 05:46:30 AM UTC 2025] Getting webroot for domain=&#39;2001:db8::2&#39;
[Wed Dec 17 05:46:30 AM UTC 2025] Verifying: 192.0.2.2
[Wed Dec 17 05:46:31 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:34 AM UTC 2025] Success
[Wed Dec 17 05:46:34 AM UTC 2025] Verifying: 2001:db8::2
[Wed Dec 17 05:46:35 AM UTC 2025] Pending. The CA is processing your order, please wait. (1/30)
[Wed Dec 17 05:46:38 AM UTC 2025] Success
[Wed Dec 17 05:46:38 AM UTC 2025] Verification finished, beginning signing.
[Wed Dec 17 05:46:38 AM UTC 2025] Let&#39;s finalize the order.
[Wed Dec 17 05:46:38 AM UTC 2025] Le_OrderFinalize=&#39;https://acme-v02.api.letsencrypt.org/acme/finalize/blablablablablablablabla/blablablablablablablabla&#39;
[Wed Dec 17 05:46:41 AM UTC 2025] Downloading cert.
[Wed Dec 17 05:46:41 AM UTC 2025] Le_LinkCert=&#39;https://acme-v02.api.letsencrypt.org/acme/cert/blablablablablablablabla&#39;
[Wed Dec 17 05:46:42 AM UTC 2025] Cert success.
-----BEGIN CERTIFICATE-----
blablablablablablablablablablablablablablablablablablabla
-----END CERTIFICATE-----
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.cer
[Wed Dec 17 05:46:42 AM UTC 2025] Your cert key is in: /root/.acme.sh/192.0.2.2_ecc/192.0.2.2.key
[Wed Dec 17 05:46:42 AM UTC 2025] The intermediate CA cert is in: /root/.acme.sh/192.0.2.2_ecc/ca.cer
[Wed Dec 17 05:46:42 AM UTC 2025] And the full-chain cert is in: /root/.acme.sh/192.0.2.2_ecc/fullchain.cer
</code></pre>
<p>然后我们可以把申请好的证书放在 <code>/etc/nginx/ssl</code> 目录：</p>
<pre><code class="language-bash">mkdir -p /etc/nginx/ssl

acme.sh --install-cert -d 192.0.2.2 \
  --key-file       /etc/nginx/ssl/ip.key  \
  --fullchain-file /etc/nginx/ssl/ip.crt \
  --ca-file        /etc/nginx/ssl/ip.ca.crt \
  --reloadcmd     &quot;systemctl restart nginx&quot;
</code></pre>
<h2>配置 Nginx 443 端口的默认站点</h2>
<p>安装完证书后我们即可配置 Nginx 默认的 443 端口了，你可以把这段配置一起放入 <code>/etc/nginx/sites-available/default</code> 文件：</p>
<pre><code class="language-nginx"># HTTPS Server block - Handle all HTTPS requests
server {
    # Standard TLS listening
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    # HTTP/2 protocol support
    http2 on;
   
    # HTTP/3 QUIC protocol support
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    add_header Alt-Svc &#39;h3=&quot;:443&quot;; ma=86400&#39; always;
    add_header X-Protocol $server_protocol always;
   
    # Match all domain names
    server_name _;
    return 403;

    # modern configuration
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    ssl_certificate /etc/nginx/ssl/ip.crt;
    ssl_certificate_key /etc/nginx/ssl/ip.key;
}
</code></pre>
<p>然后检查并重新加载 Nginx：</p>
<pre><code class="language-bash">nginx -t
nginx -s reload
</code></pre>
<p>一切就绪以后就可以直接访问 <code>https://192.0.2.2/</code> 并返回 403 错误页面，我们可以看到证书里 <code>Subject Alt Names</code> 字段也显示 <code>IP Address</code> 了：</p>
<p><img src="https://s.bh.sb/2025/12/17/image_sK4sr.png" alt="证书详情中 Subject Alt Names 字段显示 IP Address"></p>
<p>读者们在使用过程中如果遇到问题，可以在 V2EX 交流讨论或在下方评论：</p>
<p><a href="https://be.st/pGx9">https://be.st/pGx9</a></p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2025-12-17T16:13:37.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[RDAP.SS - 基于 RDAP 协议的 Whois 查询网站]]></title>
        <id>https://u.sb/rdap-ss/</id>
        <link href="https://u.sb/rdap-ss/"/>
        <updated>2025-11-03T08:00:00.000Z</updated>
        <summary type="html"><![CDATA[RDAP.SS 是一个基于 RDAP 协议的 Whois 查询网站，本文介绍 RDAP 协议以及如何使用。]]></summary>
        <content type="html"><![CDATA[<p>RDAP.SS 是一个基于 RDAP 协议的 Whois 查询网站，本文介绍 RDAP 协议以及如何使用。</p>
<h2>1、什么是 RDAP 协议？</h2>
<p><a href="https://www.icann.org/en/contracted-parties/registry-operators/resources/registration-data-access-protocol">RDAP</a>，全称 Registration Data Access Protocol，由 IETF（互联网工程任务组）制定，主要用于查询以下注册信息：</p>
<ul>
<li>域名（Domain）</li>
<li>IP 地址（IPv4 / IPv6）</li>
<li>ASN（自治系统号）</li>
</ul>
<p>它定义在 RFC <a href="https://www.rfc-editor.org/rfc/rfc7480.txt">7480</a>、<a href="https://www.rfc-editor.org/rfc/rfc7481.txt">7481</a>、<a href="https://www.rfc-editor.org/rfc/rfc7482.txt">7482</a>、<a href="https://www.rfc-editor.org/rfc/rfc7483.txt">7483</a>、<a href="https://www.rfc-editor.org/rfc/rfc7484.txt">7484</a> 等系列标准中。</p>
<h2>2、RDAP 和 Whois 协议的区别</h2>
<p>以下是对两者主要区别的总结：</p>
<table>
<thead>
<tr>
<th>比较项目</th>
<th>WHOIS</th>
<th>RDAP</th>
</tr>
</thead>
<tbody><tr>
<td>传输协议</td>
<td>基于 TCP 文本协议</td>
<td>基于 HTTPS RESTful API</td>
</tr>
<tr>
<td>数据格式</td>
<td>纯文本、非结构化</td>
<td>JSON 格式、结构化</td>
</tr>
<tr>
<td>国际化支持</td>
<td>差，编码不统一</td>
<td>完全支持 UTF-8</td>
</tr>
<tr>
<td>安全性</td>
<td>无加密、无认证</td>
<td>支持 HTTPS、OAuth</td>
</tr>
<tr>
<td>分布式查询</td>
<td>依靠人工跳转</td>
<td>内置 <code>bootstrap</code> 机制，可自动重定向至正确注册局</td>
</tr>
<tr>
<td>标准化程度</td>
<td>各注册局格式不同</td>
<td>格式统一，易于机器读取与解析</td>
</tr>
</tbody></table>
<p>在传统的 Whois 协议中，用户需要给 Whois 服务器的 43 端口发送查询，然后 Whois 服务器返回纯文本的结果。</p>
<p>这导致了传统的 Whois 协议有几个无法修补的劣势：</p>
<ol>
<li>非结构化的文本输出，没有标准化的接口</li>
</ol>
<p>传统的 Whois 没有统一的查询和返回 API，只有简单的文本命令，这导致每家注册局返回的信息格式不一致，例如有的字段写作 <code>Registrant Email</code>，有的写作 <code>Contact Email</code>。这给开发者带来了巨大的解析工作量，需要为不同注册局甚至注册商编写对应的匹配规则。</p>
<ol start="2">
<li>非加密传输</li>
</ol>
<p>TCP 43 端口使用明文传输，任何中间节点（包括运营商）都能看到查询内容。🤷‍♂️ 这在 2025 年已经难以被接受。</p>
<ol start="3">
<li>缺乏访问控制权限</li>
</ol>
<p>Whois 服务器只能识别查询的 IP 地址，无法针对单个用户分配权限或进行身份区分，安全设置也只能基于 IP 层面。</p>
<ol start="4">
<li>管理混乱</li>
</ol>
<p>Whois 客户端需要为每个 TLD 手动配置对应的 Whois 服务器，缺乏类似 RDAP 的自动 <code>bootstrap</code> 机制。</p>
<h2>3、RDAP 协议的优势</h2>
<p>而 RDAP 协议完美的弥补了这些劣势：</p>
<ol>
<li>标准化的 JSON 输出</li>
</ol>
<p>RDAP 返回的数据是结构化的 JSON，字段统一定义，例如：</p>
<pre><code class="language-json">{
  &quot;objectClassName&quot;: &quot;domain&quot;,
  &quot;ldhName&quot;: &quot;example.com&quot;,
  &quot;status&quot;: [&quot;active&quot;],
  &quot;entities&quot;: [...]
}
</code></pre>
<p>这让程序能直接解析字段，无需依赖正则或人工格式识别。</p>
<ol start="2">
<li>统一的 API</li>
</ol>
<p>RDAP 基于 HTTP/HTTPS 的 RESTful API，支持 GET 请求、分页、过滤等现代查询方式。</p>
<p>例如：</p>
<pre><code class="language-bash">GET https://rdap.verisign.com/com/v1/domain/example.com
</code></pre>
<p>即可从注册局调用 <code>example.com</code> 的信息</p>
<ol start="3">
<li>Bootstrap 自动重定向</li>
</ol>
<p>RDAP 客户端无需知道具体注册商，它会根据 IANA 的 bootstrap 数据自动跳转到正确的注册局或 RIR</p>
<p>IANA 的 Bootstrap 数据是公开的，可在以下地址访问：</p>
<p><a href="https://data.iana.org/rdap/">https://data.iana.org/rdap/</a></p>
<p>按照 RDAP.org 的<a href="https://deployment.rdap.org/">统计</a>，目前大约有 77% 的注册局已经接入了 RDAP 协议，除了少部分 TLD 需要手工添加 RDAP 服务器，大部分已经都接入了 IANA 的 bootstrap 数据。</p>
<ol start="4">
<li>安全和隐私</li>
</ol>
<p>RDAP 强制使用 HTTPS，保障查询与返回内容的完整性与保密性。防止中间人攻击与数据监听。</p>
<p>如有需要，注册局还可以通过 OAuth 2.0 或 Token 认证实现访问控制，根据用户身份（如公众、注册商或执法机构）返回不同级别的信息。这非常契合 GDPR 等隐私法规的要求。</p>
<ol start="5">
<li>可扩展性</li>
</ol>
<p>RDAP 支持使用 extensions（扩展字段），注册局可以在标准字段外添加自定义信息，而不会破坏兼容性。</p>
<p>例如：</p>
<pre><code class="language-json">&quot;rdapConformance&quot;: [&quot;rdap_level_0&quot;, &quot;icann_rdap_technical_implementation_guide_0&quot;]
</code></pre>
<h2>4、RDAP.SS 网站</h2>
<p>基于 RDAP 的优势，我使用 Claude Code 开发了一个基于 RDAP 协议的 Whois 查询网站： <a href="https://rdap.ss/">RDAP.SS</a></p>
<p>技术栈：</p>
<ul>
<li>Next.js</li>
<li>Tailwind CSS</li>
<li>Redis 缓存</li>
</ul>
<p>目前支持如下格式的 Whois 查询：</p>
<ul>
<li>域名 - <a href="https://rdap.ss/whois/google.com">https://rdap.ss/whois/google.com</a></li>
<li>IPv4 - <a href="https://rdap.ss/whois/8.8.8.8">https://rdap.ss/whois/8.8.8.8</a></li>
<li>IPv4 CIDR - <a href="https://rdap.ss/whois/8.8.8.0/24">https://rdap.ss/whois/8.8.8.0/24</a></li>
<li>IPv6 - <a href="https://rdap.ss/whois/2001:4860:4860::8888">https://rdap.ss/whois/2001:4860:4860::8888</a></li>
<li>IPv6 CIDR - <a href="https://rdap.ss/whois/2001:4860::/32">https://rdap.ss/whois/2001:4860::/32</a></li>
<li>ASN - <a href="https://rdap.ss/whois/AS15169">https://rdap.ss/whois/AS15169</a></li>
</ul>
<p>仅当对应的 TLD 注册局支持 RDAP 协议时，域名查询才可用；未支持的注册局会降级为传统 Whois 协议返回结果。</p>
<p>如果在使用过程中遇到问题，请随时在 GitHub 提交 <a href="https://github.com/rdapss/rdap.ss/issues">issue</a>。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2025-11-03T17:01:40.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian 使用 extrepo 配置第三方软件源]]></title>
        <id>https://u.sb/debian-extrepo/</id>
        <link href="https://u.sb/debian-extrepo/"/>
        <updated>2025-10-20T20:45:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何在 Debian 下使用 extrepo 配置第三方软件源。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何在 Debian 下使用 extrepo 配置第三方软件源。</p>
<h2>什么是 extrepo？</h2>
<p><a href="https://packages.debian.org/stable/extrepo">extrepo</a> 用于管理 Debian 中的外部软件源。</p>
<p>在没有 extrepo 之前，想要使用未被 Debian 官方打包的软件，用户通常需要手动编写 APT 配置文件、以 root 身份运行未经签名的脚本，或安装一个包含所有系统配置的未签名 <code>.deb</code> 包。</p>
<p>遗憾的是，这些方法都不是很安全。</p>
<p>打个比方，我们要添加 Docker 的软件源，有三种方式。</p>
<p>第一种是传统的 <code>One-Line Style</code>：</p>
<pre><code class="language-bash">curl -sSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor &gt; /usr/share/keyrings/docker-ce.gpg

echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-ce.gpg] https://download.docker.com/linux/debian $(lsb_release -sc) stable&quot; | sudo bash -c &#39;cat &gt; /etc/apt/sources.list.d/docker-ce.list&#39;

sudo apt update
sudo install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
</code></pre>
<p>第二种是新的 <code>DEB822</code> 格式：</p>
<pre><code class="language-bash">curl -sSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor &gt; /usr/share/keyrings/docker-ce.gpg

sudo bash -c &#39;cat &gt; /etc/apt/sources.list.d/docker-ce.sources &lt;&lt; EOF
Components: stable
Architectures: $(dpkg --print-architecture)
Suites: $(lsb_release -cs)
Types: deb
Uris: https://download.docker.com/linux/debian
Signed-By: /usr/share/keyrings/docker-ce.gpg
EOF&#39;

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
</code></pre>
<p>第三种方法则更为简单粗暴，直接运行脚本：</p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
</code></pre>
<p>在以上这些方法中（包括第三种脚本方式），我们都需要手动下载并导入 GPG 密钥，创建必要的 APT 配置文件，更新软件列表，然后再安装软件。而我们推荐使用 extrepo 的话，只需要简单的三个命令即可完成整个过程。</p>
<h2>安装并使用 extrepo</h2>
<p>Debian Stable 下直接用如下命令安装 extrepo 即可：</p>
<pre><code class="language-bash">sudo apt update
sudo apt install extrepo -y
</code></pre>
<p>接着我们就可以启用比如 Docker CE 的仓库源：</p>
<pre><code class="language-bash">sudo extrepo enable docker-ce
</code></pre>
<p>然后更新系统并安装 Docker：</p>
<pre><code class="language-bash">sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y
</code></pre>
<p>此时我们会发现在 <code>/etc/apt/sources.list.d</code> 目录中多了一个 <code>extrepo_docker-ce.sources</code> 文件：</p>
<pre><code class="language-bash"># cat /etc/apt/sources.list.d/extrepo_docker-ce.sources 
Suites: trixie
Types: deb
Uris: https://download.docker.com/linux/debian
Components: stable
Architectures: amd64 arm64 armhf s390x ppc64el
Signed-By: /var/lib/extrepo/keys/docker-ce.asc
</code></pre>
<p>换句话说，extrepo 帮我们完成了以下工作：</p>
<ol>
<li>抓取经过验证的 GPG Key</li>
<li>使用 DEB822 格式写入 APT 配置</li>
</ol>
<p>对比之前的几种方法，你可能还需要满大街找配置命令，找脚本命令，现在只要敲几行命令就可以搞定，何乐而不为呢？</p>
<h2>External Repository Metadata</h2>
<p>看到这里读者可能有疑问，extrepo 的数据又是谁维护的呢？它是由 <a href="https://salsa.debian.org/extrepo-team">Debian External Repositories Team</a> 维护，成员主要为志愿者（包括本文作者），数据本身也有个仓库叫做 <a href="https://salsa.debian.org/extrepo-team/extrepo-data">extrepo-data</a>。</p>
<p>我个人维护了一些仓库，包括 <a href="https://salsa.debian.org/extrepo-team/extrepo-data/-/commits/master/repos/debian/redis.yaml?ref_type=heads">Redis</a>，<a href="https://salsa.debian.org/extrepo-team/extrepo-data/-/commits/master/repos/debian/mariadb.yaml?ref_type=heads">MariaDB</a>，<a href="https://salsa.debian.org/extrepo-team/extrepo-data/-/commits/master/repos/debian/n.wtf.yaml?ref_type=heads">N.WTF</a> 等，你可以从这里看到我的 <a href="https://salsa.debian.org/showfom/extrepo-data/-/commits/master?author=Xiufeng+Guo">commits</a>。</p>
<p>任何人都可以对这个 Git 仓库做出贡献，而且 YAML 语法也十分容易上手，你可以参考默认的<a href="https://salsa.debian.org/extrepo-team/extrepo-data/-/blob/master/template.yaml?ref_type=heads">模板文件</a>。</p>
<p>需要查看完整的第三方软件源列表的话，你可以在<a href="https://salsa.debian.org/extrepo-team/extrepo-data/-/tree/master/repos/debian?ref_type=heads">这里</a>浏览所有的第三方仓库的 <code>.yaml</code> 文件。</p>
<p>了解了 extrepo 的机制和优势后，我们再来总结一下。</p>
<h2>结论</h2>
<p>总之，使用 extrepo 是一种既安全又方便的添加第三方软件源的方法。</p>
<p>它的主要优点在于：</p>
<ol>
<li>可以让你快速启用经过测试和验证的软件源，而无需手动搜索和复制第三方软件源的配置信息。</li>
<li>当第三方仓库的 GPG 密钥过期或 URI 发生变化（这种情况相当常见）时，也不再需要手动更新。</li>
</ol>
<p>比如，使用 extrepo 时，只需运行 <code>extrepo enable docker-ce</code> 和 <code>extrepo update docker-ce</code>，它就会自动刷新 Docker CE 仓库的 GPG 密钥和 URI。</p>
<p>不过，extrepo 也有一定的缺点：</p>
<ol>
<li>目前只兼容 Debian 系统，Ubuntu 因为没有人维护 extrepo-data 所以基本没有可以用的第三方软件源。</li>
<li>默认的 <a href="https://extrepo-team.pages.debian.net/extrepo-data/">extrepo-data</a> 托管在 Debian 官方的 GitLab Pages，这对一些网络不通畅的服务器来说获取仓库信息就有点困难，而自建 extrepo-data 的过程相对繁琐，并不算方便。</li>
</ol>
<p><img src="https://s.bh.sb/2025/10/20/that-will-save-lots-of-time-alicia-rodriguez_BhSQa.gif" alt="That will save a lot of time"></p>
<p><a href="https://be.st/MIQr">English Version</a></p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2025-10-21T05:45:49.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[升级 Debian 后 GitLab PostgreSQL 无法启动的解决方法]]></title>
        <id>https://u.sb/fix-gitlab-postgresql-not-starting-debian-upgrade/</id>
        <link href="https://u.sb/fix-gitlab-postgresql-not-starting-debian-upgrade/"/>
        <updated>2025-09-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将介绍在升级 Debian 系统后，GitLab 的 PostgreSQL 数据库无法正常启动的问题。]]></summary>
        <content type="html"><![CDATA[<p>本文将介绍在升级 Debian 系统后，GitLab 的 PostgreSQL 数据库无法正常启动的问题。</p>
<h2>背景前提</h2>
<p><strong>本文的 GitLab 是基于官方源安装的，具体安装和升级方法限于篇幅，不再阐述。</strong></p>
<p>我们从 Debian 11 升级到 Debian 12 后，再更新 GitLab 会发现如下报错：</p>
<pre><code class="language-bash">gitlab [execute] WARNING: database &quot;postgres&quot; has a collation version mismatch DETAIL: The database was created using collation version 2.31, but the operating system provides version 2.36.
</code></pre>
<p>或者从 Debian 12 升级到 Debian 13 后更新 GitLab 也会有如下报错：</p>
<pre><code class="language-bash">WARNING:  database “gitlabhq_production” has a collation version mismatch
DETAIL:  The database was created using collation version 2.36, but the operating system provides version 2.41.
HINT:  Rebuild all objects in this database that use the default collation and run ALTER DATABASE gitlabhq_production REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.
</code></pre>
<p>我们以 Debian 13 为例，可以看到系统自带的 glibc 版本为 2.41：</p>
<pre><code class="language-bash"># ldd --version
ldd (Debian GLIBC 2.41-12) 2.41
</code></pre>
<p>而 Debian 12 自带的 glibc 版本为 2.36：</p>
<pre><code class="language-bash"># ldd --version
ldd (Debian GLIBC 2.36-9+deb12u13) 2.36
</code></pre>
<p>Debian 11 的 glibc 版本为 2.31：</p>
<pre><code class="language-bash"># ldd --version
ldd (Debian GLIBC 2.31-13+deb11u13) 2.31
</code></pre>
<p>所以我们直接升级系统以后 GitLab 的 PostgreSQL 数据库仍使用旧版本的排序规则，导致版本不匹配，所以 GitLab 升级会失败。</p>
<h2>解决方法</h2>
<p>我们以 Debian 11 升级到 Debian 12 后的情况举例，首先备份一下 GitLab：</p>
<pre><code class="language-bash">sudo gitlab-backup create
</code></pre>
<p>然后我们进入 GitLab 的 PostgreSQL 控制台：</p>
<pre><code class="language-bash">sudo gitlab-psql
</code></pre>
<p>接着重新建立索引并修复 <code>gitlabhq_production</code> 数据库：</p>
<pre><code class="language-sql">SET statement_timeout = 0;
REINDEX DATABASE gitlabhq_production;
ALTER DATABASE gitlabhq_production REFRESH COLLATION VERSION;
</code></pre>
<p>重建完成后输入 <code>\q</code> 并按回车退出，然后我们修复 <code>template1</code> 和 <code>postgres</code> 数据库：</p>
<pre><code class="language-bash">sudo gitlab-psql -d template1 -c &quot;ALTER DATABASE template1 REFRESH COLLATION VERSION;&quot;
sudo gitlab-psql -d postgres -c &quot;ALTER DATABASE postgres REFRESH COLLATION VERSION;&quot;
</code></pre>
<p>修复完成后验证结果：</p>
<pre><code class="language-bash">sudo gitlab-psql -c &quot;
SELECT 
    datname,
    datcollversion,
    pg_collation_actual_version((SELECT oid FROM pg_collation WHERE collname = &#39;default&#39;)) as system_version,
    CASE 
        WHEN datcollversion = pg_collation_actual_version((SELECT oid FROM pg_collation WHERE collname = &#39;default&#39;)) 
        THEN &#39;✅ Good&#39; 
        ELSE &#39;❌ Bad&#39; 
    END as status
FROM pg_database 
WHERE datname IN (&#39;template1&#39;, &#39;postgres&#39;, &#39;gitlabhq_production&#39;)
ORDER BY datname;&quot;
</code></pre>
<p>如果出现如下结果，则修复完成：</p>
<pre><code class="language-bash">       datname       | datcollversion | system_version | status  
---------------------+----------------+----------------+---------
 gitlabhq_production | 2.36           | 2.36           | ✅ Good
 postgres            | 2.36           | 2.36           | ✅ Good
 template1           | 2.36           | 2.36           | ✅ Good
</code></pre>
<p>然后我们就可以重新配置 GitLab 并更新升级了：</p>
<pre><code class="language-bash">sudo gitlab-ctl reconfigure
</code></pre>
<p>完成以后重启 GitLab 即可恢复服务：</p>
<pre><code class="language-bash">sudo gitlab-ctl restart
</code></pre>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2025-09-16T06:09:09.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian 12 Bookworm 升级 Debian 13 Trixie]]></title>
        <id>https://u.sb/debian-upgrade-13/</id>
        <link href="https://u.sb/debian-upgrade-13/"/>
        <updated>2025-08-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何升级 Debian 12 Bookworm 到 Debian 13 Trixie。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何升级 Debian 12 Bookworm 到 Debian 13 Trixie。</p>
<p>相关教程：<a href="https://u.sb/debian-upgrade-12/">Debian 11 Bullseye 升级 Debian 12 Bookworm</a>。</p>
<h2>准备工作</h2>
<p>除非你是物理服务器，以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机，否则升级大版本更新内核是有一定机率导致 Grub 加载失败的，切记备份重要数据！</p>
<p><em>OpenVZ 6 和 LXC 构架的 VPS 是无法升级的，因为他们没有自己独立的内核</em></p>
<p>再强调一遍，一定要备份重要数据！</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>更新系统</h2>
<p>首先需要更新你当前的系统</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>如果内核更新了，可以重启让最新的内核生效，也可以直接进行升级。</p>
<h2>升级系统</h2>
<p>首先更新 <code>apt</code> 源，替换 <code>bookworm</code> 为 <code>trixie</code>：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/bookworm/trixie/g&#39; /etc/apt/sources.list
sudo sed -i &#39;s/bookworm/trixie/g&#39; /etc/apt/sources.list.d/*.list
sudo sed -i &#39;s/bookworm/trixie/g&#39; /etc/apt/sources.list.d/*.sources
</code></pre>
<p>如果没有对应文件会提示诸如 <code>sed: can&#39;t read /etc/apt/sources.list.d/*.sources: No such file or directory</code> 的错误，忽略即可。</p>
<p>或者直接一行命令：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/bookworm/trixie/g&#39; /etc/apt/sources.list /etc/apt/sources.list.d/*.{list,sources} 2&gt;/dev/null
</code></pre>
<p>修改完成后你的 <code>/etc/apt/sources.list</code> 文件内容应该类似如下：</p>
<pre><code class="language-bash">deb https://deb.debian.org/debian trixie main contrib non-free non-free-firmware

deb https://security.debian.org/debian-security trixie-security main contrib non-free non-free-firmware

deb https://deb.debian.org/debian trixie-updates main contrib non-free non-free-firmware
</code></pre>
<p>大部分旧的安装的 Debian 的软件源配置文件使用传统的 One-Line-Style，路径为 <code>/etc/apt/sources.list</code>；但是从 Debian 12 的容器版本开始，以及 Debian 13 正式版后，其软件源配置文件变更为 <code>DEB822</code> 格式，路径为 <code>/etc/apt/sources.list.d/debian.sources</code>:（<a href="https://mirrors.help/debian/">参考</a>）</p>
<pre><code class="language-bash">Types: deb
URIs: https://deb.debian.org/debian
Suites: trixie trixie-updates trixie-backports
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: https://security.debian.org/debian-security
Suites: trixie-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
</code></pre>
<p>如果使用 <code>DEB822</code> 格式，则你可以安全地删除 <code>/etc/apt/sources.list</code> 文件，只保留 <code>/etc/apt/sources.list.d/debian.sources</code> 即可。</p>
<p><em>国内服务器可以替换 <code>deb.debian.org</code> 和 <code>security.debian.org</code> 为 <code>mirrors.tuna.tsinghua.edu.cn</code></em></p>
<p>然后我们再次执行更新系统：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
</code></pre>
<p><em>更新过程中，Debian 13 早期版本可能会提示你是否自动更换为软件源配置文件为 <code>DEB822</code> 格式，如果需要的话可以使用 <code>sudo apt modernize-sources</code> 命令来自动转换，或者自行重写 sources 文件。如果使用命令转换，请自行重写 backports 仓库，目前的转换会有 bug 漏掉这个仓库的 GPG Key。</em></p>
<p>推荐更换成更科学的 <code>DEB822</code> 格式哦~</p>
<p><em>如果 apt 命令最后没有带 <code>-y</code> 自动同意参数的话，更新过程中还会提示一些软件的更新列表，是否需要自动重启等，选 <code>Yes</code> 即可，以及一些软件的配置文件是否需要更新，按照自己的情况选择即可，默认回车即视为使用旧的配置文件，一般会出现在 <code>OpenSSH</code> 等软件的更新上。</em></p>
<p>在 <code>apt-listchanges: News</code> 界面可以按 <code>q</code> 退出：</p>
<p><img src="https://s.bh.sb/2025/08/05/image_S2xQK.png" alt="apt-listchanges News 界面"></p>
<p>提示是否自动重启服务：</p>
<p><img src="https://s.bh.sb/2025/08/05/image_CeXju.png" alt="询问是否自动重启服务的提示界面"></p>
<p>提示是否更新配置文件：</p>
<p><img src="https://s.bh.sb/2025/08/05/image_v7Yk2.png" alt="询问是否更新配置文件的提示界面"></p>
<p>注意某些软件更新后可能会更新 <code>systemd</code> 服务配置，此时我们可以执行 <code>sudo systemctl daemon-reload</code> 重新加载配置。</p>
<p>更新后删除不必要的软件和依赖：</p>
<pre><code class="language-bash">sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>然后我们使用 <code>sudo reboot</code> 命令重启系统，耐心等待后，查看最新的系统版本：</p>
<pre><code class="language-bash">root@debian ~ # cat /etc/debian_version 
13.0
</code></pre>
<pre><code class="language-bash">root@debian ~ # lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 13 (trixie)
Release:	13
Codename:	trixie
</code></pre>
<pre><code class="language-bash">root@debian ~ # uname -a
Linux upload 6.12.38+deb13-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.38-1 (2025-07-16) x86_64 GNU/Linux
</code></pre>
<p>这时我们就已经更新到了最新的 Debian 13 Trixie 和内核了。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2025-08-05T17:46:44.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ubuntu 22.04 Jammy 升级 Ubuntu 24.04 Noble]]></title>
        <id>https://u.sb/ubuntu-upgrade-2404/</id>
        <link href="https://u.sb/ubuntu-upgrade-2404/"/>
        <updated>2024-06-21T05:55:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何升级 Ubuntu 22.04 Jammy Jellyfish 到 Ubuntu 24.04 Noble Numbat。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何升级 Ubuntu 22.04 Jammy Jellyfish 到 Ubuntu 24.04 Noble Numbat。</p>
<p>相关教程：<a href="https://u.sb/ubuntu-upgrade/">Ubuntu 20.04 Focal Fossa 升级 Ubuntu 22.04 Jammy Jellyfish</a>。</p>
<h2>准备工作</h2>
<p>除非你是物理服务器，以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机，否则升级大版本更新内核是有一定机率导致 Grub 加载失败的，切记备份重要数据！</p>
<p><em>OpenVZ 和 LXC 构架的 VPS 是无法升级的，因为他们没有自己独立的内核</em></p>
<p>再强调一遍，一定要备份重要数据！</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>更新系统</h2>
<p>首先需要更新你当前的系统</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>如果内核更新了，可以重启让最新的内核生效，也可以直接进行升级。</p>
<h2>升级系统</h2>
<p>这里有两种升级系统的方法，第一种是使用 <code>do-release-upgrade</code> 命令，第二种是手动更新 <code>apt</code> 源文件。</p>
<h3>方法一：使用 <code>do-release-upgrade</code> 命令</h3>
<p>首先需要安装 <code>ubuntu-release-upgrader-core</code> 包：</p>
<pre><code class="language-bash">sudo apt install ubuntu-release-upgrader-core
</code></pre>
<p>然后修改 <code>/etc/update-manager/release-upgrades</code> 文件，确保 <code>Prompt</code> 值为 <code>lts</code>：</p>
<pre><code class="language-bash">cat /etc/update-manager/release-upgrades | grep lts
</code></pre>
<p>显示如下内容即可：</p>
<pre><code class="language-bash">root@ubuntu ~ # cat /etc/update-manager/release-upgrades | grep lts
#  lts    - Check to see if a new LTS release is available.  The upgrader
Prompt=lts
</code></pre>
<p>最后执行以下命令升级系统：</p>
<pre><code class="language-bash">sudo do-release-upgrade -d
</code></pre>
<h3>方法二：手动更新 <code>apt</code> 源文件</h3>
<p>首先更新 <code>apt</code> 源，替换 <code>jammy</code> 为 <code>noble</code>：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/jammy/noble/g&#39; /etc/apt/sources.list
sudo sed -i &#39;s/jammy/noble/g&#39; /etc/apt/sources.list.d/*.list
</code></pre>
<p>系统 <code>apt</code> 源文件 <code>/etc/apt/sources.list</code> 应该是类似这样的：</p>
<pre><code class="language-bash">deb https://archive.ubuntu.com/ubuntu/ noble main restricted universe multiverse

deb https://archive.ubuntu.com/ubuntu/ noble-updates main restricted universe multiverse

deb https://archive.ubuntu.com/ubuntu/ noble-backports main restricted universe multiverse

deb http://security.ubuntu.com/ubuntu/ noble-security main restricted universe multiverse
</code></pre>
<p>由于在 Ubuntu 24.04 之前，Ubuntu 的软件源配置文件使用传统的 One-Line-Style，路径为 <code>/etc/apt/sources.list</code>；从 Ubuntu 24.04 开始，Ubuntu 的软件源配置文件变更为 <code>DEB822</code> 格式，路径为 <code>/etc/apt/sources.list.d/ubuntu.sources</code>（<a href="https://mirrors.help/ubuntu/">参考</a>），所以使用 <code>DEB822</code> 格式的源文件 <code>/etc/apt/sources.list.d/ubuntu.sources</code>：</p>
<pre><code class="language-bash">Types: deb
URIs: https://archive.ubuntu.com/ubuntu
Suites: noble noble-updates noble-backports
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: noble-security
Components: main restricted universe multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
</code></pre>
<p><em>国内服务器可以替换 <code>archive.ubuntu.com</code> 和 <code>security.ubuntu.com</code> 为 <code>mirrors.tuna.tsinghua.edu.cn</code></em></p>
<p>然后我们再次执行更新系统：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt full-upgrade -y
</code></pre>
<p><em>更新过程中会提示一些软件是否需要自动重启，选 Yes 即可，以及一些软件的配置文件是否需要更新，按照自己的情况选择即可，默认回车即视为使用旧的配置文件，一般会出现在 OpenSSH 等软件的更新上。</em></p>
<p>更新后删除不必要的软件和依赖：</p>
<pre><code class="language-bash">sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>然后我们使用 <code>sudo reboot</code> 命令重启系统，耐心等待后，查看最新的系统版本：</p>
<pre><code class="language-bash">root@ubuntu ~ # lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 24.04 LTS
Release:	24.04
Codename:	noble
</code></pre>
<pre><code class="language-bash">root@ubuntu ~ # uname -a
Linux ubuntu 6.8.0-35-generic #35-Ubuntu SMP PREEMPT_DYNAMIC Mon May 20 15:51:52 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
</code></pre>
<p>这时我们就已经更新到了最新的 Ubuntu 24.04 Noble 和内核了。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2024-06-21T15:28:05.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Docker 安装 FreshRSS 教程]]></title>
        <id>https://u.sb/docker-freshrss/</id>
        <link href="https://u.sb/docker-freshrss/"/>
        <updated>2024-06-21T05:29:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何在 Linux 下使用 Docker 和 Docker Compose 安装 FreshRSS 开源 RSS 聚合器服务。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何在 Linux 下使用 Docker 和 Docker Compose 安装 FreshRSS 开源 RSS 聚合器服务。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。其中 <code>docker</code> 命令未加 <code>sudo</code>：建议把当前用户加入 <code>docker</code> 用户组（<code>sudo usermod -aG docker $USER</code>，重新登录生效），或自行为 <code>docker</code> 命令加上 <code>sudo</code>。</p>
<p><em>PS：本文同时适用于任何可安装 Docker 的 Linux 发行版。</em></p>
<h2>什么是 FreshRSS？</h2>
<p><a href="https://freshrss.org/">FreshRSS</a> 是一款免费且开源的 RSS 聚合器，设计用于帮助用户集中管理和阅读来自不同网站的新闻源。它具有高效、轻量的特点，并且支持多用户使用。</p>
<h2>安装 Docker 和 Docker Compose</h2>
<p>Debian 和 Ubuntu 系统请参考<a href="https://u.sb/debian-install-docker/">本站教程</a>。</p>
<p>其他 Linux 系统可以使用 Docker 官方的脚本安装 Docker 和 Docker Compose：</p>
<pre><code class="language-bash">curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
</code></pre>
<h2>安装 FreshRSS</h2>
<p>这里我们使用 PostgreSQL 数据库，首先创建一个目录用于存放 FreshRSS 的配置文件和数据库文件：</p>
<pre><code class="language-bash">sudo mkdir -p /opt/freshrss
cd /opt/freshrss
</code></pre>
<p>然后创建一个 <code>docker-compose.yml</code> 文件：</p>
<pre><code class="language-yaml">services:

  freshrss:
    image: freshrss/freshrss:latest
    container_name: freshrss
    hostname: freshrss
    restart: unless-stopped
    logging:
      options:
        max-size: 10m
    volumes:
      - ./data:/var/www/FreshRSS/data
      - ./extensions:/var/www/FreshRSS/extensions
    environment:
      TZ: Etc/UTC
      CRON_MIN: &#39;3,33&#39;
      TRUSTED_PROXY: 172.16.0.1/12 192.168.0.1/16
      ADMIN_EMAIL: 你的邮箱
      BASE_URL: 你的 FreshRSS 访问地址
    ports:
      - 127.0.0.1:8080:80

  freshrss-db:
    image: postgres:16
    container_name: freshrss-db
    hostname: freshrss-db
    restart: unless-stopped
    logging:
      options:
        max-size: 10m
    volumes:
      - ./db:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: freshrss
      POSTGRES_USER: freshrss
      POSTGRES_PASSWORD: freshrss
    command:
      - -c
      - shared_buffers=1GB
      - -c
      - work_mem=32MB
</code></pre>
<p>请自行替换 <code>ADMIN_EMAIL</code> 和 <code>BASE_URL</code> 的值，<code>BASE_URL</code> 需要写全，比如 <code>https://freshrss.example.com</code>。</p>
<p>然后拉取 Docker 镜像并运行：</p>
<pre><code class="language-bash">cd /opt/freshrss
docker compose pull
docker compose up -d
</code></pre>
<h2>安装配置 Nginx 反向代理</h2>
<p>我们的 Docker Compose 配置文件中，FreshRSS 服务监听在 <code>127.0.0.1:8080</code> 端口，所以我们需要配置 Nginx 反代来访问，假设你 FreshRSS 的地址是 <code>https://freshrss.example.com</code>：</p>
<p><code>freshrss.example.com</code> 段配置：</p>
<pre><code class="language-nginx">	location / {
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_http_version 1.1;
		proxy_set_header Upgrade $http_upgrade;
		proxy_redirect off;
		proxy_set_header        X-Forwarded-Proto $scheme;
		proxy_connect_timeout       300;
		proxy_send_timeout          300;
		proxy_read_timeout          300;
		send_timeout                300;
		proxy_pass http://127.0.0.1:8080;
	}
</code></pre>
<p>最后记得参考本站 <a href="https://u.sb/nginx-ssl/">Nginx SSL 配置教程</a>加上 SSL 证书后，即可访问 <code>https://freshrss.example.com/</code>：</p>
<h2>配置 FreshRSS</h2>
<p>访问 <code>https://freshrss.example.com/</code>，在安装向导中填写数据库信息：</p>
<ul>
<li>数据库类型：<code>PostgreSQL</code></li>
<li>数据库主机（Host）：<code>freshrss-db</code></li>
<li>数据库名称（Database）：<code>freshrss</code></li>
<li>数据库用户（User）：<code>freshrss</code></li>
<li>数据库密码（Password）：<code>freshrss</code></li>
</ul>
<p>然后点击 <code>Install FreshRSS</code> 即可完成安装。</p>
<p>如果需要安装插件，可以把插件上传到 <code>/opt/freshrss/extensions</code> 目录，然后在 FreshRSS 后台安装。</p>
<p>推荐在官方插件仓库里下载插件：<a href="https://github.com/FreshRSS/Extensions">FreshRSS Extensions</a></p>
<p>推荐安装 <a href="https://github.com/FreshRSS/Extensions/tree/master/xExtension-CustomCSS">CustomCSS</a> 插件，就可以使用自定义 CSS 样式了，个人比较喜欢<a href="https://github.com/catppuccin/freshrss">这个主题系列</a>。</p>
<p>如果需要第三方客户端，可以在这里查看支持的应用：<a href="https://github.com/FreshRSS/FreshRSS/blob/edge/README.md#apis--native-apps">APIs &amp; native apps</a></p>
<h2>迁移 FreshRSS</h2>
<p>可以参考《<a href="https://u.sb/docker-mailcow/#mailcow-%E7%9A%84%E8%BF%81%E7%A7%BB-t2">使用 Docker 安装 Mailcow 自建域名邮箱</a>》。</p>
<h2>备份 FreshRSS</h2>
<p>我们可以定期备份 FreshRSS 网站文件和数据库，压缩网站目录和导出数据库命令如下：</p>
<pre><code class="language-bash">backup_folder_name=$(date +&quot;%Y_%m_%d_%I_%M_%p&quot;)
# 备份 FreshRSS 网站文件
tar --exclude /opt/freshrss/data/cache -zcvf data-$backup_folder_name.tar.gz /opt/freshrss/data
tar -zcvf extensions-$backup_folder_name.tar.gz /opt/freshrss/extensions
# 备份 FreshRSS 数据库
docker exec -t freshrss-db pg_dumpall -c -U freshrss | gzip &gt; database-$backup_folder_name.gz
</code></pre>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2024-06-21T14:34:43.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 开启 SSH 的 RSA Key 登录]]></title>
        <id>https://u.sb/debian-enable-ssh-rsa-key/</id>
        <link href="https://u.sb/debian-enable-ssh-rsa-key/"/>
        <updated>2023-06-09T17:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何在 Debian 和 Ubuntu 开启 SSH 的 RSA Key 登录。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何在 Debian 和 Ubuntu 开启 SSH 的 RSA Key 登录。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<p>自从 OpenSSH 8.3 开始，RSA Key 登录默认<a href="https://lwn.net/Articles/821544/">被禁用</a>，并被认为<a href="https://confluence.atlassian.com/bitbucketserverkb/ssh-rsa-key-rejected-with-message-no-mutual-signature-algorithm-1026057701.html">不安全</a>。</p>
<p>所以自从 Ubuntu 22.04 和 Debian 12 开始，如果某些古老的业务需要使用 RSA Key 登录，你需要手动开启 RSA Key 登录。</p>
<h2>开启 RSA Key 登录</h2>
<p>我们不需要修改 <code>/etc/ssh/sshd_config</code> 这个系统默认的 SSH 配置文件，只需要添加一个 <code>/etc/ssh/sshd_config.d/enable_rsa_keys.conf</code> 配置文件即可：</p>
<pre><code class="language-bash">sudo bash -c &#39;cat &gt; /etc/ssh/sshd_config.d/enable_rsa_keys.conf &lt;&lt; EOF
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa
EOF&#39;
</code></pre>
<h2>重启 SSH 服务</h2>
<p>然后重启 SSH 服务即可：</p>
<pre><code class="language-bash">sudo systemctl restart ssh
</code></pre>
<p>或</p>
<pre><code class="language-bash">sudo systemctl restart sshd
</code></pre>
<p>这两个服务在 Debian 12 下都是一样的：</p>
<pre><code class="language-bash">root@debian ~ # systemctl status ssh.service
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; preset: enabled)
     Active: active (running) since Fri 2023-06-09 16:54:43 UTC; 15min ago
</code></pre>
<p>对比</p>
<pre><code class="language-bash">root@debian ~ # systemctl status sshd
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; preset: enabled)
     Active: active (running) since Fri 2023-06-09 16:54:43 UTC; 15min ago
</code></pre>
<p>我们可以看到 sshd 其实是 ssh 服务的别称：</p>
<pre><code class="language-bash">root@debian ~ # cat /lib/systemd/system/ssh.service | grep Alias
Alias=sshd.service
</code></pre>
<p>此时您就可以使用 RSA Key 登录 SSH 了。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2023-06-10T03:16:24.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian 11 Bullseye 升级 Debian 12 Bookworm]]></title>
        <id>https://u.sb/debian-upgrade-12/</id>
        <link href="https://u.sb/debian-upgrade-12/"/>
        <updated>2023-06-09T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何升级 Debian 11 Bullseye 到 Debian 12 Bookworm。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何升级 Debian 11 Bullseye 到 Debian 12 Bookworm。</p>
<p>相关教程：<a href="https://u.sb/debian-upgrade/">Debian 10 Buster 升级 Debian 11 Bullseye</a>。</p>
<h2>准备工作</h2>
<p>除非你是物理服务器，以及没有用过奇奇怪怪定制或修改的内核的 KVM 构架的 VPS 和云主机，否则升级大版本更新内核是有一定机率导致 Grub 加载失败的，切记备份重要数据！</p>
<p><em>OpenVZ 6 和 LXC 构架的 VPS 是无法升级的，因为他们没有自己独立的内核</em></p>
<p>再强调一遍，一定要备份重要数据！</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>更新系统</h2>
<p>首先需要更新你当前的系统</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>如果内核更新了，可以重启让最新的内核生效，也可以直接进行升级。</p>
<h2>升级系统</h2>
<p>首先更新 <code>apt</code> 源，替换 <code>bullseye</code> 为 <code>bookworm</code>：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/bullseye/bookworm/g&#39; /etc/apt/sources.list
sudo sed -i &#39;s/bullseye/bookworm/g&#39; /etc/apt/sources.list.d/*.list
sudo sed -i &#39;s/bullseye/bookworm/g&#39; /etc/apt/sources.list.d/*.sources
</code></pre>
<p>对于 Debian 12 以后的版本，所有 Debian 可以分发的打包的非自由固件二进制文件 (non-free)，比如某些驱动，都被转移到 Debian Archive 中的一个新组件，称为非自由固件 (non-free-firmware)。如果您从旧版的 Debian 升级，并且需要这些固件二进制文件，您应该更新您系统上的 <code>/etc/apt/sources.list</code>，以使用这个新组件 (<a href="https://wiki.debian.org/Firmware#Debian_12_.28bookworm.29_and_later">来源</a>)：</p>
<pre><code class="language-bash">sudo sed -i &#39;s/non-free/non-free non-free-firmware/g&#39; /etc/apt/sources.list
</code></pre>
<p>默认的系统 <code>apt</code> 源文件 <code>/etc/apt/sources.list</code> 应该是类似这样的：</p>
<pre><code class="language-bash">deb http://deb.debian.org/debian bookworm main contrib non-free non-free-firmware

deb http://security.debian.org/debian-security bookworm-security main contrib non-free non-free-firmware

deb http://deb.debian.org/debian bookworm-updates main contrib non-free non-free-firmware
</code></pre>
<p>大部分 Debian 的软件源配置文件使用传统的 One-Line-Style，路径为 <code>/etc/apt/sources.list</code>；但是对于容器镜像，从 Debian 12 开始，其软件源配置文件变更为 <code>DEB822</code> 格式，路径为 <code>/etc/apt/sources.list.d/debian.sources</code>:（<a href="https://mirrors.help/debian/">参考</a>）</p>
<pre><code class="language-bash">Types: deb
URIs: https://deb.debian.org/debian
Suites: bookworm bookworm-updates bookworm-backports
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg

Types: deb
URIs: http://security.debian.org/debian-security
Suites: bookworm-security
Components: main contrib non-free non-free-firmware
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
</code></pre>
<p><em>国内服务器可以替换 <code>deb.debian.org</code> 和 <code>security.debian.org</code> 为 <code>mirrors.tuna.tsinghua.edu.cn</code></em></p>
<p>然后我们再次执行更新系统：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
</code></pre>
<p><em>更新过程中会提示一些软件是否需要自动重启，选 Yes 即可，以及一些软件的配置文件是否需要更新，按照自己的情况选择即可，默认回车即视为使用旧的配置文件，一般会出现在 OpenSSH 等软件的更新上。</em></p>
<p>在 <code>apt-listchanges: News</code> 界面可以按 <code>q</code> 退出：</p>
<p><img src="https://s.bh.sb/uploads/2023/06/10/6zkX4boedhWO9nR.png" alt="apt-listchanges News 界面"></p>
<p>提示是否自动重启服务：</p>
<p><img src="https://s.bh.sb/uploads/2023/06/10/gtoa1WO57ST24ui.png" alt="询问是否自动重启服务的提示界面"></p>
<p>提示是否更新 OpenSSH 配置文件：</p>
<p><img src="https://s.bh.sb/uploads/2023/06/10/l4fe7Y9VckH8iBz.png" alt="询问是否更新 OpenSSH 配置文件的提示界面"></p>
<p>注意某些软件更新后可能会更新 <code>systemd</code> 服务配置，此时我们可以执行 <code>sudo systemctl daemon-reload</code> 重新加载配置。</p>
<p>如果升级的时候遇到了如下错误：</p>
<pre><code class="language-bash">Setting up dbus-daemon (1.14.6-1) ...
UUID file &#39;/var/lib/dbus/machine-id&#39; should contain a hex string of length 32, not length 0, with no other text
dpkg: error processing package dbus-daemon (--configure):
 installed dbus-daemon package post-installation script subprocess returned error exit status 1
dpkg: dependency problems prevent configuration of dbus:
 dbus depends on dbus-daemon (= 1.14.6-1); however:
  Package dbus-daemon is not configured yet.

dpkg: error processing package dbus (--configure):
 dependency problems - leaving unconfigured
Processing triggers for libc-bin (2.36-9) ...
Errors were encountered while processing:
 dbus-daemon
 dbus
E: Sub-process /usr/bin/dpkg returned an error code (1)
</code></pre>
<p>删除 <code>/var/lib/dbus/machine-id</code> 这个空文件后重新执行命令即可，升级 <code>dbus</code> 时会自动生成这个文件。</p>
<pre><code class="language-bash">sudo rm -rf /var/lib/dbus/machine-id
</code></pre>
<p>更新后删除不必要的软件和依赖：</p>
<pre><code class="language-bash">sudo apt autoclean
sudo apt autoremove -y
</code></pre>
<p>然后我们使用 <code>sudo reboot</code> 命令重启系统，耐心等待后，查看最新的系统版本：</p>
<pre><code class="language-bash">root@debian ~ # cat /etc/debian_version 
12.5
</code></pre>
<pre><code class="language-bash">root@debian ~ # lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm
</code></pre>
<pre><code class="language-bash">root@debian ~ # uname -a
Linux debian 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64 GNU/Linux
</code></pre>
<p>这时我们就已经更新到了最新的 Debian 12 Bookworm 和内核了。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2023-06-10T03:16:24.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 下使用 HTTP/3 协议的 Nginx QUIC]]></title>
        <id>https://u.sb/debian-nginx-quic/</id>
        <link href="https://u.sb/debian-nginx-quic/"/>
        <updated>2023-08-02T01:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文适合 Debian Stable 和 Ubuntu LTS.。]]></summary>
        <content type="html"><![CDATA[<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。其中 <code>docker</code> 命令未加 <code>sudo</code>：建议把当前用户加入 <code>docker</code> 用户组（<code>sudo usermod -aG docker $USER</code>，重新登录生效），或自行为 <code>docker</code> 命令加上 <code>sudo</code>。</p>
<h2>1、什么是 HTTP/3 和 QUIC？</h2>
<p><a href="https://en.wikipedia.org/wiki/HTTP/3">HTTP/3</a> 是一种基于 <a href="https://en.wikipedia.org/wiki/QUIC">QUIC</a> (Quick UDP Internet Connections) 协议的 HTTP 协议版本，它是 HTTP/2 的后继者，旨在改进 Web 性能和安全性。</p>
<p>HTTP/3 与之前的 HTTP 协议有很大的不同，最明显的区别是它使用 QUIC 协议而不是 TCP 协议来传输数据。</p>
<p>QUIC 是一种由 Google 开发的协议，基于 <a href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">UDP</a>，它在保持安全性的同时提供更快的连接和更少的延迟。与 TCP 不同，QUIC 允许多个请求同时在同一连接上进行，从而减少了网络拥塞和握手延迟的影响。</p>
<p>总的来说，HTTP/3 的设计目标是通过减少延迟和提高性能，为 Web 应用程序提供更快、更安全和更高效的用户体验。</p>
<h2>2、安装 Nginx Quic</h2>
<p>这里我们推荐<a href="https://n.wtf/">烧饼博客</a>团队打包的 Nginx Quic 版本，它是基于最新的官方 1.25.0 源码打包的，支持 HTTP/3 和 QUIC 协议。</p>
<h3>2.1 更新系统并安装部分必要软件</h3>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates
</code></pre>
<h3>2.2 增加 GPG Key</h3>
<pre><code class="language-bash">curl -sSL https://n.wtf/public.key | sudo gpg --dearmor -o /usr/share/keyrings/n.wtf.gpg
</code></pre>
<h3>2.3 添加 Nginx QUIC 源</h3>
<pre><code class="language-bash">echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror-cdn.xtom.com/sb/nginx/ $(lsb_release -sc) main&quot; | sudo tee /etc/apt/sources.list.d/n.wtf.list
</code></pre>
<p>如果你的服务器在国内，可以使用下面的源：</p>
<pre><code class="language-bash">echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror.iscas.ac.cn/sb/nginx/ $(lsb_release -sc) main&quot; | sudo tee /etc/apt/sources.list.d/n.wtf.list
</code></pre>
<p>或</p>
<pre><code class="language-bash">echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror.nju.edu.cn/sb/nginx/ $(lsb_release -sc) main&quot; | sudo tee /etc/apt/sources.list.d/n.wtf.list
</code></pre>
<h3>2.4 更新并安装 Nginx</h3>
<pre><code class="language-bash">sudo apt update
sudo apt install nginx-extras -y
</code></pre>
<p>安装完毕后，我们可以使用 <code>nginx -V</code> 命令看到 Nginx 已经是最新的 1.31.2 主线版 + QUIC 了：</p>
<pre><code class="language-bash">root@debian ~ # nginx -V
nginx version: nginx-n.wtf/1.31.2
built by gcc 14.2.0 (Debian 14.2.0-19) 
built with OpenSSL 4.0.1 9 Jun 2026
TLS SNI support enabled
</code></pre>
<h3>2.5 使用 Docker 安装</h3>
<p>你也可以使用 <a href="https://github.com/u-sb/nginx-docker">Docker</a> 进行体验：</p>
<pre><code class="language-bash">docker run --name nginx --net host --restart always -v $HOME/nginx-config:/usr/src/docker-nginx/conf:ro -d ghcr.io/u-sb/nginx
</code></pre>
<p>此时配置文件的目录在当前目录的 <code>nginx-config</code> 文件夹下。</p>
<h2>3、配置 Nginx</h2>
<p>首先，HTTP/3 仅支持 HTTPS 协议，因此我们需要准备好 SSL 证书，可以参考<a href="https://u.sb/nginx-ssl/">《Nginx 配置 SSL 证书》</a>获取 SSL 证书。</p>
<p>其次，需要开启 TLS 1.3 支持，因为 HTTP/3 是基于 TLS 1.3 的，如果没有开启 TLS 1.3，那么 HTTP/3 将无法正常工作。</p>
<p><del>最后，需要添加 <code>listen 443 http3</code> 监听端口并开启 HTTP/3 支持，以及需要添加一个 <code>Alt-Svc</code> 的头部信息 <code>add_header Alt-Svc &#39;h3=&quot;:443&quot;; ma=86400&#39;;</code>，用于告诉浏览器使用 HTTP/3 协议访问网站。</del></p>
<p>最后，需要添加 <code>listen 443 quic</code> 监听端口并开启 HTTP/3 支持，以及需要添加一个 <code>Alt-Svc</code> 的头部信息 <code>add_header Alt-Svc &#39;h3=&quot;:$server_port&quot;; ma=86400&#39;;</code>，用于告诉浏览器使用 HTTP/3 协议访问网站。</p>
<p>因为<a href="https://hg.nginx.org/nginx-quic/rev/69bae2437d74">这个修改</a>，从 Nginx 1.25.0 开始，已经不支持 <code>http3</code> 的 <code>listen</code> 段，需要改为 <code>quic</code>。</p>
<p>另外，从 Nginx 1.25.1 开始，<code>listen ... http2</code> 已废弃，需要改为 <code>http2 on</code>，否则会报警告 <code>nginx: [warn] the &quot;listen ... http2&quot; directive is deprecated, use the &quot;http2&quot; directive instead</code>：</p>
<p>我们附上一个基本的配置示例：</p>
<pre><code class="language-nginx">server {
	listen 443 ssl default_server;
	listen [::]:443 ssl default_server;
    # 开启 HTTP/3
	listen 443 quic reuseport;
	listen [::]:443 quic reuseport;
    # 开启 HTTP/2
    http2 on;

	server_name example.com;

	root /var/www/example.com;
	index index.html;

	ssl_certificate /etc/nginx/ssl/example.com.crt;
	ssl_certificate_key /etc/nginx/ssl/example.com.key;

    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

	add_header Alt-Svc &#39;h3=&quot;:$server_port&quot;; ma=86400&#39; always;
    add_header X-protocol $server_protocol always;
}
</code></pre>
<p>请注意 <code>listen 443 quic reuseport</code> 里的 <code>reuseport</code> 参数，以及 <code>listen 443 ssl default_server</code> 里的 <code>default_server</code> 参数，所有 server 段里，只允许一个段出现 <code>reuseport</code> 和 <code>default_server</code> 参数，否则会报错。</p>
<p>另外 <code>listen</code> 段里的 <code>ssl</code> 无法和 <code>quic</code> 放一起，必须分开写两段。</p>
<h2>4、测试 HTTP/3</h2>
<p>我们使用 Firefox 浏览器，因为目前 DNS SVCB/HTTPS 记录<a href="https://taoshu.in/dns/dns-svcb-https.html">尚未普及</a>，所以第一次访问的时候，浏览器还是走 TCP 协议使用 HTTP/2 或者 HTTP/1.1 请求你的网站，获取 <code>Alt-Svc</code> 的头部信息后，才会走 HTTP/3 协议，所以第一次访问以后，可以关掉浏览器重新打开再测试。</p>
<p>我们可以打开 F12 开发者工具，查看 Network 选项卡，可以看到 HTTP/3 协议的请求：</p>
<p><img src="https://s.bh.sb/uploads/2023/02/20/bzZGxtN9lRjMD3f.png" alt="Firefox HTTP/3"></p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2023-02-20T05:44:13.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 使用 xcaddy 自定义编译 Caddy]]></title>
        <id>https://u.sb/xcaddy/</id>
        <link href="https://u.sb/xcaddy/"/>
        <updated>2023-01-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导使用 xcaddy 自定义编译 Caddy。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导使用 xcaddy 自定义编译 Caddy。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<p>上一篇文章介绍了如何<a href="https://u.sb/debian-install-caddy/">安装使用 Caddy</a>，但是 Caddy 的功能有时候并不能满足业务需求，如果想要使用更多的功能，就需要自定义编译 Caddy。</p>
<p><a href="https://github.com/caddyserver/xcaddy">xcaddy</a> 是 Caddy 官方制作的用于自定义编译 Caddy 的工具，它可以帮助我们快速的编译出符合自己需求的 Caddy。</p>
<h2>安装 xcaddy</h2>
<p>我们按照官方的<a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">安装方法</a>，首先，安装一些必要的软件包：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates
</code></pre>
<p>然后按照<a href="https://go.dev/doc/install">官方教程</a>安装 Go：</p>
<pre><code class="language-bash">wget https://go.dev/dl/go1.24.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go &amp;&amp; sudo tar -C /usr/local -xzf go1.24.5.linux-amd64.tar.gz
</code></pre>
<p>然后把 go 加入系统环境变量：</p>
<pre><code class="language-bash">echo &quot;export PATH=\$PATH:/usr/local/go/bin&quot; | sudo tee -a /etc/profile
</code></pre>
<p>然后加入 Caddy 的 GPG 公钥和 apt 源：</p>
<pre><code class="language-bash">curl -sSL https://dl.cloudsmith.io/public/caddy/xcaddy/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/xcaddy.gpg
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/xcaddy.gpg] https://dl.cloudsmith.io/public/caddy/xcaddy/deb/debian any-version main&quot; | sudo tee /etc/apt/sources.list.d/xcaddy.list
</code></pre>
<p>Debian 下也可以使用 extrepo：</p>
<pre><code class="language-bash">sudo apt install extrepo
sudo extrepo enable caddyserver
</code></pre>
<p>然后更新系统后即可安装 xcaddy：</p>
<pre><code class="language-bash">sudo apt update
sudo apt install xcaddy
</code></pre>
<p>重启打开 SSH 后检查一下 go 和 xcaddy 的版本：</p>
<pre><code class="language-bash">root@debian ~ # go version
go version go1.24.5 linux/amd64

root@debian ~ # xcaddy version
v0.4.5 h1:7E4b+3Gm2do/WpuDXh5MWIj+qgCCvQqR487Sm8C6hwc=
</code></pre>
<h2>自定义编译 Caddy</h2>
<p>我们可以选择一些自己喜欢的模块，比如<a href="https://github.com/caddyserver/cache-handler">缓存模块</a>和 <a href="https://github.com/ueffel/caddy-brotli">Brotli 压缩模块</a>：</p>
<pre><code class="language-bash">xcaddy build \
    --with github.com/caddyserver/cache-handler \
	  --with github.com/ueffel/caddy-brotli
</code></pre>
<p>经过一段时间的编译以后，我们就可以在当前目录下看到一个名为 <code>caddy</code> 的二进制文件，这就是我们自定义编译的 Caddy 了。</p>
<h2>自定义 Caddy 和系统 Caddy 共存</h2>
<p>如果我们想要自定义 Caddy 和系统 Caddy 共存，可以使用官方的<a href="https://caddyserver.com/docs/build">教程</a>：</p>
<p>首先，按照我们的教程<a href="https://u.sb/debian-install-caddy/">安装</a> Caddy，安装完毕后，先停止 Caddy 服务：</p>
<pre><code class="language-bash">sudo systemctl stop caddy
</code></pre>
<p>然后使用 <code>dpkg-divert</code> 命令将系统 Caddy 的二进制文件移动到 <code>/usr/bin/caddy.default</code> 并做软链接：</p>
<pre><code class="language-bash">sudo dpkg-divert --divert /usr/bin/caddy.default --rename /usr/bin/caddy
</code></pre>
<p>然后把我们自己编译好的 Caddy 二进制文件移动到 <code>/usr/bin/caddy.custom</code>：</p>
<pre><code class="language-bash">sudo mv ./caddy /usr/bin/caddy.custom
</code></pre>
<p>然后设置优先级，让我们的自定义 Caddy 优先启动：</p>
<pre><code class="language-bash">sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.default 10
sudo update-alternatives --install /usr/bin/caddy caddy /usr/bin/caddy.custom 50
</code></pre>
<p>此时我们可以看到默认的 <code>/usr/bin/caddy</code> 已经是我们自定义的 Caddy 了：</p>
<pre><code class="language-bash">root@debian ~ # ls -l /usr/bin/caddy
lrwxrwxrwx 1 root root 23 Jan 22 10:52 /usr/bin/caddy -&gt; /etc/alternatives/caddy*

root@debian ~ # ls -l /etc/alternatives/caddy
lrwxrwxrwx 1 root root 21 Jan 22 10:52 /etc/alternatives/caddy -&gt; /usr/bin/caddy.custom
</code></pre>
<p>我们也可以使用 <code>sudo update-alternatives --config caddy</code> 命令来切换系统安装的 Caddy 和自定义的 Caddy：</p>
<pre><code class="language-bash">root@caddy ~ # update-alternatives --config caddy
There are 2 choices for the alternative caddy (providing /usr/bin/caddy).

  Selection    Path                    Priority   Status
------------------------------------------------------------
* 0            /usr/bin/caddy.custom    50        auto mode
  1            /usr/bin/caddy.custom    50        manual mode
  2            /usr/bin/caddy.default   10        manual mode

Press &lt;enter&gt; to keep the current choice[*], or type selection number:
</code></pre>
<p>我们可以看到默认的 Caddy 二进制文件是我们自定义的，你可以输入 0 (按照优先级自动) 1 (手工切换自定义 Caddy) 或 2 (使用系统默认 Caddy) 来修改并切换默认的 Caddy 版本。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2023-01-22T23:54:54.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 安装 Caddy]]></title>
        <id>https://u.sb/debian-install-caddy/</id>
        <link href="https://u.sb/debian-install-caddy/"/>
        <updated>2022-12-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何在 Debian 和 Ubuntu 下安装 Caddy。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何在 Debian 和 Ubuntu 下安装 Caddy。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>什么是 Caddy？</h2>
<p><a href="https://caddyserver.com/">Caddy</a> 是一款开源的 Web 服务器，它设计简单，易于使用，并且有很多强大的功能。它可以自动处理 TLS (SSL)，并且可以使用中间件扩展功能。</p>
<p>Caddy 采用简单的配置语法，可以轻松配置路由，反向代理，重定向，缓存和其他功能。它还支持 HTTP/2，QUIC (HTTP/3) 和 WebSockets 协议，可以提供快速的网络性能。</p>
<h2>为什么尝试 Caddy？</h2>
<p>对我来说比较简单的原因是 <a href="https://quic.nginx.org/">nginx-quic</a> 这个项目一直未发布正式版，想要一款简单的能支持 <a href="https://en.wikipedia.org/wiki/HTTP/3">HTTP/3</a> 的 Web 服务器软件，目前来说 Caddy 的选择是最合适的。</p>
<p>Caddy 还支持自动化证书，对于懒人来说特别合适。</p>
<h2>安装 Caddy</h2>
<p>我们按照官方的<a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">安装方法</a>，首先，安装一些必要的软件包：</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates
</code></pre>
<p>然后加入 Caddy 的 GPG 公钥和 apt 源：</p>
<pre><code class="language-bash">curl -sSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/caddy.gpg
echo &quot;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/caddy.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main&quot; | sudo tee /etc/apt/sources.list.d/caddy.list
</code></pre>
<p>Debian 下也可以使用 extrepo：</p>
<pre><code class="language-bash">sudo apt install extrepo
sudo extrepo enable caddyserver
</code></pre>
<p>然后更新系统后即可安装 Caddy：</p>
<pre><code class="language-bash">sudo apt update
sudo apt install caddy
</code></pre>
<h2>配置 Caddy</h2>
<p>默认的 <code>Caddyfile</code> 文件位于 <code>/etc/caddy/Caddyfile</code>，官方的教程在<a href="https://caddyserver.com/docs/caddyfile-tutorial">这儿</a>，如果你习惯了 Nginx 和 Apache 的配置，那么此时应该会非常不习惯，我们就来一个最简单的例子把：</p>
<ol>
<li>我们需要绑定域名 <code>example.com</code></li>
<li>这个域名的文件位于 <code>/var/www/example.com</code>，默认首页文件名为 <code>index.html</code></li>
<li>我们需要开启 SSL 访问，并且访问 http 跳转到 https</li>
<li>我们需要设置开启 TLS 1.2 和 TLS 1.3，并开启 HSTS Preload</li>
</ol>
<p>首先，设置 <code>http://example.com/</code> 跳转到 <code>https://example.com/</code>：</p>
<p><strong>注意</strong>：默认情况如果下面的 <code>example.com:80</code> 和 <code>example.com:443</code> 没有配置其他的端口，Caddy 会自动使用 443 端口，并自动开启 <code>http://example.com/</code> 跳转到 <code>https://example.com/</code>，这里的示范是为了给有些奇怪的需求比如一些特定的端口访问 HTTP 和 HTTPS，则需要手动配置。</p>
<pre><code class="language-bash">example.com:80 {
	redir https://{host}{uri} permanent
}
</code></pre>
<p>然后我们按照 Mozilla 的<a href="https://ssl-config.mozilla.org/#server=caddy&version=2.6.2&config=intermediate&guideline=5.6">推荐配置</a>写入 <code>Caddyfile</code>：</p>
<pre><code class="language-bash">example.com:443 {
	tls {
		protocols tls1.2 tls1.3
		ciphers TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
	}

	header {
		Strict-Transport-Security &quot;max-age=63072000; includeSubDomains; preload&quot;
		Referrer-Policy strict-origin-when-cross-origin
		X-Frame-Options SAMEORIGIN
		X-Content-Type-Options nosniff
		X-XSS-Protection &quot;1; mode=block&quot;
	}

	root * /var/www/example.com

	file_server {
		index index.html
	}

	encode gzip zstd
}
</code></pre>
<p>当然也可以设置 <code>www.example.com</code> 跳转 <code>example.com</code>：</p>
<pre><code class="language-bash">www.example.com:80 {
	redir https://example.com{uri} permanent
}

www.example.com:443 {
	redir https://example.com{uri} permanent
}
</code></pre>
<p>然后把以上所有内容合并成一个 <code>Caddyfile</code> 文件，放到 <code>/etc/caddy/Caddyfile</code>，然后检查 Caddy 配置：</p>
<pre><code class="language-bash">sudo caddy validate --config /etc/caddy/Caddyfile
</code></pre>
<p>输出如下内容则表示配置正确：</p>
<pre><code class="language-bash">root@debian ~ # caddy validate --config /etc/caddy/Caddyfile
2022/12/26 17:31:22.347	INFO	using provided configuration	{&quot;config_file&quot;: &quot;/etc/caddy/Caddyfile&quot;, &quot;config_adapter&quot;: &quot;&quot;}
2022/12/26 17:31:22.349	WARN	http	server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server	{&quot;server_name&quot;: &quot;srv1&quot;, &quot;http_port&quot;: 80}
2022/12/26 17:31:22.349	INFO	http	enabling automatic HTTP-&gt;HTTPS redirects	{&quot;server_name&quot;: &quot;srv0&quot;}
2022/12/26 17:31:22.349	INFO	tls.cache.maintenance	started background certificate maintenance	{&quot;cache&quot;: &quot;0xc00018ad90&quot;}
2022/12/26 17:31:22.350	INFO	tls.cache.maintenance	stopped background certificate maintenance	{&quot;cache&quot;: &quot;0xc00018ad90&quot;}
Valid configuration
</code></pre>
<p>有强迫症的也可以把你的 Caddyfile 文件美化一下：</p>
<pre><code class="language-bash">sudo caddy fmt /etc/caddy/Caddyfile --overwrite
</code></pre>
<p>最后重启 Caddy</p>
<pre><code class="language-bash">sudo systemctl restart caddy
</code></pre>
<p>耐心等待自动签发 SSL 证书，然后我们就可以打开浏览器控制台查看 <code>https://example.com/</code> 即可看到 SSL 证书已经自动部署，同时 HTTP/3 已经开启了：</p>
<p><img src="https://s.bh.sb/img/2022/12/e3e00d.png" alt="HTTP/3"></p>
<p>更多的配置可以参考<a href="https://caddyserver.com/docs/caddyfile">官方文档</a>或<a href="https://mritd.com/2021/06/30/understand-caddyfile-syntax/">《Caddyfile 语法浅析》</a></p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2022-12-27T04:47:25.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Debian / Ubuntu 手工添加 Swap 分区]]></title>
        <id>https://u.sb/debian-swap/</id>
        <link href="https://u.sb/debian-swap/"/>
        <updated>2022-12-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导如何在 Debian 11 和 Ubuntu 22.04 下手工添加 Swap 分区。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导如何在 Debian 11 和 Ubuntu 22.04 下手工添加 Swap 分区。</p>
<p>以下操作需要 root 权限，本文命令均已带上 <code>sudo</code> 前缀，请确保当前用户拥有 <code>sudo</code> 权限；或者直接使用 root 用户操作并省略命令中的 <code>sudo</code>。</p>
<h2>准备工作</h2>
<p>首先，检查你的系统是否已经有 Swap 分区：</p>
<pre><code class="language-bash">sudo swapon -s
</code></pre>
<p>或</p>
<pre><code class="language-bash">free -m
</code></pre>
<p>如果没有返回结果或者 <code>free -m</code> 中 <code>Swap</code> 一列数值是 <code>0</code>，则表示你的系统没有 Swap 分区。</p>
<h2>创建 SWAP 分区</h2>
<p>我们可以使用 <code>fallocate</code> 命令创建一个 1GB 大小的 Swap 分区：</p>
<pre><code class="language-bash">sudo fallocate -l 1G /swapfile
</code></pre>
<p>如果这个命令无法使用，请安装 <code>util-linux</code> 包：</p>
<pre><code class="language-bash">sudo apt install util-linux
</code></pre>
<p>然后设置这个文件的权限：</p>
<pre><code class="language-bash">sudo chmod 600 /swapfile
</code></pre>
<p>然后激活 SWAP 分区</p>
<pre><code class="language-bash">sudo mkswap /swapfile
sudo swapon /swapfile
</code></pre>
<p>此时，你可以使用 <code>sudo swapon -s</code> 或 <code>free -m</code> 命令查看 Swap 分区是否已经激活。</p>
<h2>设置开机自启</h2>
<p>我们需要编辑 <code>/etc/fstab</code> 这个文件，加入下面的内容即可：</p>
<pre><code class="language-bash">echo &quot;/swapfile swap swap defaults 0 0&quot; | sudo tee -a /etc/fstab
</code></pre>
<p>大功告成，使用 <code>free -m</code> 命令查看 Swap 分区是否正确：</p>
<p><img src="https://s.bh.sb/img/2022/12/1097a4.png" alt="Debian / Ubuntu 手工添加 Swap 分区"></p>
<h2>调整系统内核 Swappiness 值</h2>
<p>Swapiness 是 Linux 内核的一个属性，定义了系统使用交换空间的频率，Swapiness 的值在 0 到 100 之间 (默认是 60)，一个低的值会使内核尽可能地避免交换，而一个高的值会使内核更积极地使用交换空间。</p>
<p>这个值默认是 <code>60</code>，我们可以使用 <code>cat /proc/sys/vm/swappiness</code> 命令查看当前值。</p>
<p>一般我们可以给他改成 <code>10</code>：</p>
<pre><code class="language-bash">echo &quot;vm.swappiness=10&quot; | sudo tee -a /etc/sysctl.conf
</code></pre>
<p>然后使用 <code>sudo sysctl -p</code> 命令使其生效。</p>
<h2>关闭 Swap</h2>
<p>有时候我们需要关闭 Swap 分区，可以使用下面的命令：</p>
<p>首先，停用 Swap 分区：</p>
<pre><code class="language-bash">sudo swapoff -v /swapfile
</code></pre>
<p>然后检查 <code>/etc/fstab</code>，删除 <code>/swapfile swap swap defaults 0 0</code> 这一行。</p>
<p>最后删除 <code>/swapfile</code> 这个文件：</p>
<pre><code class="language-bash">sudo rm /swapfile
</code></pre>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2022-12-21T02:38:29.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Nginx 使用 split_clients 进行简易 A/B 测试]]></title>
        <id>https://u.sb/nginx-ab-testing/</id>
        <link href="https://u.sb/nginx-ab-testing/"/>
        <updated>2022-07-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将介绍在 Nginx 配置简易 A/B 测试的姿势。]]></summary>
        <content type="html"><![CDATA[<p>本文将介绍在 Nginx 配置简易 A/B 测试的姿势。</p>
<h2>背景前提</h2>
<p>有时候我们需要简单的做一些 A/B 测试，并不需要复杂的判断条件，这时候我们可以用到 Nginx 的 <a href="https://nginx.org/en/docs/http/ngx_http_split_clients_module.html">ngx_http_split_clients_module</a> 模块。</p>
<h2>安装 ngx_http_split_clients_module 模块</h2>
<p>一般来说这个模块已经自带，如果没有的话推荐安装我们打包的 <a href="https://n.wtf/">N.WTF</a></p>
<h2>配置 Nginx</h2>
<p>这里举例，我们想要 <code>20%</code> 的用户跳转到网址 <code>https://example.com/</code>，<code>30%</code> 的用户跳转到网址 <code>https://example.org/</code>，剩下的跳转到网址 <code>https://examle.edu/</code>：</p>
<pre><code class="language-nginx">split_clients &quot;${remote_addr}AAA&quot; $variant {
    20%               https://example.com/;
    30%               https://example.org/;
    *                 https://example.edu/;
}

server {
    listen 80;
    listen [::]:80;
    server_name _;

    return 302 ${variant};
}
</code></pre>
<p>上述例子中，按照访客请求的 <code>IP 地址</code> 加上 <code>AAA 字符串</code> 会使用 <a href="https://en.wikipedia.org/wiki/MurmurHash#MurmurHash2">MurmurHash2</a> 转换成数字，如果得出的数字在前 <code>20%</code>，那么 <code>$variant</code> 值为 <code>https://example.com/</code>，相应的在中间 <code>30%</code> 区间的值为 <code>https://example.org/</code>，其他的为 <code>https://example.edu/</code>。</p>
<p>然后我们找两台不同 IP 的机器进行测试：</p>
<p>机器 A：</p>
<pre><code class="language-bash">root@debian ~ # curl -I 192.0.2.2
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Sat, 02 Jul 2022 20:51:43 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Location: https://example.com/
</code></pre>
<p>机器 B：</p>
<pre><code class="language-bash">root@debian ~ #  curl -I 192.0.2.2                         
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Sat, 02 Jul 2022 20:52:12 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Location: https://example.org/
</code></pre>
<p>然后就可以有更灵活的用法，比如指定不同的目录：</p>
<pre><code class="language-nginx">root /var/www/${variant};
</code></pre>
<p>指定不同的首页：</p>
<pre><code class="language-nginx">index index-${variant}.html;
</code></pre>
<p>读者可以举一反三，这个比较简易的 A/B 测试就完成啦～</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2022-07-02T21:04:50.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[KDE Plasma 关闭单击打开文件或文件夹]]></title>
        <id>https://u.sb/archlinux-kde-disable-single-click/</id>
        <link href="https://u.sb/archlinux-kde-disable-single-click/"/>
        <updated>2022-07-01T08:00:00.000Z</updated>
        <summary type="html"><![CDATA[本文将指导在 KDE Plasma 下关闭单击打开文件或文件夹。]]></summary>
        <content type="html"><![CDATA[<p>本文将指导在 KDE Plasma 下关闭单击打开文件或文件夹。</p>
<h2>前言</h2>
<p>对于从 Windows 或 macOS 迁移到 Linux 的用户，需要花费很多时间来适应各种不习惯的操作，在 <a href="https://kde.org/plasma-desktop/">KDE Plasma</a> 下，有一个默认设置，单击即可打开某个文件或者文件夹，这让很多用户感到非常不适应。</p>
<h2>解决方法</h2>
<p>参考<a href="https://forum.kde.org/viewtopic.php?t=128669">这个帖子</a>，打开 <code>System Settings</code> -&gt; <code>Workspace Behavior</code> -&gt; <code>General Behavior</code>，然后找到 <code>Clicking files or folders</code>，把默认的 <code>Opens them</code> 改成 <code>Selects them</code>：</p>
<p><img src="https://s.bh.sb/uploads/2022/07/02/nRCidrQz7qZNEPT.png" alt="KDE Plasma 关闭单击打开文件"></p>
<p>好了，解决了这个非常不习惯的操作。</p>
]]></content>
        <author>
            <name>Showfom</name>
            <email>i@m.ac</email>
            <uri>https://u.sb/</uri>
        </author>
        <published>2022-07-01T16:36:53.000Z</published>
    </entry>
</feed>