Linux VPS 变更防护三重保险:快照+Git+apt回滚实战

Linux VPS 变更防护三重保险:快照+Git+apt回滚实战

1. 这不是“后悔药”,而是 Linux 系统变更的三重保险机制

你刚在 VPS 上执行了一条sudo apt-get upgrade,结果发现某个关键服务突然无法启动;或者你手快改了/etc/nginx/sites-available/default,保存后nginx -t报错,但已经记不清改了哪一行;又或者你误删了/var/www/html/index.php,而服务器上根本没有配置自动备份。这时候,你最想要的不是“重装系统”,而是一台时间机器——能精准倒带,只撤销那一次错误操作,其他所有配置、数据、用户、权限全部原封不动。

但 Linux 没有内置的时间机器。它有的,是三套彼此独立、互为补充、覆盖不同粒度的“撤销能力”:文件级快照(Backups)代码/配置版本控制(Git)包管理器事务回溯(apt-get)。这三者不是并列选项,而是分层防御体系——就像给你的 VPS 穿上了三层防弹衣:最外层是整机快照(Backup),中层是配置与脚本的精确历史(Git),最内层是系统软件包的原子化安装与卸载(apt-get)。很多人只用其中一种,结果在某一层失效时彻底抓瞎;而真正稳定的运维习惯,是让这三层在日常操作中自然咬合,形成一条可验证、可追溯、可逆向的完整操作链。

我第一次在生产环境里吃这个亏,是在一台跑着 WordPress 的 Ubuntu 20.04 VPS 上。为了升级 PHP 版本,我执行了sudo apt-get install php8.1,系统自动移除了旧版php7.4-fpm,但没提示我wordpress包依赖的是php7.4。网站瞬间白屏,systemctl status php7.4-fpm显示服务不存在。当时我脑子里只有两个念头:一是翻 SSH 历史记录找命令,二是祈祷apt-get能像 Windows 的“程序和功能”一样点一下就卸载。现实是:apt-get remove只能删包,不能还原依赖关系;history里只有一行命令,没有上下文;而/var/backups下的apt.extended_states文件,我连看都没看过。那次花了 47 分钟才靠手动重装 PHP7.4 并恢复配置,期间网站完全不可用。从那以后,我强制自己在每次apt-get操作前,先git add .当前配置目录;每次修改 Nginx 配置,必先git commit -m "before php upgrade";每次重大系统更新,必先触发一次快照备份。这不是多此一举,而是把“出错成本”从“小时级停机”压缩到“秒级回滚”。

这篇文章不讲“如何安装 Git”或“apt-get 基础命令”,那些网上一搜一大把。我要带你拆解的是:这三套机制在真实 VPS 场景下,如何协同工作、各自边界在哪、什么情况下会失效、以及最关键的——如何用最小代价让它们成为你肌肉记忆的一部分。你会看到一个完整的、可落地的“变更防护流程”,它不依赖任何第三方 SaaS 工具,只用 Linux 自带组件,却能在绝大多数误操作场景下,让你在 30 秒内回到安全状态。

2. Backups:整机快照不是“以防万一”,而是“必须前置”的操作锚点

很多人把备份理解成“出了事再做”,这是最大的认知陷阱。在 VPS 环境下,Backup 的核心价值从来不是“恢复整个系统”,而是为 Git 和 apt-get 提供一个可信的、可验证的、时间戳明确的操作基线。没有这个基线,Git 的提交可能基于一个已损坏的配置,apt-get 的回滚可能无法解决底层库冲突——因为它们都假设“当前系统是健康的”。而 Backup,就是那个“健康”的定义者。

2.1 为什么不用 rsync 或 tar 就地打包?

你可能会想:“我每天tar -czf /backup/vps-$(date +%F).tar.gz /etc /var/www /home不就行了吗?”技术上可行,但实操中存在三个致命缺陷:

  1. 一致性风险tar是逐文件打包,如果在打包过程中,MySQL 正在写入/var/lib/mysql,Nginx 正在轮转/var/log/nginx/access.log,那么生成的 tar 包里,数据库文件和日志文件的状态是不同步的。恢复后,MySQL 可能因日志不匹配而拒绝启动。
  2. 恢复粒度粗:你只能恢复整个/etc目录,无法只还原/etc/nginx/sites-available/myapp这一个文件。而实际问题往往就出在单个配置文件上。
  3. 无验证机制tar打包成功不等于可恢复成功。你永远不知道那个.tar.gz文件在磁盘上是否已静默损坏,直到真要恢复时才发现gzip: corrupt input

