<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>架构部署 on 寻 Mi 处</title>
        <link>https://blog.mibase.top/categories/%E6%9E%B6%E6%9E%84%E9%83%A8%E7%BD%B2/</link>
        <description>Recent content in 架构部署 on 寻 Mi 处</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Wed, 11 Feb 2026 11:20:00 +0800</lastBuildDate><atom:link href="https://blog.mibase.top/categories/%E6%9E%B6%E6%9E%84%E9%83%A8%E7%BD%B2/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>受够了密码管理器的订阅费，我自己搭了一个</title>
            <link>https://blog.mibase.top/p/%E5%8F%97%E5%A4%9F%E4%BA%86%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8%E7%9A%84%E8%AE%A2%E9%98%85%E8%B4%B9%E6%88%91%E8%87%AA%E5%B7%B1%E6%90%AD%E4%BA%86%E4%B8%80%E4%B8%AA/</link>
            <pubDate>Wed, 11 Feb 2026 11:20:00 +0800</pubDate>
            <guid>https://blog.mibase.top/p/%E5%8F%97%E5%A4%9F%E4%BA%86%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8%E7%9A%84%E8%AE%A2%E9%98%85%E8%B4%B9%E6%88%91%E8%87%AA%E5%B7%B1%E6%90%AD%E4%BA%86%E4%B8%80%E4%B8%AA/</guid>
            <description>&lt;img src=&#34;https://blog.mibase.top/p/%E5%8F%97%E5%A4%9F%E4%BA%86%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8%E7%9A%84%E8%AE%A2%E9%98%85%E8%B4%B9%E6%88%91%E8%87%AA%E5%B7%B1%E6%90%AD%E4%BA%86%E4%B8%80%E4%B8%AA/cover.jpg&#34; alt=&#34;Featured image of post 受够了密码管理器的订阅费，我自己搭了一个&#34; /&gt;&lt;p&gt;我用了三年某密码管理器的付费版。&lt;/p&gt;&#xA;&lt;p&gt;涨价那次我忍了，从每年三十多美元涨到四十多。去年又涨，我看着账单发了会儿呆，然后打开了它的定价页。&lt;/p&gt;&#xA;&lt;p&gt;家庭版六个人，一年六十美元。我家里真正会用密码管理器的只有我和我老婆两个人。&lt;/p&gt;&#xA;&lt;p&gt;那天晚上我就在看有没有自建的方案。Vaultwarden 是第一个跳出来的，因为它的定位很明确，就是 Bitwarden 服务端的轻量实现，用 Rust 写的，官方服务端要好几 G 内存，它几十兆就能跑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;它和-bitwarden-是什么关系&#34;&gt;它和 Bitwarden 是什么关系&#xA;&lt;/h2&gt;&lt;p&gt;先说清楚，Vaultwarden 不是 Bitwarden 的官方项目，它是一个兼容实现。&lt;/p&gt;&#xA;&lt;p&gt;它实现了 Bitwarden 的服务端 API，所以官方的所有客户端都能连，浏览器插件、桌面端、手机 App、命令行工具，全都行。你只要在登录界面把服务器地址改成自己的域名。&lt;/p&gt;&#xA;&lt;p&gt;它的好处是轻。官方服务端是一整套微服务，跑起来要 SQL Server 加一堆容器，没有 4G 内存根本带不动。Vaultwarden 一个二进制文件加 SQLite 就能跑，1G 的机器绰绰有余。&lt;/p&gt;&#xA;&lt;p&gt;代价是它不是官方维护的，某些新功能会滞后。比如 Bitwarden 前阵子推的 passkey 支持，Vaultwarden 过了一段时间才跟上。不过核心的密码管理功能一直很稳。&lt;/p&gt;&#xA;&lt;h2 id=&#34;部署&#34;&gt;部署&#xA;&lt;/h2&gt;&lt;p&gt;我用的是 Docker Compose，目录放在 &lt;code&gt;/srv/vaultwarden&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;vaultwarden&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;vaultwarden/server:1.32.7&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;container_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;vaultwarden&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;restart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;unless-stopped&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;DOMAIN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;https://vault.mibase.top&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SIGNUPS_ALLOWED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;false&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;INVITATIONS_ALLOWED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ADMIN_TOKEN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;${ADMIN_TOKEN}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/data/db.sqlite3&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ROCKET_PORT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;80&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;TZ&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Asia/Shanghai&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./data:/data&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;127.0.0.1:8088:80&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;几个关键点。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;DOMAIN&lt;/code&gt; 必须写对，而且必须是 https。它是用来生成邮件里的链接、WebAuthn 的 origin 这些东西的。写错了，注册邀请链接会指向错误的地址。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;SIGNUPS_ALLOWED: &amp;quot;false&amp;quot;&lt;/code&gt; 是关掉公开注册。不关的话，任何人只要知道你的域名就能注册账号，虽然数据是隔离的，但白占你的资源，还有被滥用的风险。&lt;/p&gt;&#xA;&lt;p&gt;关掉之后，加人要用邀请。&lt;code&gt;INVITATIONS_ALLOWED: &amp;quot;true&amp;quot;&lt;/code&gt; 是开着邀请功能，你可以在管理后台生成邀请链接。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;ADMIN_TOKEN&lt;/code&gt; 是管理后台的密码。这个值不要写死在 compose 文件里，用环境变量从 &lt;code&gt;.env&lt;/code&gt; 文件读。生成方法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;openssl rand -base64 &lt;span class=&#34;m&#34;&gt;48&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ports&lt;/code&gt; 只绑 &lt;code&gt;127.0.0.1&lt;/code&gt;，不对外暴露。外面通过 nginx 反代进来，这样容器端口不会被直接扫到。&lt;/p&gt;&#xA;&lt;h2 id=&#34;nginx-反代&#34;&gt;nginx 反代&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;ssl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;http2&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;vault.mibase.top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate&lt;/span&gt;     &lt;span class=&#34;s&#34;&gt;/etc/letsencrypt/live/vault.mibase.top/fullchain.pem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;ssl_certificate_key&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/etc/letsencrypt/live/vault.mibase.top/privkey.pem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;client_max_body_size&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;128M&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_pass&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://127.0.0.1:8088&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Host&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Real-IP&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$remote_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$scheme&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Upgrade&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Connection&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;upgrade&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;client_max_body_size 128M&lt;/code&gt; 是给附件上传用的。Bitwarden 支持存文件附件，默认的上传限制是一兆，不改的话传大一点的附件会失败。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;Upgrade&lt;/code&gt; 和 &lt;code&gt;Connection&lt;/code&gt; 那两个头是给 WebSocket 用的。Bitwarden 的客户端会用 WebSocket 做实时同步，不配这个，同步会有延迟。&lt;/p&gt;&#xA;&lt;h2 id=&#34;证书&#34;&gt;证书&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot --nginx -d vault.mibase.top&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;certbot 会自动改 nginx 配置，加上证书路径和 http 到 https 的跳转。&lt;/p&gt;&#xA;&lt;p&gt;续期是自动的，systemd timer 会每天检查。可以手动测一下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot renew --dry-run&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这个一定要测。我见过有人配完就没管，一年后证书过期，所有客户端都连不上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;首次使用&#34;&gt;首次使用&#xA;&lt;/h2&gt;&lt;p&gt;浏览器打开 &lt;code&gt;https://vault.mibase.top&lt;/code&gt;，会看到 Bitwarden 的登录页。点&amp;quot;创建账户&amp;quot;，注册第一个账号。&lt;/p&gt;&#xA;&lt;p&gt;注册完之后，立刻去管理后台把注册关掉。虽然前面已经在环境变量里关了，但保险起见再看一眼：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;https://vault.mibase.top/admin&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;输入 &lt;code&gt;ADMIN_TOKEN&lt;/code&gt; 进去，找到 &lt;code&gt;SIGNUPS_ALLOWED&lt;/code&gt;，确认是 false。&lt;/p&gt;&#xA;&lt;p&gt;管理后台里还能看到用户列表、邀请、诊断信息。有个&amp;quot;发送测试邮件&amp;quot;的按钮，配了 SMTP 之后可以用来验证。&lt;/p&gt;&#xA;&lt;h2 id=&#34;邮件&#34;&gt;邮件&#xA;&lt;/h2&gt;&lt;p&gt;不配 SMTP 也能用，但有些功能会受限。比如忘记主密码的重置邮件、新设备登录的验证邮件、邀请链接的邮件发送。&lt;/p&gt;&#xA;&lt;p&gt;我的配置：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_HOST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;smtp.example.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_FROM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vault@mibase.top&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_FROM_NAME&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Vaultwarden&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_PORT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;587&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_SECURITY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;starttls&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_USERNAME&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vault@mibase.top&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SMTP_PASSWORD&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;${SMTP_PASSWORD}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;我用的是某个邮件服务商的免费额度，一个月五百封，够用了。&lt;/p&gt;&#xA;&lt;p&gt;有个细节，&lt;code&gt;SMTP_SECURITY&lt;/code&gt; 的值是 &lt;code&gt;starttls&lt;/code&gt; 或者 &lt;code&gt;force_tls&lt;/code&gt;，不是 &lt;code&gt;tls&lt;/code&gt;。写错了它不会报错，只是发不出去，日志里也看不太出来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;客户端怎么连&#34;&gt;客户端怎么连&#xA;&lt;/h2&gt;&lt;p&gt;浏览器插件和手机 App 的登录界面，都有一个设置服务器的入口。&lt;/p&gt;&#xA;&lt;p&gt;浏览器插件里，在登录框下面有个齿轮图标，点进去能填&amp;quot;自托管环境&amp;quot;的服务器 URL。&lt;/p&gt;&#xA;&lt;p&gt;手机 App 上，登录页左上角有个齿轮，点进去填。&lt;/p&gt;&#xA;&lt;p&gt;填 &lt;code&gt;https://vault.mibase.top&lt;/code&gt;，不要带 &lt;code&gt;/admin&lt;/code&gt; 或者别的路径。&lt;/p&gt;&#xA;&lt;p&gt;填完之后回登录页，用刚才注册的账号登录。如果之前用过官方服务，本地已经有数据，会提示你选择&amp;quot;登录到另一个服务器&amp;quot;还是&amp;quot;迁移&amp;quot;。选迁移的话，本地数据会同步到新服务器。&lt;/p&gt;&#xA;&lt;h2 id=&#34;数据在哪儿&#34;&gt;数据在哪儿&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/srv/vaultwarden/data/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── db.sqlite3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── db.sqlite3-wal&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── db.sqlite3-shm&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── attachments/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── sends/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── rsa_key.pem&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── rsa_key.pub.pem&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── icon_cache/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;db.sqlite3&lt;/code&gt; 是主数据库，所有的密码条目、用户信息都在里面。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;rsa_key.pem&lt;/code&gt; 是服务端的私钥，用来签名 JWT。这个文件丢了，所有用户都得重新登录。所以备份的时候一定要带上。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;attachments&lt;/code&gt; 是附件，&lt;code&gt;sends&lt;/code&gt; 是临时分享的文件。&lt;/p&gt;&#xA;&lt;h2 id=&#34;备份&#34;&gt;备份&#xA;&lt;/h2&gt;&lt;p&gt;因为用的是 SQLite，直接拷贝文件是不安全的，可能有正在写的事务。&lt;/p&gt;&#xA;&lt;p&gt;正确的做法是用 SQLite 的备份命令：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker &lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; vaultwarden sqlite3 /data/db.sqlite3 &lt;span class=&#34;s2&#34;&gt;&amp;#34;.backup &amp;#39;/data/backup.sqlite3&amp;#39;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这个命令会在数据库层面做一次一致性备份，不会因为并发写入出错。&lt;/p&gt;&#xA;&lt;p&gt;然后打包：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tar -czf /backup/vaultwarden-&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;date +%F&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;.tar.gz &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -C /srv/vaultwarden/data &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  --exclude&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;icon_cache&amp;#39;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  db.sqlite3 backup.sqlite3 attachments sends rsa_key.pem&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;icon_cache&lt;/code&gt; 是网站图标缓存，没必要备份，重新访问的时候会自动下载。&lt;/p&gt;&#xA;&lt;p&gt;我写了个脚本，每天凌晨跑一次，保留三十天，同步到对象存储。具体写法在另一篇备份的文章里。&lt;/p&gt;&#xA;&lt;h2 id=&#34;恢复&#34;&gt;恢复&#xA;&lt;/h2&gt;&lt;p&gt;这个流程一定要演练过。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose down&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rm -rf data/*&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;tar -xzf /backup/vaultwarden-2026-02-10.tar.gz -C data/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mv data/backup.sqlite3 data/db.sqlite3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose up -d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意要先 &lt;code&gt;down&lt;/code&gt; 再恢复文件，不然容器还在跑，恢复的文件会被覆盖。&lt;/p&gt;&#xA;&lt;p&gt;我第一次演练的时候就是忘了停容器，恢复完发现数据还是旧的，还以为是备份有问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;安全上要注意的&#34;&gt;安全上要注意的&#xA;&lt;/h2&gt;&lt;p&gt;这个服务存的是你最敏感的东西，安全得多花点心思。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;ADMIN_TOKEN&lt;/code&gt; 要够长，至少四十八个字符的随机串。这个 token 泄露了，别人能改你的配置。&lt;/p&gt;&#xA;&lt;p&gt;不要开公开注册。即使开了，也要配验证码和邮件验证，否则会被当成免费的密码库来用。&lt;/p&gt;&#xA;&lt;p&gt;HTTPS 是必须的，不是可选项。客户端的很多功能（WebAuthn、剪贴板 API）在 http 下根本不能用。&lt;/p&gt;&#xA;&lt;p&gt;考虑加一层 Cloudflare Access，只允许自己的邮箱访问。不过这会影响到手机 App 的连接，因为 App 不方便走浏览器登录。我自己没加，因为 Vaultwarden 本身有登录验证，风险可控。&lt;/p&gt;&#xA;&lt;h2 id=&#34;用下来的感受&#34;&gt;用下来的感受&#xA;&lt;/h2&gt;&lt;p&gt;到现在用了四个月，手机、电脑、浏览器插件都连着，同步很稳。&lt;/p&gt;&#xA;&lt;p&gt;速度比官方服务快，因为服务器就在国内，延迟只有几十毫秒。官方服务在国内访问有时候要绕很久。&lt;/p&gt;&#xA;&lt;p&gt;有一个小遗憾，它没有官方的&amp;quot;紧急访问&amp;quot;功能（就是你挂了之后让别人拿到你的密码库）。社区有替代方案，但要额外配。&lt;/p&gt;&#xA;&lt;p&gt;还有一个，自托管意味着你得自己保证它一直活着。如果服务器挂了，你又刚好需要查一个密码，那就尴尬了。所以我把重要的几个密码（邮箱、银行、主密码）另外抄了一份在纸上，放在家里。&lt;/p&gt;&#xA;&lt;h2 id=&#34;值不值&#34;&gt;值不值&#xA;&lt;/h2&gt;&lt;p&gt;一年省六十美元，折合人民币四百多。花的时间大概是一个周末。&lt;/p&gt;&#xA;&lt;p&gt;如果只看钱，不太值。但这四个月里，我对自己的密码到底存在哪儿、怎么加密的、备份在什么地方，有了完全的掌控。这种确定感是订阅服务给不了的。&lt;/p&gt;&#xA;&lt;p&gt;当然也有代价。服务器挂了是我的责任，数据丢了我的责任，被人攻破了也是我的责任。用别人的服务，这些责任是别人担的。&lt;/p&gt;&#xA;&lt;p&gt;这个取舍每个人不一样。我的选择是，愿意为掌控感承担这份责任。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>再也不用 scp 传文件了，push 一下博客就上线</title>
            <link>https://blog.mibase.top/p/%E5%86%8D%E4%B9%9F%E4%B8%8D%E7%94%A8-scp-%E4%BC%A0%E6%96%87%E4%BB%B6%E4%BA%86push-%E4%B8%80%E4%B8%8B%E5%8D%9A%E5%AE%A2%E5%B0%B1%E4%B8%8A%E7%BA%BF/</link>
            <pubDate>Thu, 12 Dec 2024 04:58:00 +0800</pubDate>
            <guid>https://blog.mibase.top/p/%E5%86%8D%E4%B9%9F%E4%B8%8D%E7%94%A8-scp-%E4%BC%A0%E6%96%87%E4%BB%B6%E4%BA%86push-%E4%B8%80%E4%B8%8B%E5%8D%9A%E5%AE%A2%E5%B0%B1%E4%B8%8A%E7%BA%BF/</guid>
            <description>&lt;img src=&#34;https://blog.mibase.top/p/%E5%86%8D%E4%B9%9F%E4%B8%8D%E7%94%A8-scp-%E4%BC%A0%E6%96%87%E4%BB%B6%E4%BA%86push-%E4%B8%80%E4%B8%8B%E5%8D%9A%E5%AE%A2%E5%B0%B1%E4%B8%8A%E7%BA%BF/cover.jpg&#34; alt=&#34;Featured image of post 再也不用 scp 传文件了，push 一下博客就上线&#34; /&gt;&lt;p&gt;以前写博客的流程是这样的。&lt;/p&gt;&#xA;&lt;p&gt;本地写完，&lt;code&gt;hugo&lt;/code&gt; 构建，然后用 FileZilla 把 &lt;code&gt;public&lt;/code&gt; 目录拖到服务器上。拖的时候还不能直接覆盖，得先删掉旧的，再传新的，不然会残留一堆已经删掉的文章。&lt;/p&gt;&#xA;&lt;p&gt;有几次传了一半网断了，网站变成了半新半旧的状态，首页能打开，点进去文章 404。还有一次拖错了目录，把 &lt;code&gt;public&lt;/code&gt; 传到了上一级，整站挂了半小时。&lt;/p&gt;&#xA;&lt;p&gt;后来我改用 rsync，好一些，但还是要手动敲命令。再后来加了 &lt;code&gt;--delete&lt;/code&gt;，又差点把服务器上别的目录删了。&lt;/p&gt;&#xA;&lt;p&gt;这种手动流程，出问题是迟早的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;换个思路&#34;&gt;换个思路&#xA;&lt;/h2&gt;&lt;p&gt;其实我要的事情很简单。本地 &lt;code&gt;git push&lt;/code&gt; 之后，服务器上自动更新网站。&lt;/p&gt;&#xA;&lt;p&gt;Git 本身就能做这件事。服务器上建一个裸仓库，配置一个 &lt;code&gt;post-receive&lt;/code&gt; 钩子，每次收到 push 就执行一段脚本。脚本里做的事情就是把代码检出到工作目录，然后构建。&lt;/p&gt;&#xA;&lt;p&gt;这个方案的好处是不依赖任何外部服务。不用 GitHub Actions，不用 Jenkins，不用装一堆东西。一台有 git 的机器就能跑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;服务器上建裸仓库&#34;&gt;服务器上建裸仓库&#xA;&lt;/h2&gt;&lt;p&gt;登录服务器：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ssh blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;建目录：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mkdir -p /srv/git/blog.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chown -R git:git /srv/git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这里我专门建了个 &lt;code&gt;git&lt;/code&gt; 用户，不用 root，也不用我自己的账号。原因后面说。&lt;/p&gt;&#xA;&lt;p&gt;初始化裸仓库：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u git git init --bare /srv/git/blog.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--bare&lt;/code&gt; 的意思是只有 git 数据，没有工作区。你不能在这个目录里直接看到文件，它是纯粹用来接收 push 的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;建一个工作目录&#34;&gt;建一个工作目录&#xA;&lt;/h2&gt;&lt;p&gt;网站的实际文件要放在另一个地方，比如 &lt;code&gt;/srv/www/blog&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mkdir -p /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chown -R git:git /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意属主是 &lt;code&gt;git&lt;/code&gt;，因为钩子是以 &lt;code&gt;git&lt;/code&gt; 用户的身份跑的。如果属主是 root，钩子里的写操作会失败。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写钩子&#34;&gt;写钩子&#xA;&lt;/h2&gt;&lt;p&gt;钩子的位置是固定的：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo -u git nano /srv/git/blog.git/hooks/post-receive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;内容：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -euo pipefail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;WORK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;GIT_DIR&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;/srv/git/blog.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; oldrev newrev refname&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$refname&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;refs/heads/main&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;==&amp;gt; 部署 main 分支到 &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    git --work-tree&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; --git-dir&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$GIT_DIR&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; checkout -f main&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hugo --minify --gc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;==&amp;gt; 部署完成&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;给执行权限：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod +x /srv/git/blog.git/hooks/post-receive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;while read oldrev newrev refname&lt;/code&gt; 这一段是标准写法。push 的时候 git 会把每个被更新的引用通过标准输入传给你，格式是三列。只处理 &lt;code&gt;main&lt;/code&gt; 分支，别的分支推上来不触发部署。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;checkout -f&lt;/code&gt; 里的 &lt;code&gt;-f&lt;/code&gt; 很重要。它会强制覆盖工作区里的文件，不管有没有本地修改。不加这个，第二次部署就会因为文件冲突失败。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;hugo --minify --gc&lt;/code&gt; 是构建。&lt;code&gt;--minify&lt;/code&gt; 压缩 html、css、js，&lt;code&gt;--gc&lt;/code&gt; 清理掉不再引用的缓存资源。这两个参数在 Hugo 0.115 之后都还在用。&lt;/p&gt;&#xA;&lt;h2 id=&#34;本地配置远程&#34;&gt;本地配置远程&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git remote add deploy ssh://git@blog.mibase.top/srv/git/blog.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;或者用 scp 风格的写法：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git remote add deploy git@blog.mibase.top:/srv/git/blog.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;两种都行，前者更明确一点。&lt;/p&gt;&#xA;&lt;p&gt;然后：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git push deploy main&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;第一次推的时候，终端里应该能看到钩子输出的那几行。&lt;/p&gt;&#xA;&lt;h2 id=&#34;权限这块是最大的坑&#34;&gt;权限这块是最大的坑&#xA;&lt;/h2&gt;&lt;p&gt;我一开始用的是自己的账号，没建 &lt;code&gt;git&lt;/code&gt; 用户。结果钩子跑起来各种 Permission denied。&lt;/p&gt;&#xA;&lt;p&gt;原因是 SSH 登录的用户和钩子执行脚本的用户必须是同一个，而且这个用户要对工作目录有写权限，对 nginx 的目录也要有读权限。&lt;/p&gt;&#xA;&lt;p&gt;后来我理清了：&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;git&lt;/code&gt; 用户拥有裸仓库和工作目录。nginx 以 &lt;code&gt;www-data&lt;/code&gt; 身份跑，需要有读权限。所以我把工作目录的组设成 &lt;code&gt;www-data&lt;/code&gt;，权限给到 750：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chown -R git:www-data /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod -R &lt;span class=&#34;m&#34;&gt;750&lt;/span&gt; /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo usermod -aG www-data git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这样 &lt;code&gt;git&lt;/code&gt; 用户能写，&lt;code&gt;www-data&lt;/code&gt; 能读，别人进不来。&lt;/p&gt;&#xA;&lt;p&gt;还有一个 SELinux 的问题。如果你用的是 CentOS 或者 RHEL，默认的 SELinux 策略会阻止 nginx 读 &lt;code&gt;/srv&lt;/code&gt; 下面的文件。得打标签：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo semanage fcontext -a -t httpd_sys_content_t &lt;span class=&#34;s2&#34;&gt;&amp;#34;/srv/www/blog(/.*)?&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo restorecon -Rv /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Debian 和 Ubuntu 没这个烦恼。&lt;/p&gt;&#xA;&lt;h2 id=&#34;只给-git-用户最小权限&#34;&gt;只给 git 用户最小权限&#xA;&lt;/h2&gt;&lt;p&gt;用 &lt;code&gt;git&lt;/code&gt; 用户有个额外的好处，就是可以限制它能做的事。&lt;/p&gt;&#xA;&lt;p&gt;编辑 &lt;code&gt;/etc/passwd&lt;/code&gt;，把这个用户的 shell 从 &lt;code&gt;/bin/bash&lt;/code&gt; 改成 &lt;code&gt;/usr/bin/git-shell&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git:x:998:998::/srv/git:/usr/bin/git-shell&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;git-shell&lt;/code&gt; 是一个受限的 shell，只允许 git 相关的命令，别的命令一律拒绝。这样就算有人拿到了这个账号的私钥，也登不进交互式的 shell，只能在 git 协议里操作。&lt;/p&gt;&#xA;&lt;p&gt;这个设置很值得做。我之前的机器被人扫过 SSH 密码，虽然没成功，但那之后我就把能限制的地方都限制了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;nginx-那边&#34;&gt;nginx 那边&#xA;&lt;/h2&gt;&lt;p&gt;配置里 root 指到工作目录的 public：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;443&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;ssl&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;blog.mibase.top&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/srv/www/blog/public&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;try_files&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;404&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Hugo 构建完之后，静态文件在 &lt;code&gt;public&lt;/code&gt; 子目录里，所以 root 要指到那一层。&lt;/p&gt;&#xA;&lt;h2 id=&#34;增量构建的问题&#34;&gt;增量构建的问题&#xA;&lt;/h2&gt;&lt;p&gt;如果博客文章多了，每次全量构建会比较慢。Hugo 支持增量：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hugo --minify --gc --buildDrafts&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;不过增量构建有它自己的问题，改了模板或者配置之后，有时候不会重新生成所有页面，导致部分页面还是旧的。我的经验是，日常发文用增量，改主题或者配置的时候手动跑一次全量。&lt;/p&gt;&#xA;&lt;p&gt;Hugo 0.123 之后引入了一个 &lt;code&gt;--renderToMemory&lt;/code&gt; 的选项，构建更快，但对大站内存要求高。我这种几十篇文章的站感觉不出差别。&lt;/p&gt;&#xA;&lt;h2 id=&#34;多个站点的做法&#34;&gt;多个站点的做法&#xA;&lt;/h2&gt;&lt;p&gt;后来我又加了一个笔记站，用的是同一套方案。&lt;/p&gt;&#xA;&lt;p&gt;裸仓库和钩子都是独立的，互不干扰。唯一要改的是钩子里的路径和构建命令。&lt;/p&gt;&#xA;&lt;p&gt;我把钩子的公共部分抽成了一个脚本，放在 &lt;code&gt;/srv/git/deploy.sh&lt;/code&gt;，两个钩子都去调它，传不同的参数：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/bin/bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -euo pipefail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;WORK&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;BRANCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;:-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; oldrev newrev refname&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$refname&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;refs/heads/&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BRANCH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    git --work-tree&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; --git-dir&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$GIT_DIR&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; checkout -f &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$BRANCH&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$WORK&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hugo --minify --gc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;GIT_DIR&lt;/code&gt; 是 git 在执行钩子时自动设的环境变量，不用自己传。&lt;/p&gt;&#xA;&lt;h2 id=&#34;出问题怎么回滚&#34;&gt;出问题怎么回滚&#xA;&lt;/h2&gt;&lt;p&gt;这个方案有个小遗憾，它没有版本回退的功能。工作目录里永远是 main 分支的最新内容。&lt;/p&gt;&#xA;&lt;p&gt;要回滚的话，有两个办法。&lt;/p&gt;&#xA;&lt;p&gt;一个是在本地 revert 一个 commit，再 push 一次。这个最干净，历史是线性的。&lt;/p&gt;&#xA;&lt;p&gt;另一个是直接在服务器上操作：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; /srv/www/blog&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git log --oneline -5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git checkout &amp;lt;COMMIT_ID&amp;gt; -- .&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hugo --minify --gc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;但这样工作目录会和裸仓库脱节，下次 push 的时候 &lt;code&gt;checkout -f&lt;/code&gt; 会把你的手动修改全冲掉。所以我一般用第一种。&lt;/p&gt;&#xA;&lt;h2 id=&#34;什么时候该换个方案&#34;&gt;什么时候该换个方案&#xA;&lt;/h2&gt;&lt;p&gt;这套方案适合个人博客，不适合团队协作。&lt;/p&gt;&#xA;&lt;p&gt;如果有多个人一起维护，你可能会想要在 push 之后跑测试、跑 lint、生成预览链接。这些用 GitHub Actions 或者 Gitea 的 CI 会方便很多。&lt;/p&gt;&#xA;&lt;p&gt;还有一种是构建时间很长的站。我见过有人 Hugo 站有几千篇文章，全量构建要三分钟。这种情况下每次 push 都要等三分钟，体验不好，得考虑缓存或者增量。&lt;/p&gt;&#xA;&lt;p&gt;我现在的站只有几十篇，构建三秒，用这套方案正合适。&lt;/p&gt;&#xA;&lt;h2 id=&#34;现在的感受&#34;&gt;现在的感受&#xA;&lt;/h2&gt;&lt;p&gt;最大的变化是心理上的。&lt;/p&gt;&#xA;&lt;p&gt;以前想写点东西，会先想到&amp;quot;还得传文件&amp;quot;，这个念头本身就会让人拖延。现在打开编辑器就写，写完 push，完事。中间那一步消失了，写东西的阻力小了很多。&lt;/p&gt;&#xA;&lt;p&gt;这种小工具的收益，往往不在技术本身，而在它把你从一件烦人的事情里解放出来。省下的那几分钟不重要，重要的是你不再因为烦而逃避。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>家里那台小主机没有公网 IP，我用 Cloudflare Tunnel 把它挂上了网</title>
            <link>https://blog.mibase.top/p/%E5%AE%B6%E9%87%8C%E9%82%A3%E5%8F%B0%E5%B0%8F%E4%B8%BB%E6%9C%BA%E6%B2%A1%E6%9C%89%E5%85%AC%E7%BD%91-ip%E6%88%91%E7%94%A8-cloudflare-tunnel-%E6%8A%8A%E5%AE%83%E6%8C%82%E4%B8%8A%E4%BA%86%E7%BD%91/</link>
            <pubDate>Thu, 29 Feb 2024 19:44:00 +0800</pubDate>
            <guid>https://blog.mibase.top/p/%E5%AE%B6%E9%87%8C%E9%82%A3%E5%8F%B0%E5%B0%8F%E4%B8%BB%E6%9C%BA%E6%B2%A1%E6%9C%89%E5%85%AC%E7%BD%91-ip%E6%88%91%E7%94%A8-cloudflare-tunnel-%E6%8A%8A%E5%AE%83%E6%8C%82%E4%B8%8A%E4%BA%86%E7%BD%91/</guid>
            <description>&lt;img src=&#34;https://blog.mibase.top/p/%E5%AE%B6%E9%87%8C%E9%82%A3%E5%8F%B0%E5%B0%8F%E4%B8%BB%E6%9C%BA%E6%B2%A1%E6%9C%89%E5%85%AC%E7%BD%91-ip%E6%88%91%E7%94%A8-cloudflare-tunnel-%E6%8A%8A%E5%AE%83%E6%8C%82%E4%B8%8A%E4%BA%86%E7%BD%91/cover.jpg&#34; alt=&#34;Featured image of post 家里那台小主机没有公网 IP，我用 Cloudflare Tunnel 把它挂上了网&#34; /&gt;&lt;p&gt;搬进新租的房子之后，第一件让我难受的事就是宽带没有公网 IP。&lt;/p&gt;&#xA;&lt;p&gt;装机师傅上门那天我特意问了，他说现在都这样，上面统一做的 NAT，想开端口得另外找客服申请，而且就算申请下来也是动态的，重启光猫就换一个。我试着在路由器上拨号看了一下，WAN 口拿到的是 &lt;code&gt;100.64.x.x&lt;/code&gt;，运营商级 NAT 的内网地址，彻底没戏。&lt;/p&gt;&#xA;&lt;p&gt;问题是我家里有台常年开着的小主机，跑着几个自己写的小服务。一个是用来看照片的相册，一个是给家里人用的备忘录，还有一个是给小孩做的手工课视频站。以前在旧房子那边有公网 IP，路由器上做个端口映射，出门在外随时能打开。现在全断了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;试过的几种方案&#34;&gt;试过的几种方案&#xA;&lt;/h2&gt;&lt;p&gt;先说被排除的。&lt;/p&gt;&#xA;&lt;p&gt;花生壳那类内网穿透我最早试过，免费版带宽小得可怜，而且域名是一长串随机字符，发给家里人他们根本记不住。付费版按年收费，我看了一眼价格，不如直接买台有公网 IP 的 VPS。&lt;/p&gt;&#xA;&lt;p&gt;frp 我用了差不多一年。自己在香港买了台 1 核 1G 的小机器当跳板，家里跑 frpc，外面跑 frps。能用，但有几个麻烦。跳板机的带宽是个硬瓶颈，我买的那台是 30M 峰值，实际晚上跑起来经常掉到 5M 以下，看个视频就卡。而且这台机器我得自己维护，系统更新、证书续期、被扫了还得上去看看，一年下来也是一笔精力。&lt;/p&gt;&#xA;&lt;p&gt;后来在 V2EX 上看到有人提 Cloudflare Tunnel，我一开始以为是那种要企业套餐的付费功能，点进去看了才知道，免费版也能用，而且不限流量。这个就值得试了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;它到底是怎么工作的&#34;&gt;它到底是怎么工作的&#xA;&lt;/h2&gt;&lt;p&gt;原理说起来很简单。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;cloudflared&lt;/code&gt; 这个程序装在你的内网机器上，它主动往外连 Cloudflare 的边缘节点，建立一条长连接。这条连接是出站的，所以你不需要有公网 IP，也不需要动路由器上的任何设置。外面的人访问你的域名，请求先到 Cloudflare，Cloudflare 顺着这条已经建好的隧道把请求送进来，你的机器响应之后再原路返回。&lt;/p&gt;&#xA;&lt;p&gt;因为隧道是 &lt;code&gt;cloudflared&lt;/code&gt; 主动发起的，中间不存在任何入站端口，家里的防火墙和运营商的封禁都不受影响。&lt;/p&gt;&#xA;&lt;p&gt;还有个好处是，你不需要在路由器上开任何端口。以前做端口映射的时候，我总担心哪天路由器固件有漏洞被人扫到，现在这条路直接堵死了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;装起来&#34;&gt;装起来&#xA;&lt;/h2&gt;&lt;p&gt;我的小主机是 Debian 12，直接加官方源装最省事。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo mkdir -p --mode&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0755&lt;/span&gt; /usr/share/keyrings&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee /usr/share/keyrings/cloudflare-main.gpg &amp;gt;/dev/null&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee /etc/apt/sources.list.d/cloudflared.list&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt update &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo apt install cloudflared&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;装完 &lt;code&gt;cloudflared --version&lt;/code&gt; 能出版本号就行。&lt;/p&gt;&#xA;&lt;p&gt;接下来登录。这一步会给你一个很长的链接，你得复制到浏览器里打开：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloudflared tunnel login&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;浏览器里会让你选一个域名授权。我的域名在 Cloudflare 上托管，选它，然后它会自动在你的 DNS 里加一条 &lt;code&gt;cert.pem&lt;/code&gt; 用的授权。授权文件落在 &lt;code&gt;~/.cloudflared/cert.pem&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;注意这个 cert 是按域名授权的，一个域名一个文件。如果你有多个域名都要用，得分别登录一次。&lt;/p&gt;&#xA;&lt;h2 id=&#34;创建隧道和配置&#34;&gt;创建隧道和配置&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloudflared tunnel create homelab&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;它会生成一个 UUID，并且在 &lt;code&gt;~/.cloudflared/&lt;/code&gt; 下面放一个同名的 json 文件，里面是隧道的凭据。这个文件是密钥，别传 GitHub 上去。&lt;/p&gt;&#xA;&lt;p&gt;然后写配置文件，位置在 &lt;code&gt;~/.cloudflared/config.yml&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;tunnel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;homelab&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;credentials-file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;/home/mingyue/.cloudflared/8f3c1a2b-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;ingress&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;photo.mibase.top&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;http://127.0.0.1:8081&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;note.mibase.top&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;http://127.0.0.1:8082&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;hostname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;craft.mibase.top&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;http://127.0.0.1:8083&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;service&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;http_status:404&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ingress&lt;/code&gt; 是从上往下匹配的，最后那条兜底必须写，不然配置检查会报错。&lt;/p&gt;&#xA;&lt;p&gt;还有一个细节，如果你内网的服务绑的是 &lt;code&gt;0.0.0.0&lt;/code&gt;，用 &lt;code&gt;127.0.0.1&lt;/code&gt; 也没问题。但如果服务只监听 IPv6，就得写成 &lt;code&gt;http://[::1]:8081&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;把-dns-指过去&#34;&gt;把 DNS 指过去&#xA;&lt;/h2&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloudflared tunnel route dns homelab photo.mibase.top&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloudflared tunnel route dns homelab note.mibase.top&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cloudflared tunnel route dns homelab craft.mibase.top&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;它会自动在 Cloudflare 里加 CNAME，指向 &lt;code&gt;&amp;lt;UUID&amp;gt;.cfargotunnel.com&lt;/code&gt;，并且是橙云状态，也就是走代理。&lt;/p&gt;&#xA;&lt;p&gt;这三条加完，DNS 那块就不用手动动了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;跑成服务&#34;&gt;跑成服务&#xA;&lt;/h2&gt;&lt;p&gt;前台跑着测试没问题之后，换成 systemd 托管：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo cloudflared service install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl &lt;span class=&#34;nb&#34;&gt;enable&lt;/span&gt; --now cloudflared&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;然后看状态：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl status cloudflared&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo journalctl -u cloudflared -f&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;日志里能看到四条连接建起来的信息。Cloudflare 默认会建四条，冗余用的，一条断了另外几条顶上，切换的时候用户基本无感。&lt;/p&gt;&#xA;&lt;h2 id=&#34;访问控制和登录验证&#34;&gt;访问控制和登录验证&#xA;&lt;/h2&gt;&lt;p&gt;隧道跑通之后，第一个冒出来的念头是：这玩意儿是不是谁都能访问。&lt;/p&gt;&#xA;&lt;p&gt;是的，默认就是公开的。所以我立刻去配了 Cloudflare Access。&lt;/p&gt;&#xA;&lt;p&gt;在 Zero Trust 后台里，Access 下面的 Applications，新建一个 Self-hosted 应用。域名填 &lt;code&gt;note.mibase.top&lt;/code&gt;，然后建一条策略，Action 选 Allow，Include 里选 Emails，把我自己和家里人的邮箱填进去。&lt;/p&gt;&#xA;&lt;p&gt;配完之后再访问，会先跳到一个 Cloudflare 的登录页，输入邮箱收验证码，通过了才放行。免费版给五十个用户，够用了。&lt;/p&gt;&#xA;&lt;p&gt;给小孩看的视频站我没加验证，那个本来就是想分享给亲戚的，加了反而麻烦。&lt;/p&gt;&#xA;&lt;h2 id=&#34;顺手做的一件事&#34;&gt;顺手做的一件事&#xA;&lt;/h2&gt;&lt;p&gt;既然流量都过 Cloudflare 了，缓存规则也可以顺手配上。视频站的封面图我设了一年的 Edge TTL，这样重复访问基本不消耗回源带宽。&lt;/p&gt;&#xA;&lt;p&gt;这个和博客那边是同一套思路，静态的东西缓存长一点，动态的别缓存。&lt;/p&gt;&#xA;&lt;h2 id=&#34;和-frp-比到底差在哪&#34;&gt;和 frp 比到底差在哪&#xA;&lt;/h2&gt;&lt;p&gt;用了一个多月，我的感受是这样的。&lt;/p&gt;&#xA;&lt;p&gt;带宽上 Cloudflare 明显强。它是全球任播，国内访问会落到就近的节点，实际测下来晚上也能跑满我家的上行。frp 那台跳板机受限于单机带宽，这点没法比。&lt;/p&gt;&#xA;&lt;p&gt;省心程度上也是 Cloudflare 好。跳板机不用自己维护，证书它帮你签，系统补丁不用管，被打也不用管。这一点对我这种懒得折腾的人来说很重要。&lt;/p&gt;&#xA;&lt;p&gt;但也不是没代价。&lt;/p&gt;&#xA;&lt;p&gt;延迟会比直连高一些，毕竟多绕了一层。我在家用局域网访问，走的是本地地址，没影响；在外面访问大概多二十毫秒，日常用感觉不出来，但如果你要跑远程桌面，那点延迟是能感觉到的。&lt;/p&gt;&#xA;&lt;p&gt;另外就是把一部分控制权交出去了。域名、证书、访问日志都在 Cloudflare 那边。真要哪天它改政策或者涨价，迁移的成本不算低。所以我一直保留着 frp 的配置没删，算是个备份方案。&lt;/p&gt;&#xA;&lt;p&gt;还有一点，免费版对 WebSocket 的支持是有的，但长连接有时候会被掐。我那个相册里的实时同步功能就偶尔断，后来改成轮询才稳定下来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;遇到的两个坑&#34;&gt;遇到的两个坑&#xA;&lt;/h2&gt;&lt;p&gt;一个是 &lt;code&gt;cloudflared&lt;/code&gt; 的内存占用。这东西跑久了会慢慢涨，我有一次看它吃了 400 多兆。后来加了个定时重启，每天凌晨重启一次，问题就没了。官方 issue 里也有人反馈过，看起来还没彻底修。&lt;/p&gt;&#xA;&lt;p&gt;另一个是回源协议。我的服务是 http，如果 &lt;code&gt;service&lt;/code&gt; 写成 &lt;code&gt;https://127.0.0.1:8081&lt;/code&gt;，会报 TLS 握手失败。改成 &lt;code&gt;http://&lt;/code&gt; 就好。这个错我当时找了半天，因为日志里只写了个 502，没提 TLS。&lt;/p&gt;&#xA;&lt;h2 id=&#34;现在的情况&#34;&gt;现在的情况&#xA;&lt;/h2&gt;&lt;p&gt;三个服务稳定跑了一个多月，家里人也习惯了直接输域名访问，不再问我&amp;quot;那个链接是多少&amp;quot;。&lt;/p&gt;&#xA;&lt;p&gt;回头看，这件事最舒服的地方不是技术多新，而是它把一件需要长期维护的事情变成了不用管的事情。以前跑 frp，我每个月至少要看一眼那台跳板机，现在这块彻底从我待办里消失了。&lt;/p&gt;&#xA;</description>
        </item><item>
            <title>给 Cloudflare 配上缓存规则之后，博客的图片终于不用每次回源了</title>
            <link>https://blog.mibase.top/p/%E7%BB%99-cloudflare-%E9%85%8D%E4%B8%8A%E7%BC%93%E5%AD%98%E8%A7%84%E5%88%99%E4%B9%8B%E5%90%8E%E5%8D%9A%E5%AE%A2%E7%9A%84%E5%9B%BE%E7%89%87%E7%BB%88%E4%BA%8E%E4%B8%8D%E7%94%A8%E6%AF%8F%E6%AC%A1%E5%9B%9E%E6%BA%90%E4%BA%86/</link>
            <pubDate>Mon, 05 Feb 2024 02:45:00 +0800</pubDate>
            <guid>https://blog.mibase.top/p/%E7%BB%99-cloudflare-%E9%85%8D%E4%B8%8A%E7%BC%93%E5%AD%98%E8%A7%84%E5%88%99%E4%B9%8B%E5%90%8E%E5%8D%9A%E5%AE%A2%E7%9A%84%E5%9B%BE%E7%89%87%E7%BB%88%E4%BA%8E%E4%B8%8D%E7%94%A8%E6%AF%8F%E6%AC%A1%E5%9B%9E%E6%BA%90%E4%BA%86/</guid>
            <description>&lt;img src=&#34;https://blog.mibase.top/p/%E7%BB%99-cloudflare-%E9%85%8D%E4%B8%8A%E7%BC%93%E5%AD%98%E8%A7%84%E5%88%99%E4%B9%8B%E5%90%8E%E5%8D%9A%E5%AE%A2%E7%9A%84%E5%9B%BE%E7%89%87%E7%BB%88%E4%BA%8E%E4%B8%8D%E7%94%A8%E6%AF%8F%E6%AC%A1%E5%9B%9E%E6%BA%90%E4%BA%86/cover.jpg&#34; alt=&#34;Featured image of post 给 Cloudflare 配上缓存规则之后，博客的图片终于不用每次回源了&#34; /&gt;&lt;p&gt;域名托管到 Cloudflare 有一段时间了，页面打开速度一直说不上好。白天在公司摸鱼的时候打开自己的博客，首屏的图片总是一块一块地往下蹦，像是网速被谁掐了一半。&lt;/p&gt;&#xA;&lt;p&gt;打开 F12 看 Network，问题挺明显的。那些几十 KB 的封面图，每一张的响应时间都在三百毫秒往上，而且不管我刷新多少次，它都是这个速度。点开 Response Headers 一看，&lt;code&gt;CF-Cache-Status&lt;/code&gt; 是 &lt;code&gt;MISS&lt;/code&gt;，有时候干脆是 &lt;code&gt;DYNAMIC&lt;/code&gt;。意思很清楚，Cloudflare 根本没帮我存，每次都是它跑回我的 VPS 拿一遍，再转手给我。&lt;/p&gt;&#xA;&lt;p&gt;我这才认真去看了它的缓存逻辑。&lt;/p&gt;&#xA;&lt;h2 id=&#34;默认只缓存一小撮东西&#34;&gt;默认只缓存一小撮东西&#xA;&lt;/h2&gt;&lt;p&gt;Cloudflare 的免费套餐对缓存是做了限制的。它只对一部分后缀名有默认缓存行为，比如 css、js、图片、字体这些。听起来好像我需要的都在里面，但实际上还有一个坑，就是缓存时长很短，而且第一次请求之后 Edge 节点上存不存、存多久，还要看源站给的 &lt;code&gt;Cache-Control&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;我的 Nginx 那时候配置得很随便，静态资源只写了 &lt;code&gt;expires 7d&lt;/code&gt;，别的什么都没管。Cloudflare 拿到这个头，就按七天存。听着还行，可我博客的图换了之后，七天之内访客看到的还是旧图，这个体验很别扭。&lt;/p&gt;&#xA;&lt;p&gt;后来我把思路理清楚了。图片、字体、css、js 这些东西带上内容指纹，改一次文件名就变一次，这种资源可以放心地缓存很久，甚至一年。HTML 不行，HTML 一改就得让用户立刻看到新的。&lt;/p&gt;&#xA;&lt;h2 id=&#34;换成-cache-rules&#34;&gt;换成 Cache Rules&#xA;&lt;/h2&gt;&lt;p&gt;网上很多老教程还在教你用 Page Rules。这个功能 Cloudflare 从 2023 年就开始推着大家迁走了，免费套餐能建的条数也少，表达式写法也老旧。现在应该用的是 Rules 里的 Cache Rules，逻辑更清楚，条件可以写得很细。&lt;/p&gt;&#xA;&lt;p&gt;我进控制台，左边找到 Rules，点 Cache Rules，Create rule。&lt;/p&gt;&#xA;&lt;p&gt;名字随便起，我写的是 &lt;code&gt;static-assets&lt;/code&gt;。下面 When incoming requests match 那里，选 Custom filter expression，然后填：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;(http.request.uri.path.extension in {&amp;#34;jpg&amp;#34; &amp;#34;jpeg&amp;#34; &amp;#34;png&amp;#34; &amp;#34;gif&amp;#34; &amp;#34;webp&amp;#34; &amp;#34;svg&amp;#34; &amp;#34;ico&amp;#34; &amp;#34;css&amp;#34; &amp;#34;js&amp;#34; &amp;#34;woff&amp;#34; &amp;#34;woff2&amp;#34; &amp;#34;ttf&amp;#34;})&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这个写法比老教程里那种一串 &lt;code&gt;contains&lt;/code&gt; 拼起来要干净得多，它是直接判断路径后缀。&lt;/p&gt;&#xA;&lt;p&gt;Then 那部分做这些设置：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Cache eligibility: Eligible for cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Edge TTL: Ignore cache-control header and use this TTL -&amp;gt; 1 month&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Browser TTL: Override origin -&amp;gt; 1 month&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Edge TTL 是 Cloudflare 自己节点上存多久，Browser TTL 是告诉访客的浏览器存多久。两个都拉长，命中率才上得去。免费套餐的 Edge TTL 最长就是一个月，够用了。&lt;/p&gt;&#xA;&lt;p&gt;这里有个细节值得说一下。如果你选的是 &lt;code&gt;Use cache-control header if present&lt;/code&gt;，那 Cloudflare 就会听源站的，你 Nginx 里写 7 天它就是 7 天。想让 Cloudflare 说了算，就得选 ignore 那一项。我第一次没注意，改完发现一点变化都没有，白折腾了半小时。&lt;/p&gt;&#xA;&lt;h2 id=&#34;html-也可以缓存但要小心&#34;&gt;HTML 也可以缓存，但要小心&#xA;&lt;/h2&gt;&lt;p&gt;纯静态博客有个好处，HTML 也是构建出来的静态文件，没有数据库查询，缓存起来毫无压力。我另外加了一条规则，只针对 HTML：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;(http.request.uri.path.extension eq &amp;#34;&amp;#34;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;然后 Edge TTL 设成 2 小时，Browser TTL 设成 30 分钟。&lt;/p&gt;&#xA;&lt;p&gt;为什么留这么短。因为我一改文章就得让人看到新的。两小时对我来说是个能接受的窗口，写完文章去 Cloudflare 那边手动 Purge 一下就行。如果哪天忘了 Purge，最多两小时也会自己过期，不至于一直挂着旧内容。&lt;/p&gt;&#xA;&lt;p&gt;Browser TTL 我压得更短，是因为浏览器缓存最难清。用户本地存着的 HTML，你在服务器上怎么改他都不知道，只能等他缓存过期。30 分钟是个折中。&lt;/p&gt;&#xA;&lt;h2 id=&#34;源站那边也得配合&#34;&gt;源站那边也得配合&#xA;&lt;/h2&gt;&lt;p&gt;只改 Cloudflare 不管源站，效果会打折扣。我在 Nginx 里把静态资源的响应头重写了一遍：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;~&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;\.(jpg|jpeg|png|gif|webp|svg|ico|css|js|woff2?)&lt;/span&gt;$ &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;expires&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;1y&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;add_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Cache-Control&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;public,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;max-age=31536000,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;immutable&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;access_log&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;off&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;immutable&lt;/code&gt; 这个值挺有用。它告诉浏览器，这个文件在有效期内绝对不会变，用户按 F5 刷新的时候不用再发一次条件请求。少了这个 &lt;code&gt;304&lt;/code&gt; 往返，刷新页面的感觉会明显快一截。&lt;/p&gt;&#xA;&lt;p&gt;前提是你的文件名得带指纹。Hugo 生成的 css 和 js 默认是带 hash 的，图片是我自己命名的，没带。所以我给图片加了个小约定，文件名后面缀上内容的前八位哈希，改图就改名，这样缓存一年也不会出问题。&lt;/p&gt;&#xA;&lt;h2 id=&#34;验证到底有没有生效&#34;&gt;验证到底有没有生效&#xA;&lt;/h2&gt;&lt;p&gt;改完之后别急着高兴，得实际去看。&lt;/p&gt;&#xA;&lt;p&gt;最直接的办法是 curl：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -sI https://blog.mibase.top/images/cover.jpg &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep -i cf-cache&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;第一次大概率是 &lt;code&gt;MISS&lt;/code&gt;，这是正常的，节点上还没有。再跑一次，应该就变成 &lt;code&gt;HIT&lt;/code&gt; 了。如果跑了好几次还是 &lt;code&gt;MISS&lt;/code&gt;，那就回去看看规则的条件是不是没匹配上，或者 Cache eligibility 是不是还停在 &lt;code&gt;Bypass cache&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;还有一个 &lt;code&gt;DYNAMIC&lt;/code&gt;，这个值的意思是 Cloudflare 认为这个请求不该走缓存。通常出现在 HTML 上，或者规则没命中。&lt;/p&gt;&#xA;&lt;p&gt;我还注意到有个 &lt;code&gt;REVALIDATED&lt;/code&gt;，那是源站返回了 304，Cloudflare 拿旧的继续用。这个状态其实也挺好，至少没重新传一遍完整内容。&lt;/p&gt;&#xA;&lt;h2 id=&#34;命中率从四成到九成&#34;&gt;命中率从四成到九成&#xA;&lt;/h2&gt;&lt;p&gt;Cloudflare 控制台的 Caching 下面有个 Analytics，能看到缓存命中率。我改之前是 41% 左右，改完第二天稳定在 92% 上下。&lt;/p&gt;&#xA;&lt;p&gt;这个数字带来的变化是很实在的。以前国内访问我的博客，图片要绕到我的 VPS 上拿，VPS 在洛杉矶，一个来回就是两百多毫秒。现在大部分请求落在 Cloudflare 离用户最近的节点上，二三十毫秒就回来了。&lt;/p&gt;&#xA;&lt;p&gt;还有一个连带的好处，源站的带宽掉得很明显。Cloudflare 后台显示回源流量少了七成多，我那台按流量计费的机器，账单也跟着降了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;几个踩过的坑&#34;&gt;几个踩过的坑&#xA;&lt;/h2&gt;&lt;p&gt;规则是有优先级的，同一个请求命中多条规则时，只有最上面那条生效。我一开始把 HTML 的规则放在最前面，结果图片规则被它盖住了，因为 HTML 那条的条件写得太宽。顺序一定要捋一遍。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;Purge Everything&lt;/code&gt; 别乱点。它会把你所有节点上的缓存全部清掉，清完之后的一段时间里，所有请求都会压到源站上。我手滑点过一次，那一分钟 VPS 的负载直接飙到 8。后来我都是按 URL 精确 Purge。&lt;/p&gt;&#xA;&lt;p&gt;还有 &lt;code&gt;Vary&lt;/code&gt; 头。如果你的源站返回了 &lt;code&gt;Vary: Cookie&lt;/code&gt; 之类的东西，Cloudflare 基本就不会缓存这个响应了，因为同一个 URL 对应太多变体。静态博客一般没这个问题，但如果你在 Nginx 里加过什么奇怪的配置，值得检查一下。&lt;/p&gt;&#xA;&lt;h2 id=&#34;写在最后&#34;&gt;写在最后&#xA;&lt;/h2&gt;&lt;p&gt;这件事的性价比确实高。不用升级套餐，不用换服务器，只是把规则写对，速度就上了一个台阶。&lt;/p&gt;&#xA;&lt;p&gt;我现在给静态站做优化的顺序基本固定了。先看资源有没有带指纹，再看缓存头怎么写，最后才去考虑 Cloudflare 那层。前面两步做扎实，CDN 才帮得上忙。顺序反过来的话，你在 CDN 后台点半天，源站一个 &lt;code&gt;no-cache&lt;/code&gt; 就把你打回原形。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
