<?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/decisions/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/decisions/index.xml" rel="self" type="application/rss+xml"/><item><title>站点以静态树提供服务，而非动态应用服务器</title><link>https://www.xbcatm.com/zh/decisions/adr-static-not-dynamic-server/</link><pubDate>Tue, 25 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/decisions/adr-static-not-dynamic-server/</guid><description>决策记录：选择在 Caddy 之后提供构建好的静态树，而非动态应用，并说明接受的取舍。</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;本站是文档与运维内容：用来阅读、检索、归档的页面。早期构想考虑过用动态应用服务器来渲染模板、管理会话或提供交互组件。内容本身对服务器端状态、按用户的认证逻辑或客户端渲染都没有要求。&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;用 Caddy 将一个预构建的静态树作为站点提供服务，发布时整棵目录一次性交换。边缘后面没有应用服务器、没有脚本通道、没有数据库。&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;Caddy（公共边缘）──HTTPS──► 静态/ 树（资源、HTML、SVG）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;服务的契约由响应头（&lt;code&gt;script-src 'none'&lt;/code&gt;）与构建期检查强制，后者拒绝脚本与残缺产物。&lt;/p&gt;
&lt;h2 id="后果"&gt;后果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;发布平淡：&lt;/strong&gt; 发布是一次完整目录交换，因此没有状态迁移、没有 schema。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;威胁面窄：&lt;/strong&gt; 没有运行时执行用户输入，关闭了整类注入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存诚实：&lt;/strong&gt; 每个文件有稳定的字节身份与校验器。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接受的取舍：&lt;/strong&gt; 没有交互、没有认证个性化、没有每用户 HTML。若确实需要，应作为独立的服务来组合，而不是并进这棵树。&lt;/li&gt;
&lt;/ul&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://caddyserver.com/docs/caddyfile/directives/file_server"&gt;Caddy 文档：&lt;code&gt;file_server&lt;/code&gt;&lt;/a&gt;，在没有应用层的情况下提供静态根目录，已核验 2026-08-25。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"&gt;MDN：内容安全策略&lt;/a&gt;，限制边缘会执行什么，已核验 2026-08-25。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>钉住 vendored 主题与生成器版本</title><link>https://www.xbcatm.com/zh/decisions/adr-pin-vendored-theme-and-generator/</link><pubDate>Fri, 21 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/decisions/adr-pin-vendored-theme-and-generator/</guid><description>决策记录：选择把主题纳入版本库并钉住 Hugo 版本，使重新构建能复现相同输出。</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;本站用 Hugo 生成器与 PaperMod 主题构建。两者任一未跟踪的升级都可能静默改变输出：主题的模板、生成器的渲染规则，或两者都可能改变两次&amp;quot;相同&amp;quot;重建之间的 HTML 字节。当发布用字节清单证明时，可复现不是可有可无；同一份源码的两次构建必须产生同一棵树。&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;把主题纳入仓库，并把确切的生成器版本与主题版本记录在钉住文件 &lt;code&gt;data/upstream-pins.toml&lt;/code&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;Hugo 0.165.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PaperMod v8.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;data/upstream-pins.toml （钉住）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;被纳管的主题放在 &lt;code&gt;themes/&lt;/code&gt; 目录下，作为提交的一部分，因此也成为一次发布字节身份的一部分。&lt;/p&gt;
&lt;h2 id="后果"&gt;后果&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可复现输出：&lt;/strong&gt; 相同源码加上钉住的生成器得到相同树，使清单有意义。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;升级可见：&lt;/strong&gt; 升主题或生成器是对钉钉文件的显式、可审的改变，而非意外漂移。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;来源可审计：&lt;/strong&gt; 任何一次发布的读者都能看到是哪个生成器与主题产生了这些字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接受的取舍：&lt;/strong&gt; 仓库带上 vendored 主题与钉住簿记；相比静默的输出漂移，这个代价很小。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="为什么它胜出"&gt;为什么它胜出&lt;/h2&gt;
&lt;p&gt;输入未钉住的构建，今天可能给正确的答案，明天却给出不同的树，而 diff 与清单都无法告诉你为什么。钉住把&amp;quot;构建变了&amp;quot;变成&amp;quot;钉住变了&amp;quot;，而这是一个 changelog 真正能回答的问题。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/usage/"&gt;Hugo 文档：版本&lt;/a&gt;，报告并匹配生成器发布版，已核验 2026-08-21。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semver.org/"&gt;语义化版本规范&lt;/a&gt;，为什么版本边界让可复现有意义，已核验 2026-08-21。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>通过完整目录交换发布，绝不做逐文件覆盖</title><link>https://www.xbcatm.com/zh/decisions/adr-complete-directory-release/</link><pubDate>Thu, 20 Aug 2026 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/decisions/adr-complete-directory-release/</guid><description>决策记录：发布作为单一目录名之后的一次整树交换，保留旧树以便回滚。</description><content:encoded>&lt;h2 id="context"&gt;Context&lt;/h2&gt;
&lt;p&gt;通过把变更文件覆盖到线上树上来部署，会有访问者看到部分发布的窗口风险：某些文件是新的、另一些是旧的，相关联的资源彼此不一致。单个损坏文件也可能弄坏一个本来完好的线上站点。任何原地编辑被服务目录的发布路径，都让&amp;quot;上一个发布&amp;quot;难以命名和恢复。&lt;/p&gt;
&lt;h2 id="decision"&gt;Decision&lt;/h2&gt;
&lt;p&gt;发布时在线上树旁边暂存一棵完整的新树，然后重命名 Caddy 服务的单一目录指向它。旧树保留在磁盘上，用其先前的名字。&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;rsync -a public/ /srv/www/example-site.new/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv /srv/www/example-site.new /srv/www/example-site
&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;strong&gt;无部分状态：&lt;/strong&gt; 访问者总是看到一个连贯的发布。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回滚廉价：&lt;/strong&gt; 旧树仍然存在；恢复是一次反向重命名。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交换前可验证：&lt;/strong&gt; 暂存的树在被服务之前可以被检查和哈希。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储取舍：&lt;/strong&gt; 服务器持有当前树外加一棵暂存树；额外磁盘是原子性的代价。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="为什么它胜过就地同步"&gt;为什么它胜过就地同步&lt;/h2&gt;
&lt;p&gt;就地同步承诺在线调整一棵树，但它无法原子地回答&amp;quot;此刻正在被服务的字节是哪些&amp;quot;，并且让回滚复杂化为&amp;quot;回到昨天&amp;quot;。完整目录交换用一个持久事实——被服务的目录名——来回答发布问题，让前进与后退都是单次重命名。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.martinfowler.com/bliki/BlueGreenDeployment.html"&gt;Martin Fowler：蓝绿部署&lt;/a&gt;，完整树与蓝绿/暂存交换模型，已核验 2026-08-20。&lt;/li&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-20。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>