把交接文档摊开以后,我做的第一件事,是把几个容易混在一起的名字分开:前端页面、公开接口、后端服务、数据库,以及承载写入的那台机器。
以前我会把“博客能打开”当成系统状态。首页返回 200,文章能点进去,RSS 也能订阅,好像这几件事已经替所有问题盖了章。后来一次上游同步把这个想法拆开了。GitHub Actions 可以全部通过,Vercel 仍然可能因为子模块和安装路径失败;页面重新发布以后,RSS 还要单独确认;文章写入接口返回成功,也不代表详情页已经读到了同一份内容。
先把几条线画出来
现在的博客至少有四条互相牵连、又不能互相代替的线。
第一条是前端。它负责页面、路由、缓存和 Feed 的展示,代码在自己的镜像仓库里跟随上游更新。同步脚本会抓取上游树,再把保留的主题适配、测试和图片资源放回去。这里最容易发生的误会,是把“同步成功”理解成“站点已经部署”。同步只证明工作树能够生成一次提交,Vercel 还要用自己的项目设置、安装命令和构建环境重新跑一遍。
第二条是 API。首页从这里拿聚合数据,文章详情、手记、分类和 RSS 也沿着这条路径走。API 健康不等于前端所有页面都正常,但它能帮助判断故障落在网络入口,还是落在页面渲染。排查时先看公开的 ping、health 和匿名聚合,比直接清缓存更容易留下证据。
第三条是后端和数据库。文章的写入属于这一层,日期、公开状态、栏目、标签和正文都应该通过受控接口修改。以前我也想过直接进数据库改一列,后来发现那样很难保证缓存、RSS、搜索和前端页面一起更新。除非是事故窗口且已经有明确的 SQL 回滚,否则日常编辑不应该绕过 API。
第四条是运行环境。公开入口、主节点和备用节点承担的事情不一样。入口机器的 Nginx 可以决定请求送到哪里,主节点承载当前写入,备用节点是否可接管还要看代码版本、数据恢复时间、附件和配置是否齐全。把目录复制过去,只能证明文件到了,不能证明它能在故障时接住请求。
绿色检查不是最后一站
最近一次上游同步的失败很有代表性。第一次失败来自提交钩子:上游的 lint-staged 想在镜像工作区里自动 stash,但这个工作区并没有适合它保存的状态。绕过钩子以后,第二次失败又暴露了 Vercel 的安装环境:设计系统子模块没有在项目构建前准备好,复制到工作区以后,冻结锁文件又找不到对应的 importer。
每一次红色都只回答一个很窄的问题。GitHub Actions 通过,说明同步脚本的测试和生产构建完成;它没有回答 Vercel 是否能从自己的根目录安装依赖。Vercel Ready,说明产物发布完成;它没有替我打开文章、分类和 RSS。两个域名返回 200,说明公开入口可达;它没有说明私密手记不会出现在聚合里。
所以交接文档不能只写“执行这个命令”。还要写命令前应该确认什么、命令失败时停在哪里、成功以后要读回哪几个页面。没有这些边界,下一位维护者很容易把重试当排查,把清缓存当修复,把一次成功当长期稳定。
写入也需要回执
文章写作和运维其实共享同一条习惯:做完以后再读一次。
创建一条私有手记时,我现在会保留标题、slug、正文哈希、栏目或话题和公开状态。接口返回 ID 只是第一张回执;随后必须用同一个 ID 读回,比较这些字段是否仍然一致。再访问列表、详情和 RSS,确认私有对象没有因为聚合或缓存出现在公开面。
这件事看起来比“保存成功”慢一点,但它把很多模糊的担心变成了可以定位的差异:是写入没生效,是读回拿到旧缓存,是某个列表过滤漏了,还是前端把标题又渲染了一次。以前我会先想着怎样把流程做得更快,现在更愿意先让每一步留下可以复查的东西。
备用节点要回答“能不能回来”
四台机器的拓扑写进手册以后,我才发现“有备用”这个说法太宽了。备用节点至少要回答几个问题:自己来自哪个代码版本,数据库恢复到哪一刻,图片和附件是否在,环境变量和证书是否齐全,服务启动以后健康检查是否通过,切回主节点时怎样撤销它接收的写入。
这些问题没有答案时,备用节点只是另一份看起来很像的目录。它可能能启动,也可能只能返回一张首页;它可能有昨天的数据库,却没有今天的上传文件。一个合格的同步脚本,应该先生成备份和版本账本,再对每个目标做健康检查。任何一个目标失败,都应该停在原处,留下失败原因,而不是继续把半成品推向下一台机器。
交接不是把所有东西都交出去
整理 README 的时候,我刻意没有把密码、会话信息、访问凭据和私密语雀内容塞进去。下一位智能体需要知道入口、架构、命令顺序和回滚条件,但不需要在文档里看到能够直接登录生产的秘密。认证应该通过本机受限的 Profile、钥匙串或一次性登录恢复;原始素材也应该停留在它原来的权限边界内。
这同样适用于博客文章。语雀知识库、备忘录和 GitHub 提交可以帮助我回忆这几年做过什么,却不意味着每张卡片都适合公开。先登记来源,再分开事实、感受、外部摘录和生成内容;不能核对的地方留下空白,不拿一个化名遮住别人的身份,也不把计划表写成已经完成的项目。
我希望下一个接手博客的人,看到的不是一串看似万能的脚本,而是一张不容易误解的地图:哪里可以读,哪里可以写,哪里必须先备份;哪一个绿色只代表一层通过,哪一个红色应该让流程停下来;哪些内容可以继续整理,哪些内容即使很有趣也不能搬到公开页面。
博客因此不只是页面和文章的集合。它还包括每次修改留下的版本、每次失败留下的原因,以及下一次有人打开文档时,不必重新猜一遍的边界。