2026-10-09 · 部署 部署与运维
把静态站发布做稳:一次"先删后解包"的教训
事故是怎么发生的
发布脚本要干的事很普通:本地打包,scp 上传,服务器解包,重载 nginx,最后做公网验收。问题出在服务器那一段的执行顺序上,写的是「上传 → rm -rf 目标目录 → tar -xzf 包」。
那一次,上传那一步其实失败了。失败归失败,远端脚本照常往下跑:先执行了 rm -rf,而 tar 又找不到包。于是正式站点的公开目录被清空了大约一分钟。
万幸的是脚本在清空之前先打了一份改前备份(tar -czf 备份.tgz -C 目标目录 .),靠这份备份即时恢复了。也就是说,救回这次事故的不是脚本写得多聪明,而是那个看起来多余的备份步骤。
根因是三条叠在一起
单看每一条都不致命,叠起来才出事。
- 顺序错了。把"删除"放在了"确认包已到达"之前。正确的形态应该是:解包到临时目录 → 校验关键文件存在 → 再原子替换。删除只能发生在"替换"这一步,不能发生在"下载"这一步之前。
- PowerShell 变量撞车。脚本里写了
$HOST = 'deploy@…',而$HOST是 PowerShell 的只读自动变量$Host(变量名大小写不敏感),赋值当场报VariableNotWritable。 - 重命名只改了一半。把
$HOST批量替换成$DEST的时候,scp 那一行写的是${HOST}:(带花括号),字面量里并不含$HOST,于是被漏掉了。那一行因此拿到了一个乱七八糟的主机名,上传失败——这就是上面那次事故的扳机。
第二条和第三条的关系值得单独记一句:变量名写错本身只是让某一行报错,真正造成损失的是"报错之后脚本还在继续跑"。发布脚本里任何一步失败,都不该被当成可以忽略的噪声。
同一次里还踩了一个独立的坑
用 PowerShell 批量改文件时写了 $c.Replace($from, $to, 1)。这个三参数重载并不存在(三参版要的是 StringComparison 枚举,不是 int),调用直接抛异常,写入全部失败。可脚本没有停下来,继续往下跑,最后还照样打印了"已改 ✅"。
这条和上面的发布事故其实是同一个毛病的两种表现:脚本把"我以为做了什么"当成"实际发生了什么"来汇报。一个失败的写入,被一行成功的提示盖了过去。
留下来的三条
- 任何"清空 + 重建"的发布,默认形态必须是「解包到临时目录 → 校验 → 替换」。
rm -rf绝不能出现在包还没确认到达之前。 - 改前备份是必需品,不是可选项。这一次它就是唯一的救命绳。
- 写盘之后必须回读校验,而且校验要独立于写入路径。"脚本说成功了"永远不等于"事实已经改变"。这次正是自己写的独立复核,把那个假成功抓出来的。
顺带一句
$HOST / $HOME / $PID / $INPUT 这类名字,在 PowerShell 里不能拿来当自己的变量。踩过一次就够记一辈子了。