这就是为什么,VPS 备份的黄金标准是快照(Snapshot),而非文件归档。主流云平台(如 AWS EC2、DigitalOcean Droplets、Oracle Cloud Infrastructure)都提供块存储级别的快照功能。它的原理是:在发起快照指令的瞬间,存储系统冻结当前所有数据块的状态,并创建一个只读的、指向这些块的指针集合。整个过程耗时通常在毫秒级,对运行中的服务零影响,且保证了文件系统级的一致性。

提示:快照不是“复制数据”,而是“记录数据位置”。因此首次快照几乎瞬时完成,后续增量快照也极快。但快照本身不等于备份——它依附于原磁盘存在。一旦磁盘物理损坏,快照也随之消失。所以,快照必须配合“跨区域复制”或“导出为 AMI/镜像”才能构成真正的异地备份。

2.2 快照的正确使用节奏:不是“每天一次”,而是“每次变更前”

很多教程教你怎么设置 cron 每天凌晨 2 点自动快照。这在数据量小、变更少的测试环境尚可,但在生产 VPS 上,它会产生大量无效快照,浪费存储费用,更关键的是——它无法覆盖你最需要的时刻:就在你敲下sudo apt-get install的前一秒。

我的实践节奏是:

  • 重大系统变更前(如apt-get dist-upgrade,kernel升级,glibc更新):手动触发一次快照,命名规则为pre-apt-upgrade-20240520-1430
  • 应用部署前(如git pull新代码、composer installnpm run build):手动触发快照,命名为pre-deploy-myapp-v2.3.1
  • 配置批量修改前(如一次性修改 5 个 Nginx vhost、调整防火墙规则):手动触发快照,命名为pre-config-batch-20240520-firewall+nginx

这个节奏的关键在于“手动”和“命名”。手动,是为了强制你停下来思考:“这次操作的风险是什么?我是否已备份了当前状态?”命名,则是为了在快照列表里一眼识别出“那个救了我命的快照”。我在 DigitalOcean 控制台的快照列表里,永远能看到类似这样的命名:

pre-apt-upgrade-20240515-1622 (Size: 2.1 GB, Created: May 15, 2024) pre-deploy-wordpress-6.5-20240518-0911 (Size: 1.8 GB, Created: May 18, 2024) pre-config-nginx-ssl-20240520-1405 (Size: 1.9 GB, Created: May 20, 2024)

注意:快照恢复是“整盘替换”操作,会覆盖当前磁盘上的所有数据。因此,恢复前务必确认目标 VPS 已停止所有服务(sudo systemctl stop nginx mysql php8.1-fpm),并确保你没有在/tmp/run下存放重要临时数据——这些内存文件系统在重启后本就不该持久化。

2.3 快照之外的兜底方案:当云平台不支持快照时

如果你的 VPS 服务商(如某些廉价 OpenVZ 容器)不提供快照功能,或者你用的是本地虚拟机(VirtualBox/KVM),那么必须退回到文件级备份,但要用更严谨的方式:

  1. 使用rsync+--link-dest实现硬链接快照
    这是rsync最被低估的功能。它能创建多个“看起来像完整备份”的目录,但实际只占用一份数据的磁盘空间。命令如下:

    # 创建基础备份 rsync -aHAX --delete /etc /var/www /home /root /backup/full-20240520/ # 创建增量备份(硬链接到上一个备份) rsync -aHAX --delete --link-dest=/backup/full-20240520/ /etc /var/www /home /root /backup/incr-20240521/

    incr-20240521/目录下的所有未改动文件,都是指向full-20240520/中对应文件的硬链接,只占用 inode,不占额外空间。只有真正变化的文件才会被复制。这样,你就能拥有多个时间点的“完整视图”,而总空间占用接近单次全备。

  2. 强制校验与压缩
    rsync后,立即执行:

    # 生成校验和,存入备份目录 find /backup/incr-20240521/ -type f -exec sha256sum {} \; > /backup/incr-20240521/SHA256SUMS # 压缩为 tar.xz(比 gzip 压缩率高 30%,且支持完整性校验) tar -cJf /backup/incr-20240521.tar.xz -C /backup incr-20240521/ # 验证压缩包完整性 xz -t /backup/incr-20240521.tar.xz

    这一步看似繁琐,但它把“备份成功”从“命令返回 0”提升到了“数据可验证、可解压、可校验”的工业级标准。我见过太多人备份脚本 cron 运行成功,但.tar.gz文件实际是空的,因为磁盘满了导致tar静默失败。

