<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>发布与恢复 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/series/release-and-recovery/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/series/release-and-recovery/index.xml" rel="self" type="application/rss+xml"/><item><title>小系统的优势</title><link>https://www.xbcatm.com/zh/notes/the-advantage-of-small-systems/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/the-advantage-of-small-systems/</guid><description>当系统的重要行为能装进一个思维模型时，它更容易被信任。</description><content:encoded>&lt;p&gt;小不等于未完成。它意味着每个组件职责清晰、依赖经过斟酌，并且从请求到结果之间始终可见。&lt;/p&gt;
&lt;h2 id="可见性是一种特性"&gt;可见性是一种特性&lt;/h2&gt;
&lt;p&gt;当运维人员能说出每个边界时，他们也能指出缺少哪份证据。日志变得更有用，告警更具体，例行维护风险更低。&lt;/p&gt;
&lt;p&gt;对静态网站而言，重要的边界通常很容易列出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS 把主机名指向预期的边缘。&lt;/li&gt;
&lt;li&gt;TLS 在预期的代理处终结。&lt;/li&gt;
&lt;li&gt;代理只服务一个已知的输出目录。&lt;/li&gt;
&lt;li&gt;输出只包含站点需要的文件。&lt;/li&gt;
&lt;li&gt;404 响应由站点本身处理，而不是意外的上游。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把这些写下来，比加上一块回答不了任何上述问题的仪表盘更有价值。&lt;/p&gt;
&lt;h2 id="复杂性要用实力取得位置"&gt;复杂性要用实力取得位置&lt;/h2&gt;
&lt;p&gt;当新的一层消除的不确定性多于它引入的，它才值得。缓存重要时，给资源加指纹的构建步骤会有用；内容需要事务时，数据库才有用。但对一个小而多为静态的站点，这两者都不必然是改进。&lt;/p&gt;
&lt;p&gt;对客户端 JavaScript 也适用同一规则。如果页面需要搜索索引或交互编辑器，就加入满足该需求的最小实现，并有意识地更新内容。如果页面是一份文档，纯 HTML 往往更容易检视、更有韧性。&lt;/p&gt;
&lt;h2 id="让所有权明确"&gt;让所有权明确&lt;/h2&gt;
&lt;p&gt;每个文件都应有存在的理由与明确的更新方式。vendored 主题应记录其上游版本与许可。生成目录应标记为生成物。资源下载应说明它包含什么、何时更新、校验和如何产生。&lt;/p&gt;
&lt;p&gt;这些简短声明能防止未来的运维者把偶然产生的制品当作受支持的接口。&lt;/p&gt;
&lt;h2 id="为下一次变更留出空间"&gt;为下一次变更留出空间&lt;/h2&gt;
&lt;p&gt;最好的小系统不是行数最少的那个，而是下一次变更看得见去处的那一个。笔记站可以从 Markdown、静态生成器与一个 Web 服务器起步；如果以后需要搜索或评论，可以独立评估这个新需求，而不是一开始就假设它存在。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/directory-structure/"&gt;Hugo directory structure&lt;/a&gt;，内容与资源的目录约定，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/file_server"&gt;Caddy &lt;code&gt;file_server&lt;/code&gt; 指令&lt;/a&gt;，设计中引用的静态文件服务默认值，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>静态服务器应有一个明确的根目录</title><link>https://www.xbcatm.com/zh/notes/a-static-server-should-have-one-obvious-root/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-static-server-should-have-one-obvious-root/</guid><description>让 Caddy 文件服务、生成制品与回滚路径保持一致。</description><content:encoded>&lt;p&gt;静态部署最有用的属性是：运维人员不用读应用代码，就能回答&amp;quot;这条响应是哪个目录提供的？&amp;quot;。Caddy 的 &lt;code&gt;root&lt;/code&gt; 与 &lt;code&gt;file_server&lt;/code&gt; 指令直接提供了这个边界。&lt;/p&gt;
&lt;h2 id="让根目录明确"&gt;让根目录明确&lt;/h2&gt;
&lt;p&gt;一个最小的站点块可以贴近制品布局：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;www.example.test {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; root * /srv/example-site
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; encode zstd gzip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -Server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; X-Content-Type-Options nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; X-Frame-Options DENY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Referrer-Policy strict-origin-when-cross-origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file_server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该路径只是示例，不是该盲目粘贴的值。发布命令应发布一个完整目录、核验它，然后才把这个目录设为配置里的根。在旧的已知良好目录通过冒烟测试之前，保留它。&lt;/p&gt;
&lt;h2 id="检查边缘而不只是文件树"&gt;检查边缘，而不只是文件树&lt;/h2&gt;
&lt;p&gt;请求一个 HTML 页面、一个可下载的文本文件和一个缺失路径。把内容类型与状态码与本地制品比较。即使 404 页看起来很精致，若返回 &lt;code&gt;200&lt;/code&gt; 仍是部署缺陷。&lt;/p&gt;
&lt;p&gt;当 Caddy 管理 HTTPS 时，证书签发与 HTTP 到 HTTPS 的重定向都属于边缘行为。请从裸的 HTTP 监听器测试重定向，并从公共主机名读取证书名称。不要只凭进程健康就推断成功。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/file_server"&gt;Caddy &lt;code&gt;file_server&lt;/code&gt; 指令&lt;/a&gt;，静态文件服务与根目录选择，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/caddyfile/directives/header"&gt;Caddy &lt;code&gt;header&lt;/code&gt; 指令&lt;/a&gt;，响应头配置，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/docs/automatic-https"&gt;Caddy Automatic HTTPS&lt;/a&gt;，证书签发与 HTTPS 重定向，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"&gt;MDN: &lt;code&gt;X-Frame-Options&lt;/code&gt;&lt;/a&gt;，防止被嵌套的选项，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>静态网站的发布检查清单</title><link>https://www.xbcatm.com/zh/notes/a-release-checklist-for-a-static-site/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-release-checklist-for-a-static-site/</guid><description>每个发布都值得一组小、快、针对性强、且会为真实原因失败的检查。</description><content:encoded>&lt;p&gt;静态网站发布很简单，但简单并不免除发布检查的需要。有用的检查足够小，能在每次发布运行，也足够具体，能为真实的理由而失败。&lt;/p&gt;
&lt;h2 id="构建干净的制品"&gt;构建干净的制品&lt;/h2&gt;
&lt;p&gt;使用固定版本的生成器，构建到一次性的输出目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --gc --minify --destination public
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确切命令因项目而异，但关键属性是：输出能从受跟踪的源码复现。不要在构建之后手改生成的 HTML。&lt;/p&gt;
&lt;h2 id="检查制品而不只是源码"&gt;检查制品，而不只是源码&lt;/h2&gt;
&lt;p&gt;检视生成的树，确认期望的入口点存在：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s public/index.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s public/404.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s public/robots.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s public/sitemap.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find public -type f -print &lt;span class="p"&gt;|&lt;/span&gt; sort
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然后搜索意外的占位符、本地路径与仅开发用的主机：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rg -n &lt;span class="s1"&gt;&amp;#39;localhost|127\.0\.0\.1|TODO|example\.com|/Users/&amp;#39;&lt;/span&gt; public --glob &lt;span class="s1"&gt;&amp;#39;!notes/a-release-checklist-for-a-static-site/**&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;文章里有意保留的示例应用围栏明确标注为示例。链接里的主机名或私有路径通常是发布错误。&lt;/p&gt;
&lt;h2 id="验证公共路由"&gt;验证公共路由&lt;/h2&gt;
&lt;p&gt;在本地提供制品并请求访问者可达的路径：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo server --bind 127.0.0.1 --port &lt;span class="m"&gt;1313&lt;/span&gt; --disableFastRender
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:1313/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:1313/notes/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:1313/resources/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I http://127.0.0.1:1313/missing-page/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确认 404 路由在生产服务器上返回 404 状态。一个外观精美但状态 200 的错误页，仍然会破坏监控与缓存。&lt;/p&gt;
&lt;h2 id="记录结果"&gt;记录结果&lt;/h2&gt;
&lt;p&gt;在发布记录中保留源码提交、生成器版本与每个可下载资源的校验和。这样无需依赖记忆，就能把线上文件与产生它的源码进行比较。&lt;/p&gt;
&lt;p&gt;这份检查清单的可下载版本在&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;资源&lt;/a&gt;一节中。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/commands/hugo/"&gt;Hugo command reference&lt;/a&gt;，干净生产构建的生成器标志，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，HTTP 验证与响应头检查选项，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;静态网站发布检查清单&lt;/a&gt;，可下载的配套资源。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>通过切换完整目录发布静态网站</title><link>https://www.xbcatm.com/zh/notes/publish-a-static-site-by-switching-complete-directories/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/publish-a-static-site-by-switching-complete-directories/</guid><description>在活动树旁边预置一份完整制品，并只在验证后替换目录名。</description><content:encoded>&lt;p&gt;直接把新文件复制到活动的静态目录上，会造成一段访问者可能从一个版本拿到 HTML、从另一个版本拿到资源的时期。更安全的模式是：在活动树旁预置一棵完整的树，验证它，再改变目录名。&lt;/p&gt;
&lt;h2 id="把所有发布目录放在同一文件系统上"&gt;把所有发布目录放在同一文件系统上&lt;/h2&gt;
&lt;p&gt;当活动树、预置树与回滚树共享同一文件系统与父目录时，名字替换最强。先检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;df -P /srv/www
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在该父目录下创建唯一命名的中转目录。不要把未解析的变量或宽泛通配符当成删除目标来复用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d /srv/www/site.new.XXXXXX&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="填充并验证预置目录"&gt;填充并验证预置目录&lt;/h2&gt;
&lt;p&gt;把生成制品复制进空目录，然后检查代表性文件与期望的文件数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -C public -cf - . &lt;span class="p"&gt;|&lt;/span&gt; tar -C &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -xf -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/index.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;/404.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -type f &lt;span class="p"&gt;|&lt;/span&gt; wc -l
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当工具支持目录参数时，对预置路径运行链接与校验和检查。未通过验证的预置目录绝不能获得活动目录的名字。&lt;/p&gt;
&lt;h2 id="用显式回滚路径切换名字"&gt;用显式回滚路径切换名字&lt;/h2&gt;
&lt;p&gt;在第一次移动前解析每个路径：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;live&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/srv/www/site
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;stamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date -u +%Y%m%dT%H%M%SZ&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;rollback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/srv/www/site.old-&lt;span class="nv"&gt;$stamp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$live&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -d &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; ! -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rollback&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$live&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rollback&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; mv &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$stage&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$live&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$live&lt;/span&gt;&lt;span class="s2"&gt;/index.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mv &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rollback&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$live&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段序列在两个移动之间留下一个短窗口。对每次请求都对根的单一静态服务进程来说，该窗口通常很小但仍可观察。平台特定的 rename 或符号链接模式可以收紧切换；在依赖更强断言之前，请测试确切的文件系统与服务器行为。&lt;/p&gt;
&lt;h2 id="验证公共边缘"&gt;验证公共边缘&lt;/h2&gt;
&lt;p&gt;切换之后，请求首页、一个深层路由、一个带指纹的资源与一个缺失路径。把公共字节与预置制品比较。在检查完成与观察窗口结束之前，保留回滚目录。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pubs.opengroup.org/onlinepubs/9799919799/functions/rename.html"&gt;POSIX &lt;code&gt;rename()&lt;/code&gt;&lt;/a&gt;，名字替换语义，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man2/rename.2.html"&gt;Linux &lt;code&gt;rename(2)&lt;/code&gt;&lt;/a&gt;，文件系统与替换行为，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/tar/manual/tar.html"&gt;GNU tar manual&lt;/a&gt;，归档创建与解包，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>发布清单应点名每一个已发布的字节</title><link>https://www.xbcatm.com/zh/notes/a-release-manifest-should-name-every-published-byte/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-release-manifest-should-name-every-published-byte/</guid><description>生成确定性的文件清单，并在传输前后比较。</description><content:encoded>&lt;p&gt;静态制品是一个目录，而不是一个可执行文件。只记录源码提交或只记录首页校验和会留下缺口：缺失的图片、陈旧的别名或变化的下载可能悄悄存活下来。一份发布清单应枚举每个常规文件。&lt;/p&gt;
&lt;h2 id="生成稳定的列表"&gt;生成稳定的列表&lt;/h2&gt;
&lt;p&gt;从制品根目录运行命令，使路径保持相对。在固定区域设置下按路径排序：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; public
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find . -type f -exec sha256sum &lt;span class="o"&gt;{}&lt;/span&gt; + &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C sort -k2 &amp;gt; ../release.sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;清单是关于制品的证据，因此把它放在被测量目录之外，除非清单本身有意作为发布的一部分。&lt;/p&gt;
&lt;h2 id="传输后验证"&gt;传输后验证&lt;/h2&gt;
&lt;p&gt;在目标端，逐个文件验证：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /srv/www/site.new
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256sum -c /path/to/release.sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这能捕获变化的字节与缺失的路径。它不会报告意外的多余文件，因此也要比较目标端的文件列表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find . -type f &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C sort &amp;gt; destination.files
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;awk &lt;span class="s1"&gt;&amp;#39;{print $2}&amp;#39;&lt;/span&gt; /path/to/release.sha256 &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C sort &amp;gt; expected.files
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;diff -u expected.files destination.files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="为发布记录增加聚合摘要"&gt;为发布记录增加聚合摘要&lt;/h2&gt;
&lt;p&gt;对清单自身做哈希，为日志与部署记录生成一个短标识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256sum release.sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;聚合摘要只有在清单生成规则固定时才有意义：相对路径形式、排序区域、哈希算法，以及是否包含生成的元数据，都属于流程的一部分。&lt;/p&gt;
&lt;h2 id="不要把完整性等同于真实性"&gt;不要把完整性等同于真实性&lt;/h2&gt;
&lt;p&gt;SHA-256 证明两个字节序列一致。如果攻击者既能替换制品也能替换清单，那么这种检查不能确立是谁产生的它们。制品签名与可信分发是独立的控制手段。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/coreutils/manual/html_node/sha2-utilities.html"&gt;GNU Coreutils: sha2 utilities&lt;/a&gt;，校验和生成与检查，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man1/sha256sum.1.html"&gt;sha256sum(1)&lt;/a&gt;，命令行为与输出格式，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>用两条命令读取公共边缘</title><link>https://www.xbcatm.com/zh/notes/read-the-public-edge-in-two-commands/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/read-the-public-edge-in-two-commands/</guid><description>对活动站点运行只读探针，让响应头与证书日期确认你以为部署的东西。</description><content:encoded>&lt;p&gt;一次部署，是当公共边缘与你选择的文件一致时才完成，而不是因为最后一条上传命令退出码为零。操作套件里的两个只读小工具，能让任何能到达主机的机器看到这种一致性。&lt;/p&gt;
&lt;h2 id="检查响应头"&gt;检查响应头&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;check-http-headers.sh&lt;/code&gt; 抓取一个 URL，并打印响应行、响应头与重定向后的最终 URL：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;check-http-headers.sh https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最近对本站一次运行的结果：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;server: nginx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html; charset=utf-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: default-src &amp;#39;self&amp;#39;; script-src &amp;#39;none&amp;#39;; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;strict-transport-security: max-age=31536000; includeSubDomains
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x-content-type-options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意正文被丢弃，只显示响应线之外的状态行。要比较一次发布前后的变化，保存一份基线并把新的输出与之相减。&lt;/p&gt;
&lt;h2 id="读取证书日期"&gt;读取证书日期&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;check-tls-expiry.sh&lt;/code&gt; 从 TLS 握手读取服务器证书的过期日期，而不改变服务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;check-tls-expiry.sh www.xbcatm.com &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它打印证书的 &lt;code&gt;notAfter&lt;/code&gt; 以及到该日期剩余的天数。如果剩余天数低于阈值，它以非零状态退出——适合加进发布冒烟测试。&lt;/p&gt;
&lt;h2 id="发布冒烟测试里它们配合使用"&gt;发布冒烟测试里它们配合使用&lt;/h2&gt;
&lt;p&gt;排定发布冒烟测试时，让一个命令确认法着陆状态，另一个确认即将过期的证书。剩下的剩余风险是活动报表之外、真正浏览无法发现的内容——例如重量级第三方脚本，或是仅在大流量出现的缓存问题。一条只读输出现场完成不了这些，仍需一次真实的浏览器会话。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，响应语义与条件请求，已核验 2026-08-24。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openssl.org/3.0/man1/openssl-s_client/"&gt;OpenSSL &lt;code&gt;s_client&lt;/code&gt;&lt;/a&gt;，TLS 连接与证书日期检查，已核验 2026-08-24。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;制品 HTTP 响应头检查&lt;/a&gt;，贯穿本记录使用的只读检查。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>发布在公共边缘一致之前不算完成</title><link>https://www.xbcatm.com/zh/notes/a-release-is-not-complete-until-the-public-edge-agrees/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-release-is-not-complete-until-the-public-edge-agrees/</guid><description>把生成制品与访问者实际能收到的响应进行比较。</description><content:encoded>&lt;p&gt;静态发布消除了应用运行时的复杂度，但不会消除生成目录与公共响应之间的边界。发布检查应同时覆盖两边。&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" src="https://www.xbcatm.com/diagrams/release-path.svg" alt="一条小的静态发布路径" /&gt;
&lt;/p&gt;
&lt;h2 id="从干净的制品开始"&gt;从干净的制品开始&lt;/h2&gt;
&lt;p&gt;使用固定生成器构建到干净的目录。记录源码提交、生成器版本、输出文件列表，以及用户会下载的文件的校验和。一次干净的构建能捕获复制式部署可能会意外保留的陈旧文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make verify
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find public -type f -print &lt;span class="p"&gt;|&lt;/span&gt; sort
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;文件列表是证据，不是装饰。如果某条路由消失，diff 应能说明是该来源被移除，还是生成器未能产出它。&lt;/p&gt;
&lt;h2 id="演练三类响应"&gt;演练三类响应&lt;/h2&gt;
&lt;p&gt;请求一个 HTML 页面、一个静态资源与一个缺失路径。检查状态码、内容类型、缓存策略与安全头：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsS -D - -o /dev/null https://www.example.test/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsS -D - -o /dev/null https://www.example.test/downloads/check-http-headers.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sS -D - -o /dev/null https://www.example.test/does-not-exist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第三条命令有意不用 &lt;code&gt;-f&lt;/code&gt;：404 是预期结果，仍应被捕获。把公共正文与生成的 &lt;code&gt;404.html&lt;/code&gt; 比较，并确认状态始终为 404。&lt;/p&gt;
&lt;h2 id="让回滚保持简单"&gt;让回滚保持简单&lt;/h2&gt;
&lt;p&gt;在新目录通过检查之前，不要删除上一个目录。回滚应选择一个已知良好目录，而不是在事故中凭记忆重新构造一个。&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;发布检查清单&lt;/a&gt;与&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;HTTP 响应头检查&lt;/a&gt;都刻意做得足够小，能在干净的 shell 里运行。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，状态与表示语义，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://curl.se/docs/manpage.html"&gt;curl manual&lt;/a&gt;，响应检查选项，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;静态网站发布检查清单&lt;/a&gt;，本地配套资源。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>DNS 变更需要一个感知缓存的回滚窗口</title><link>https://www.xbcatm.com/zh/notes/dns-changes-need-a-cache-aware-rollback-window/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/dns-changes-need-a-cache-aware-rollback-window/</guid><description>在变更前临时降低 TTL，并不会抹掉已用旧值缓存的应答。</description><content:encoded>&lt;p&gt;改一条地址记录很容易，预测每个解析器何时停止使用旧应答却很难。时间到活性（TTL）值随 DNS 响应传播，并控制缓存可复用的时长。在迁移那一刻降低 TTL，并不能改写更早被缓存下来的应答。&lt;/p&gt;
&lt;h2 id="在事件前降低-ttl"&gt;在事件前降低 TTL&lt;/h2&gt;
&lt;p&gt;如果既有记录是一天的 TTL，至少要在计划切换前一个旧 TTL 时就把它降低。这样在旧端点仍然权威时，缓存有时间去取较短的值。确切间隔应写进变更计划，而不是在迁移中即兴执行。&lt;/p&gt;
&lt;p&gt;记录来自权威服务器与至少一个递归解析器的应答：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig +noall +answer www.example.test A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dig @1.1.1.1 +noall +answer www.example.test A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;递归解析器显示的剩余 TTL 是它当前缓存状态的证据。不同解析器合理地显示不同的剩余值。&lt;/p&gt;
&lt;h2 id="让两侧都保持有效"&gt;让两侧都保持有效&lt;/h2&gt;
&lt;p&gt;切换期间，保持旧端点与新端点都能为预期主机名提供服务。TLS 名称、重定向与应用配置在两侧必须一致。权威记录改变后立刻移除旧端点，会给仍持有先前应答的客户端造成本可避免的停机。&lt;/p&gt;
&lt;p&gt;否定应答也会被缓存。一个最近返回 &lt;code&gt;NXDOMAIN&lt;/code&gt; 的名字，其出现可能很慢才可见，因为根据区域的权威数据，否定响应可能仍留在递归缓存里。&lt;/p&gt;
&lt;h2 id="观察后恢复-ttl"&gt;观察后恢复 TTL&lt;/h2&gt;
&lt;p&gt;一旦权威与具有代表性的递归应答都指向新端点，且旧缓存窗口已过，就恢复正常 TTL。在变更记录中保留旧值、新值、权威应答与回滚条件。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc1034.html"&gt;RFC 1034 — Domain Names: Concepts and Facilities&lt;/a&gt;，缓存模型，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc1035.html"&gt;RFC 1035 — Domain Names: Implementation and Specification&lt;/a&gt;，TTL 字段，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc2308.html"&gt;RFC 2308 — Negative Caching of DNS Queries&lt;/a&gt;，否定缓存模型与保留的错误 TTL，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>重启策略需要一个速率限制</title><link>https://www.xbcatm.com/zh/notes/restart-policies-need-a-rate-limit/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/restart-policies-need-a-rate-limit/</guid><description>让 systemd 恢复瞬时故障，同时不掩盖永久性的配置错误。</description><content:encoded>&lt;p&gt;对长驻服务来说，自动重启很有用，但无界的循环会把一个清晰的失败变成一个嘈杂的失败。单元文件应同时描述正常进程生命周期与可接受的恢复量。&lt;/p&gt;
&lt;h2 id="捕获启动失败"&gt;捕获启动失败&lt;/h2&gt;
&lt;p&gt;对长驻进程，&lt;code&gt;Type=exec&lt;/code&gt; 能在启动时暴露配置程序执行失败，而不是把一次 fork 当成功。把它与显式的 &lt;code&gt;ExecStart&lt;/code&gt; 以及能完成工作所需最小权限的 &lt;code&gt;User&lt;/code&gt; 搭配使用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/srv/site/bin/server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;RestartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Restart=on-failure&lt;/code&gt; 是一种恢复策略，不是健康检查。如果进程在提供错误响应的同时保持存活，systemd 不会知道站点坏了；外部冒烟测试仍有其职责。&lt;/p&gt;
&lt;h2 id="限制循环"&gt;限制循环&lt;/h2&gt;
&lt;p&gt;Systemd 通过 &lt;code&gt;StartLimitIntervalSec=&lt;/code&gt; 与 &lt;code&gt;StartLimitBurst=&lt;/code&gt; 应用启动速率限制。选择允许短暂瞬时停机、但不会让崩溃循环耗尽主机资源的值。当达到限制时，日志里应包含区分&amp;quot;重启成功&amp;quot;与&amp;quot;重复失败后放弃&amp;quot;所需的证据。&lt;/p&gt;
&lt;p&gt;修改单元后，核验已加载配置并检查最近日志：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl restart example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status --no-pager example.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl -u example.service -n &lt;span class="m"&gt;80&lt;/span&gt; --no-pager
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不要用重启策略掩盖缺失文件、无效权限或糟糕的环境。先修复第一次失败，再测试恢复路径。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html"&gt;systemd.service&lt;/a&gt;，服务类型、重启与启动速率限制器，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html"&gt;systemd.unit&lt;/a&gt;，通用单元段与 drop-in 语义，已核验 2026-08-22。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>维护定时器应让其最近结果可见</title><link>https://www.xbcatm.com/zh/notes/a-maintenance-timer-should-leave-its-last-result-visible/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-maintenance-timer-should-leave-its-last-result-visible/</guid><description>用一个 oneshot 服务、一个定时器、有界运行时间与日志证据搭配使用。</description><content:encoded>&lt;p&gt;cron 式语法并不是维护任务契约的全部。命令需要一个清晰的服务身份、一个有界的运行时间，以及能区分&amp;quot;未调度&amp;quot;与&amp;quot;运行但失败&amp;quot;的证据。一个 systemd 定时器与 oneshot 服务能把这些关注点分开。&lt;/p&gt;
&lt;h2 id="把工作定义成服务"&gt;把工作定义成服务&lt;/h2&gt;
&lt;p&gt;使用显式用户与绝对命令路径：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /etc/systemd/system/site-check.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Read-only public site check&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sitecheck&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/check-public-site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;2min&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;TimeoutStartSec&lt;/code&gt; 防止网络或子进程等待无限占用调度。当契约失败时，helper 应返回非零退出码，并且不得修改它所检查的服务。&lt;/p&gt;
&lt;h2 id="调度该服务"&gt;调度该服务&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# /etc/systemd/system/site-check.timer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;每小时运行公共站点检查&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Timer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;OnCalendar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;hourly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Persistent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;RandomizedDelaySec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5min&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Unit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;site-check.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Persistent=true&lt;/code&gt; 会在机器恢复后补跑错过的日历事件。&lt;code&gt;RandomizedDelaySec&lt;/code&gt; 把一个窗口内的例行工作分散开；当任务必须在精确时刻运行时它并不合适。&lt;/p&gt;
&lt;h2 id="启用前先验证"&gt;启用前先验证&lt;/h2&gt;
&lt;p&gt;检视日历表达式，然后手动启动服务：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemd-analyze calendar hourly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl start site-check.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status site-check.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl -u site-check.service -n &lt;span class="m"&gt;50&lt;/span&gt; --no-pager
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;只有当服务在预期的用户、环境、路径与网络访问下成功时才启用定时器：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now site-check.timer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl list-timers site-check.timer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="对服务结果告警"&gt;对服务结果告警&lt;/h2&gt;
&lt;p&gt;定时器触发并不等于成功。要监控 oneshot 服务的退出状态或日志结果。保持 stdout 简洁，调试需要的详细制品写入一个有界的路径。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man5/systemd.timer.5.html"&gt;systemd.timer(5)&lt;/a&gt;，日历定时器、持久化与延迟行为，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/systemd.time.7.html"&gt;systemd.time(7)&lt;/a&gt;，日历表达式语法，已核验 2026-08-23。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html"&gt;systemd.service&lt;/a&gt;，oneshot 服务语义，已核验 2026-08-23。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>备份只有在演练过恢复之后才有用</title><link>https://www.xbcatm.com/zh/notes/backups-are-only-useful-after-a-restore-rehearsal/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/backups-are-only-useful-after-a-restore-rehearsal/</guid><description>一份备份计划应证明重要文件能被找到、验证并恢复。</description><content:encoded>&lt;p&gt;&amp;ldquo;我们有备份&amp;quot;还不是一种恢复能力。一份有用的备份有已知范围、已知新鲜度、一个验证步骤，以及一份确有人实际跑过恢复流程。&lt;/p&gt;
&lt;h2 id="定义恢复单元"&gt;定义恢复单元&lt;/h2&gt;
&lt;p&gt;对静态站点，源码仓库与生成制品用途不同。仓库是权威输入；制品是恢复最近发布版本的最快方式。请把两者的区别说清楚。&lt;/p&gt;
&lt;p&gt;至少记录：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源码提交或归档标识；&lt;/li&gt;
&lt;li&gt;生成器与主题版本；&lt;/li&gt;
&lt;li&gt;制品包含的文件列表；&lt;/li&gt;
&lt;li&gt;重要下载的校验和；&lt;/li&gt;
&lt;li&gt;保留期限与存储位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不要把凭据、私钥、Cookie 或访问令牌放进网站备份。它们应属于独立的机密管理流程。&lt;/p&gt;
&lt;h2 id="存储前先验证"&gt;存储前先验证&lt;/h2&gt;
&lt;p&gt;打不开的归档不是备份。一个最小的验证循环如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -czf site-2026-08-22.tar.gz public/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -tzf site-2026-08-22.tar.gz &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256sum site-2026-08-22.tar.gz &amp;gt; site-2026-08-22.tar.gz.sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;校验和验证的是被存储的这个文件，并不能证明所有应用层面的假设都正确。这正是为什么仍需要一次恢复演练。&lt;/p&gt;
&lt;h2 id="在某个可丢弃的地方恢复"&gt;在某个可丢弃的地方恢复&lt;/h2&gt;
&lt;p&gt;把归档解包到临时目录或隔离主机，然后检查入口点与一个有代表性的资源。不要用覆盖唯一活动副本的方式测试恢复。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;restore_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xzf site-2026-08-22.tar.gz -C &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;/public/index.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;/public/404.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这条命令刻意很平凡。演练应在事故到来之前，暴露出缺失文件、错误权限或未记录的依赖。&lt;/p&gt;
&lt;h2 id="诚实地度量新鲜度"&gt;诚实地度量新鲜度&lt;/h2&gt;
&lt;p&gt;备份日程只有在相对可能丢失的工作量时才有意义。如果源码一个月才变一次，每日快照可能不必要；如果某个资源每小时更新，一次月度归档又不够。&lt;/p&gt;
&lt;p&gt;写下目标恢复点与恢复时间，再选择能满足这些目标的存储与检查。当最后一次经过验证的恢复备份已经陈旧时，不要宣称&amp;quot;连续保护&amp;rdquo;。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/tar/manual/tar.html"&gt;GNU tar manual&lt;/a&gt;，归档创建与验证行为，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;静态网站发布检查清单&lt;/a&gt;，本地配套资源。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>可靠的默认行为胜过聪明的恢复</title><link>https://www.xbcatm.com/zh/notes/reliable-defaults-beat-clever-recovery/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/reliable-defaults-beat-clever-recovery/</guid><description>一条可预测的首要路径比复杂的兜底更能预防事故。</description><content:encoded>&lt;p&gt;小服务往往在正常路径被充分理解之前，就先累积了恢复逻辑。每一次重试、备用端点与兼容分支，都增加了必须被观察和测试的一个状态。&lt;/p&gt;
&lt;h2 id="让常见路径变得无聊"&gt;让常见路径变得无聊&lt;/h2&gt;
&lt;p&gt;一个有用的默认行为是明确的、易于检视的、且可安全重复的。对静态站点，可以简单到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构建自固定的工具链。&lt;/li&gt;
&lt;li&gt;发布一个完整目录。&lt;/li&gt;
&lt;li&gt;检查生成的 HTML 与资源。&lt;/li&gt;
&lt;li&gt;保留上一个目录以便回滚。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这段序列不如多阶段部署系统炫目，但它的状态可见。当某处失败时，运维者能回答：问题在源码、构建、复制还是 Web 服务器。&lt;/p&gt;
&lt;h2 id="只对可限定范围的操作重试"&gt;只对可限定范围的操作重试&lt;/h2&gt;
&lt;p&gt;当操作已知是瞬时的、且重复不会产生第二个副作用时，重试才有用。从源站下载可能可安全重试；数据库迁移则未必。把每个错误都看作可重试的，会掩盖需要关注的边界。&lt;/p&gt;
&lt;p&gt;当重试适当时，记录尝试次数与最终原因。类似&amp;quot;发布失败：校验和不匹配&amp;quot;的日志条目，比一串静默重试后跟一个笼统超时更有可操作性。&lt;/p&gt;
&lt;h2 id="偏好单一权威状态"&gt;偏好单一权威状态&lt;/h2&gt;
&lt;p&gt;配置、运行时状态与文档应一致地说明哪个目录是活动的、哪条命令产生它。如果一个发布可以被不同的人复制到三条不同路径，恢复就变成了猜谜。&lt;/p&gt;
&lt;p&gt;对静态部署，一份简短的发布记录就够了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;release: 2026-08-22
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;source: git commit &amp;lt;commit-id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;builder: Hugo 0.165.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;output: public/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;verification: links + headers + checksums
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;占位符 commit ID 应按设计在发布时解析，而不是在文档里杜撰。&lt;/p&gt;
&lt;h2 id="恢复应回到已知状态"&gt;恢复应回到已知状态&lt;/h2&gt;
&lt;p&gt;当恢复把服务带回最后一个已知良好目录，而不是在几个半成品替代品之间选择时，恢复最容易。保留上一次输出直到新输出通过检查，再按保留策略移除旧副本。&lt;/p&gt;
&lt;p&gt;目标不是消除失败，而是把失败缩小到能解释、把恢复缩小到能信任。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，9.2.2 节关于幂等方法与重试，已核验 2026-08-22。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;静态网站发布检查清单&lt;/a&gt;，本地配套资源。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>事故复盘应留下一份事实记录</title><link>https://www.xbcatm.com/zh/notes/a-postmortem-should-leave-a-fact-record/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/notes/a-postmortem-should-leave-a-fact-record/</guid><description>围绕捕获到的输出来组织事故记录，让复盘线程停留在证据而非记忆上。</description><content:encoded>&lt;p&gt;一份复盘在它的说法能被重新检查时才赢得信任。这意味着每个阶段都要点名产生该事实的命令，而不是某人记忆的转述。下面的四个阶段让任何写下来的复盘在日后都可复核。&lt;/p&gt;
&lt;h2 id="检测症状而且要带证据"&gt;检测：症状，而且要带证据&lt;/h2&gt;
&lt;p&gt;从最小的可验证症状与展示它的命令开始。&amp;ldquo;截图&amp;quot;好过一段散文描述：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ check-http-headers.sh https://www.xbcatm.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 200
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-security-policy: ... script-src &amp;#39;none&amp;#39; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;last-modified: Mon, 24 Aug 2026 11:20:54 GMT
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果症状是某个响应头变了，那么展示旧策略与新策略的输出应并排放在一起。如果是失败，请包含退出状态与错误行。&lt;/p&gt;
&lt;h2 id="界定范围影响多广以及你如何知道"&gt;界定范围：影响多广，以及你如何知道&lt;/h2&gt;
&lt;p&gt;用证据而不是形容词陈述爆炸半径。修复一个你能检视的边界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪些主机或路径返回同样的症状？每个候选主机跑一次 &lt;code&gt;check-http-headers.sh&lt;/code&gt; 就能回答。&lt;/li&gt;
&lt;li&gt;缓存与源站是否不同？比较 &lt;code&gt;Vary&lt;/code&gt; 与验证器。&lt;/li&gt;
&lt;li&gt;适用哪个回滚窗口？使用变更前仍生效的 TTL。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="修复变更及其预期效果"&gt;修复：变更及其预期效果&lt;/h2&gt;
&lt;p&gt;写出可重放的正确动作。优先选择恢复契约的最小变更，并说明修复后运行应显示什么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 期望出现在响应里的新策略行：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;script-src &lt;span class="s1"&gt;&amp;#39;none&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="验证事后运行"&gt;验证：事后运行&lt;/h2&gt;
&lt;p&gt;变更后重复检测命令，并演示观察到的输出现在与预期契约一致。没有最后这一节的复盘只是一个故事；有了它，它就是一份允许自己未来被再检查的记录。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;RFC 9110 — HTTP Semantics&lt;/a&gt;，响应元数据与条件请求，已核验 2026-08-24。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/zh/resources/"&gt;制品 HTTP 响应头检查&lt;/a&gt;，贯穿本记录使用的只读检查。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>