维护站点一段时间后,我发现自己每周都在重复同样的动作:构建、上传、刷新缓存、检查证书、确认备份、跑一遍链接检查。单次都不长,但加起来每周要花一两个小时,而且很容易漏。

漏掉的后果不一定立刻出现:证书忘了续,某天突然打不开;备份失败了没发现,真正需要时没有可用副本。这些事不适合靠记性。

先列出重复动作

我没有一上来就写脚本,而是先记录了两周。最后列出来的清单大概是:

动作 频率 耗时 出错后果
构建并发布 每周 1—2 次 15 分钟 页面不更新
刷新 CDN 每次发布 5 分钟 读者看到旧内容
证书检查 每周 5 分钟 证书过期
备份检查 每周 5 分钟 数据丢失
链接检查 每月 20 分钟 死链
图片压缩 每次加图 10 分钟 页面变慢

这张表让我看清一件事:真正耗时的不是单个动作,而是“切换上下文”——每次都要登录、找命令、回忆步骤。自动化的价值,很大一部分是把这个切换成本消掉。

分层:哪些放本地,哪些放服务器

我把自动化分成两层。

构建与发布放在 CI。 提交代码后自动构建,构建成功后同步到服务器。这样本地不需要装构建环境,发布也不依赖我的电脑是否开机。

服务器上的定时任务用 systemd timer。 证书检查、备份、日志清理这类事和代码发布无关,放在服务器上更合适。相比 crontab,systemd timer 的好处是能查状态、能看日志、能设置依赖。

一个最小的 timer 大概是这样:

1
2
3
4
5
6
7
8
9
10
# /etc/systemd/system/axis-backup.timer
[Unit]
Description=Daily site backup

[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true 很重要:机器在 03:30 关机时,下次启动会补跑一次,而不是直接跳过。

脚本要能安全失败

自动化最危险的不是“没跑”,而是“跑了一半”。我给自己定了几个规则:

  • 幂等:同一脚本重复执行,结果一致;
  • 先 dry-run:涉及删除、覆盖的脚本先打印将要做什么,确认后再执行;
  • 失败即停:任何一步失败就退出,不继续往下走;
  • 有超时:网络操作设置超时,避免挂住;
  • 有锁:防止上一次没跑完,下一次又启动。

一个简化过的备份脚本骨架:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env bash
set -euo pipefail

LOCK=/tmp/axis-backup.lock
exec 9>"$LOCK"
flock -n 9 || { echo "already running"; exit 0; }

SRC=/srv/axis
DST=/backup/axis

rsync -a --delete "$SRC/" "$DST/daily/"
restic backup "$SRC" --tag daily
restic check --read-data-subset=5%

set -euo pipefail 让脚本在错误时立刻退出,flock 防止并发,restic check 做一次抽样校验。这些都不复杂,但能避免很多“以为成功了”的情况。

失败必须被看见

自动化之后,我最担心的不是它出错,而是它出错了没人知道。所以每个任务都接入了同一条通知通道:失败发消息,连续失败再发一次。

通知里包含三样东西:哪个任务、哪一步失败、日志在哪。没有这三样,通知只会让人焦虑,不能帮助处理。

我没有自动化的事

有几件事我刻意保留人工:

  • DNS 切换:影响面大,宁可手动确认;
  • 删除数据:再小心也不为过,必须人工二次确认;
  • 大版本升级:可能改变行为,需要先看变更说明;
  • 发布前的最终检查:自动构建通过不代表内容没问题。

自动化的边界不是“能不能做”,而是“出错后我能不能承受”。不可逆的操作,宁可慢一点。

时间账

现在每周花在重复工作上的时间,从一两个小时降到十几分钟,主要是看一眼通知和偶尔处理失败。

但比时间更重要的是心态变化:以前我总有一根弦绷着,怕忘了续证书、忘了备份;现在这些事有固定的执行者和失败通知,我可以把注意力放在写作和真正的优化上。

工具是梁,不是目的。好的自动化应该让人感觉不到它的存在,只在出问题时提醒你:这里需要看一眼。