3. Git:把/etc/var/www变成你的“配置代码仓库”

如果说 Backup 是给你一艘救生艇,那么 Git 就是给你一套精密的航海日志和舵轮。它不负责把你从风暴中拉出来,但它能让你清晰地知道:风暴是从哪一刻开始的?风向是如何转变的?哪一次转向导致了触礁?更重要的是,它允许你只修正舵轮角度,而不必重造整艘船。

3.1 为什么 Git 是配置管理的唯一合理选择?

有人会问:“我用etckeeper不就行了吗?它也是基于 Git 的。”没错,etckeeper是一个成熟的工具,但它是一个“黑盒”。它自动提交/etc,但你无法控制提交时机、无法编写有意义的提交信息、无法在提交前运行自定义检查(比如nginx -t)。而在 VPS 运维中,可控性比自动化更重要。一个失控的自动化,比手动操作危险十倍。

Git 的优势在于其“显式性”:

  • git status告诉你哪些文件被修改了;
  • git diff告诉你具体改了哪几行;
  • git commit -m "fix: nginx ssl cert path for myapp"告诉你为什么改;
  • git log --oneline --graph告诉你整个配置演进的脉络。

我管理的每一台 VPS,/etc目录下都有一个.git仓库。这不是为了“版本控制”,而是为了“变更审计”。当apt-get upgrade导致服务异常时,我第一反应不是查日志,而是:

cd /etc git status # 看哪些配置文件被 apt 自动修改了(比如 /etc/default/grub) git diff # 看它到底改了什么 git log -n 5 --oneline # 看最近 5 次提交,找到升级前的那个 commit

然后,我可以精准地git checkout <pre-upgrade-commit> -- /etc/default/grub,只还原这个文件,其他一切保持不变。这才是真正的“撤销变更”,而不是“恢复整个系统”。

3.2 初始化你的/etcGit 仓库:避开 3 个经典坑

初始化/etc仓库看似简单,但新手常踩三个深坑:

坑一:忽略二进制文件和敏感信息
/etc/shadow/etc/gshadow/etc/ssl/private/下的私钥,绝对不能加入 Git。但直接git add /etc会把它们一并纳入。正确做法是:

cd /etc git init # 创建 .gitignore,严格过滤 cat > .gitignore << 'EOF' # 敏感文件 shadow gshadow *.pem *.key *.crt # 二进制/动态文件 mtab resolv.conf hostname # 日志和缓存 log/ cache/ tmp/ EOF git add . git commit -m "initial commit: /etc skeleton"

注意:resolv.conf被忽略,是因为它常由 DHCP 或 systemd-resolved 动态生成,手动管理反而会导致 DNS 解析失败。

坑二:不处理符号链接
/etc/systemd/system/multi-user.target.wants/下的软链接,git add默认会追踪链接本身,而不是链接指向的目标文件。这会导致git checkout后链接失效。解决方案是启用core.followSymlinks

git config core.followSymlinks false

这样 Git 会把软链接当作普通文件处理,记录其路径和目标,恢复时能重建正确的链接。

坑三:不设置全局用户信息
Git 要求user.nameuser.email。在服务器上,你不希望每次git commit都输一遍。但设成个人邮箱又不妥(暴露隐私)。我的做法是:

git config --global user.name "vps-admin" git config --global user.email "admin@$(hostname -f)"

$(hostname -f)会解析出你的 FQDN(如myapp.example.com),这样每个 VPS 的提交者都是唯一的、匿名的、且可追溯的。

3.3 Git 工作流:让“提交”成为操作仪式感

Git 的威力,不在于它有多强大,而在于它能把随意的修改,变成一个有仪式感的、可审查的操作闭环。我的标准工作流是:

  1. 修改前,git status:确认当前工作区干净。如果有未提交的修改,先搞清楚它们是什么、是否应该提交。

  2. 修改后,git diff --no-index /dev/null /etc/nginx/sites-available/myapp:对于新创建的文件,git diff默认不显示,加--no-index强制对比。

  3. 提交前,sudo nginx -t && sudo systemctl daemon-reload:对 Nginx 配置,必须先语法检查;对 systemd 服务,必须重载配置。把检查命令写进提交信息里:

    git commit -m "feat: add myapp vhost - nginx -t: OK - systemctl daemon-reload: OK - tested with curl -I https://myapp.example.com"

    这样,未来的你或同事,看到这条提交,就知道它经过了哪些验证。

  4. 定期git push到远程仓库:我用的是私有 Git 服务器(Gitea),地址是git@git.internal:/vps/myapp-etc.git。推送不是为了协作,而是为了异地冗余。git push的输出,就是你的操作日志的第二份副本。

经验:我曾遇到过一次/etc仓库.git目录被意外删除的情况。幸好有远程仓库,git clone git@git.internal:/vps/myapp-etc.git /tmp/etc-restore && sudo cp -r /tmp/etc-restore/.git /etc/,30 秒就恢复了整个 Git 历史。Git 仓库本身,就是最轻量、最可靠的备份单元。

4. Apt-Get:理解包管理器的“事务性”与“非事务性”边界

apt-get常被误解为一个简单的“下载安装器”。实际上,在 Debian/Ubuntu 系统中,它是整个软件生态的“中央银行”——它管理着数以万计的软件包、它们之间的依赖关系、版本约束,以及一个名为dpkg的底层“账本”。理解apt-get的工作原理,是实现精准回滚的前提。

4.1apt-get的“事务”真相:它其实没有真正的事务

官方文档说apt-get是“事务性”的,但这是一种简化说法。真实情况是:apt-get在执行installupgrade时,会先计算一个“解决方案”,即一个待安装/升级/卸载的包列表,然后按顺序调用dpkg --install来一个个安装。dpkg本身是原子的(一个.deb包的安装要么全成功,要么全失败),但apt-get的整个操作链不是原子的。这意味着:

  • 如果apt-get upgrade在安装第 5 个包时失败(比如磁盘空间不足),前 4 个包已经成功安装,系统处于一个“半升级”状态。
  • apt-get不会自动回滚前 4 个包,它只会报错退出。

所以,“用apt-get撤销变更”的核心,不是指望它有“回滚按钮”,而是利用它维护的元数据,手动构造一个反向操作序列。这个元数据,就藏在/var/log/apt/history.log/var/lib/apt/lists/里。

4.2 回滚apt-get upgrade:三步定位法

假设你执行了sudo apt-get upgrade,之后发现curl命令无法解析域名。你想回到升级前的状态。不要慌,按以下三步走:

第一步:锁定升级时间窗口
/var/log/apt/history.log记录了每一次apt操作的精确时间、命令和涉及的包。用grep找到最近的upgrade

grep -A 10 "Commandline: /usr/bin/apt-get upgrade" /var/log/apt/history.log | tail -n +2

输出类似:

Upgrade: libcurl4:amd64 (7.68.0-1ubuntu2.20, 7.68.0-1ubuntu2.21), curl:amd64 (7.68.0-1ubuntu2.20, 7.68.0-1ubuntu2.21) End-Date: 2024-05-20 14:30:22

这告诉你,libcurl4curl7.68.0-1ubuntu2.20升级到了7.68.0-1ubuntu2.21

第二步:查询旧版本包是否存在
apt-get默认只保留最新版本的.deb包。但旧版本包很可能还在本地缓存里:

ls /var/cache/apt/archives/ | grep "libcurl4_7.68.0-1ubuntu2.20" # 如果存在,说明可以直接降级 # 如果不存在,需要从官方仓库下载

第三步:执行精准降级
有两种方式:

  • 方式 A(推荐,安全):用apt-get install <package>=<version>强制指定版本

    sudo apt-get install libcurl4=7.68.0-1ubuntu2.20 curl=7.68.0-1ubuntu2.20

    apt-get会自动解决依赖,如果旧版本依赖的其他包也被升级了,它会一并降级。这是最稳妥的方式。

  • 方式 B(激进):用dpkg --force-downgrade直接安装.deb

    sudo dpkg --force-downgrade -i /var/cache/apt/archives/libcurl4_7.68.0-1ubuntu2.20_amd64.deb

    这绕过了apt的依赖检查,风险极高,仅在apt-get install报“依赖冲突”且你确定可以忽略时使用。

注意:apt-get install <package>=<version>会将该包标记为“手动安装”,防止下次apt-get autoremove误删。你可以用apt-mark showmanual | grep libcurl4来确认。

4.3apt-get autoremove的“幽灵依赖”陷阱

apt-get autoremove是一个双刃剑。它能清理掉“不再被任何已安装包依赖”的包,但有时会误判。例如,你安装了build-essential,它依赖g++。后来你卸载了build-essential,但g++仍留在系统里,因为apt认为它可能是你手动安装的。此时autoremove不会动它。

但如果你之前用apt-get install g++手动安装过,apt就会把它标记为“手动安装”,autoremove永远不会碰它。而如果你是通过build-essential间接安装的,g++就是“自动安装”,autoremove会把它删掉。

如何区分?看apt-mark showautoapt-mark showmanual。我的经验是:永远不要在生产 VPS 上无脑执行apt-get autoremoveapt-mark showauto | grep -E "(g\+\+|gcc|make)",确认你要删的包确实不是你主动需要的,再执行。

5. 三重保险的协同实战:一次真实的 PHP 升级故障复盘

理论讲完,现在用一个真实案例,把 Backup、Git、apt-get 三者如何协同,完整演示一遍。这个案例,就发生在我昨天管理的一台 Ubuntu 22.04 VPS 上。

5.1 故障背景:一次“无害”的 PHP 版本切换

客户要求将 WordPress 站点从 PHP 7.4 升级到 PHP 8.1。这是一个标准操作,但我依然遵循了“变更三步曲”:

  1. Backup:在 DigitalOcean 控制台,点击 “Create Snapshot”,命名为pre-php-upgrade-20240520-1500
  2. Gitcd /etc && git status确认无未提交修改,然后git commit -m "pre: php 7.4 -> 8.1 upgrade"
  3. apt-get:执行sudo apt-get install php8.1 php8.1-fpm php8.1-mysql php8.1-curl

一切顺利。php -v显示 8.1,systemctl status php8.1-fpm是 active。我修改了 Nginx 配置,将fastcgi_pass指向127.0.0.1:9001(PHP 8.1 的端口),sudo nginx -t && sudo systemctl reload nginx,然后访问网站——502 Bad Gateway。

5.2 排查链路:三重保险如何依次亮起红灯

第一层:Git 告诉我“配置没改错”
cd /etc/nginx/sites-available/ && git diff myapp显示,我只改了fastcgi_pass这一行,语法正确。git log --oneline -n 3确认,上一次成功的提交是pre-php-upgrade-20240520-1500,而当前是post-php-upgrade-20240520-1515。Git 排除了配置错误。

第二层:apt-get 告诉我“PHP 8.1 服务没起来”
sudo systemctl status php8.1-fpm输出:

● php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled) Active: failed (Result: exit-code) since Mon 2024-05-20 15:15:22 UTC; 2min 10s ago Main PID: 12345 (code=exited, status=78/CONFIG)

status=78/CONFIG是关键线索,表示 PHP-FPM 配置文件有严重错误。journalctl -u php8.1-fpm -n 50显示:

[15:15:22] ERROR: Unable to include /etc/php/8.1/fpm/pool.d/www.conf from /etc/php/8.1/fpm/php-fpm.conf

原来,www.conf文件里有一行listen = /run/php/php8.1-fpm.sock,但/run/php/目录不存在。这是 Ubuntu 22.04 的一个已知 bug,php8.1-fpm包的 postinst 脚本没有创建这个目录。

第三层:Backup 成为最终防线
此时,Git 和 apt-get 都帮不上忙了。Git 只能告诉我配置是对的,apt-get 只能告诉我服务启动失败,但无法修复缺失的目录。我有两个选择:

  • A. 手动创建/run/php/并赋权,然后systemctl start php8.1-fpm
  • B. 直接恢复到pre-php-upgrade-20240520-1500快照。

我选了 A,因为创建目录是秒级操作。但当我执行sudo mkdir -p /run/php && sudo chown www-data:www-data /run/php后,systemctl start php8.1-fpm依然失败,journalctl显示新的错误:Failed to listen on /run/php/php8.1-fpm.sock: Permission denied。原来,/run/php/目录的权限是drwxr-xr-x,而www-data用户需要rwx权限。sudo chmod 755 /run/php也不行,因为/run是 tmpfs,重启就消失。

这时,Backup 的价值凸显了。我打开 DigitalOcean 控制台,找到pre-php-upgrade-20240520-1500快照,点击 “Restore Snapshot”,选择“覆盖当前磁盘”。整个过程耗时 92 秒。VPS 重启后,php -v回到 7.4,nginx服务正常,网站立刻恢复。整个故障从发生到恢复,总计 3 分钟。

5.3 复盘与加固:让下一次升级不再踩坑

这次故障的价值,不在于它被解决了,而在于它暴露了流程的缺口。我做了三件事来加固:

  1. 在 Git 提交模板中增加“启动检查”
    我修改了/etc/.gitmessage模板:

    feat: upgrade php to 8.1 - apt-get install: OK - php -v: 8.1.27 - systemctl is-active php8.1-fpm: inactive (expected, not started yet) - mkdir /run/php: OK - systemctl start php8.1-fpm: OK - systemctl is-active php8.1-fpm: active

    现在,每次git commit,都必须填满这个模板,否则提交会被 pre-commit hook 拒绝。

  2. 为 apt-get 操作添加“预检脚本”
    我创建了/usr/local/bin/apt-safe-upgrade

    #!/bin/bash echo "=== Pre-upgrade check ===" df -h / | awk '$5 > 80 {print "WARNING: Root partition usage > 80%"}' free -h | awk '$2 ~ /G/ && $3/$2*100 > 80 {print "WARNING: Memory usage > 80%"}' echo "=== Running apt-get upgrade ===" sudo apt-get upgrade "$@"

    以后,我只执行apt-safe-upgrade,而不是裸apt-get upgrade

  3. 将快照恢复操作写成一键脚本
    ~/bin/restore-snapshot.sh

    #!/bin/bash # 从 DigitalOcean API 触发快照恢复(需提前配置 API Token) doctl compute droplet-action restore $DROPLET_ID $SNAPSHOT_ID echo "Restore initiated. Droplet will reboot automatically."

    这样,恢复操作从控制台点击 5 步,变成终端里一条命令。

6. 个人经验总结:让“撤销能力”成为你的本能反射

写到这里,你可能觉得这套流程很重。但我想说的是:它不是一套要你“学习”的知识,而是一种要你“养成”的肌肉记忆。就像老司机开车,不会去想“离合器怎么踩”,而是左脚一抬,车就走了。运维的最高境界,就是让 Backup、Git、apt-get 的操作,变成你敲命令前的一个下意识动作。

我自己用了这套方法三年,最大的体会有三点:

第一,时间花在“预防”上,永远比花在“抢救”上值。
一次快照触发,3 秒;一次git commit,5 秒;一次apt-get前的apt list --upgradable检查,2 秒。加起来不到 10 秒。而一次生产环境故障排查,平均耗时 22 分钟。三年下来,我节省的时间,足够我多学两门编程语言。

第二,工具的价值,不在于它多炫酷,而在于它多“不打扰”。
我从不追求“全自动备份”或“AI 智能回滚”。那些东西太重,容易出错。我只要求:快照按钮在控制台显眼位置;git commit的快捷别名gc已设好;apt-safe-upgrade脚本在$PATH里。越简单,越可靠。

第三,真正的安全感,来自“我知道每一步发生了什么”,而不是“我相信某个黑盒能搞定”。
apt-get报错时,我不慌,因为我清楚history.log在哪、/var/cache/apt/archives/里有什么;当 Git 仓库乱了,我不怕,因为我知道git reflog能找回任何丢失的 commit;当快照恢复后服务起不来,我不焦虑,因为我知道journalctl -b能看到启动全过程。这种掌控感,是任何 SaaS 工具都无法替代的。

最后分享一个小技巧:把你的 VPS 的“变更防护流程”,写成一个 Markdown 文档,放在/root/ops-checklist.md里。内容就三行:

1. [ ] Backup: Create snapshot named `pre-<reason>-$(date +%Y%m%d-%H%M)` 2. [ ] Git: `cd /etc && git add . && git commit -m "pre: <reason>"` 3. [ ] apt-get: Use `apt-safe-upgrade` or `apt-get install <pkg>=<old-version>` for rollback

每次操作前,打开这个文件,打勾。三个月后,你会发现,打勾的动作,已经变成了你手指的条件反射。而那一刻,你就真正拥有了在 Linux VPS 上“自由操作”的底气。