公众号旧文

终于把 GitHub 备份和加密恢复搞明白了

作者:胖子阅读 —

今天又在 JP-IIJ 服务器上折腾了好几个小时。

本来只是想给服务器做一次维护前备份,没想到从 GitHub 仓库、Git 历史、文件加密,一直研究到 Windows 本地解密和数据库完整性验证。

中间让 Codex 执行了多轮审计、备份、检查和验收。

好在最后都顺利完成,服务器正常运行,备份也有了明确的恢复办法。

这次最大的收获不是又写了多少脚本,而是终于弄清楚了:

GitHub 配置备份和加密灾难恢复备份,到底应该怎么配合使用。

一、最初只是想给服务器做个备份

JP-IIJ 是我一直在使用的一台服务器,上面运行着Tailmatrix、Nginx,以及一些自建网站和运维程序。

服务器配置经过多次调整,已经积累了不少重要数据。

如果哪天系统损坏、升级失败或者误删文件,重新配置这些服务会非常麻烦。

所以今天先让 Codex 做了一次维护前备份。

这次备份覆盖的范围比较广,包括:

  • /etc、/root、/opt、/usr/local等目录中的配置和应用数据。
  • Tailmatrix 等程序及相关配置。
  • Nginx、systemd、SSH、Tailscale 等运行环境信息。
  • SQLite 数据库、证书、定时任务及软件包清单。

为了保护敏感数据,Codex 没有直接将这些文件上传到 GitHub,而是先打包,再使用 AES-256-GCM 和 RSA 接收证书加密。

最终形成约 186.9 MiB 的加密文件,拆分成五个分片,上传到了原来的 GitHub 仓库。

这份备份包含 13,320 个归档项目,五个 SQLite 数据库都经过一致性处理和完整性检查。

当时感觉备份做得挺全面,但后来发现了一个问题。

二、原来我的 GitHub 备份并不是这么做的

我打开了之前创建的 GitHub 仓库:

pangzibaike/JP-IIJ

发现里面本来就保存着 Nginx 配置、systemd 服务文件、网站代码和运维脚本。

而今天新增的备份,却变成了五个加密文件。

虽然加密更加适合保存敏感数据,但日常使用并不方便。

比如,我只是想看看上个月 Nginx 配置改了什么,原来的 GitHub 文件可以直接打开,还能查看 Git Diff。

现在如果全部打包加密,就需要先下载、合并、解密,才能看到里面的内容。

显然,这不是我最初使用 GitHub 备份配置的目的。

于是,我让 Codex 对整个仓库进行只读审计,重点调查 6 月份到底是怎么备份的。

结果发现,2026 年 6 月 14 日,我曾经分八次向 GitHub 提交文件。

当时采用的办法很简单:

  1. 从服务器上选取需要备份的代码和配置。
  2. 复制到 /root/github/JP-IIJ。
  3. 执行 git add、git commit。
  4. 使用 git push 上传到 GitHub。

整个过程没有统一的自动备份脚本,也没有专门的 GitHub 定时任务。

当时只保存了 44 个文件,总计约 54.3 KiB。

这才是我想继续使用的方式。

三、恢复原来的 GitHub 配置备份

搞清楚历史后,我决定让 Codex 恢复原来的备份思路。

普通代码和配置直接提交 GitHub,敏感数据继续单独加密保存。

这次没有回滚仓库,也没有删除刚生成的加密快照,而是在原有 Git 仓库上继续更新。

Codex 首先进行了第一阶段准备工作,对当前服务器文件和历史备份内容逐项比较。

检查发现,很多配置已经发生变化。

比如原来的 Nginx 只备份了部分站点,而现在服务器存在十个站点入口。

另外,Tailmatrix 的安全加固配置、Worker、cluster-agent 等内容,也需要补充到新的备份范围中。

经过整理,第一阶段一共准备了 158 个变更文件:

  • 新增 136 个文件。
  • 修改 22 个文件。
  • 没有删除原有文件。

同时对 Shell、Python、JSON 和 systemd 配置进行了静态检查,并扫描潜在的敏感信息。

审计过程中还发现,服务器上的一个 Python 监控脚本存在硬编码的 BOT_TOKEN。

这种文件就不适合直接上传到 GitHub,即使仓库已经设置成 Private。

因此,这次明确排除了真实 Token、SSH 私钥、证书私钥、数据库、.env 和其他敏感资料。

第一阶段完成之后,Codex 没有直接提交,而是等待确认。

随后才进入第二阶段,正式执行 Git 提交和推送。

最终提交成功。

GitHub 远端 main 与服务器本地提交哈希一致,Git 工作树干净。

更重要的是,整个备份过程中没有修改生产配置,也没有重启正在运行的服务。

至此,GitHub 配置备份恢复完成。

四、GitHub 私有仓库和文件加密,是两回事

今天还重新认识了 GitHub Private 仓库和加密备份之间的区别。

我的 GitHub 仓库是私有的,账号也启用了双因素认证。

对于日常代码、普通配置和恢复文档,我认为放在这样的私有仓库中就比较合适。

不过,私有仓库并不等于所有文件都经过了只有自己才能解开的端到端加密。

GitHub 的访问权限控制和文件自身的加密保护,解决的是两个不同的问题。

如果上传的是普通配置文件,拥有仓库读取权限的账号或程序就可以看到它的内容。

如果上传的是经过 CMS 加密的备份,即使拿到加密文件,没有对应的解密私钥,也不能直接读取原始内容。

因此,最后决定采用两种方式:

GitHub 负责日常代码和配置版本管理,加密备份负责敏感数据及重大故障恢复。

没有必要将所有东西都加密,也不应该把所有敏感文件都直接提交到 GitHub。

五、备份完成后,又发现自己不会解密

原以为上传成功就可以结束了,后来突然想到一个问题:

这些加密文件到底怎么解密?

如果真的遇到服务器故障,手里有五个加密分片,却不知道怎么恢复,那备份的实际价值就会大打折扣。

于是又让 Codex 专门做了一次 Windows 本地解密演练。

这次没有连接或修改服务器,而是在本地使用已有的备份文件进行检查。

经过验证,GitHub 上的完整快照采用:

  • OpenSSL CMS AuthEnvelopedData 格式。
  • AES-256-GCM 内容加密。
  • RSA 3072 位接收密钥。
  • 解密后的内容为 .tar.gz 归档。

完整快照先生成一个 CMS 加密文件,再拆分为五个分片。

所以解密时,必须先检查各分片的 SHA256,然后按照正确顺序进行二进制合并。

合并完成后,再用 OpenSSL 和本地私钥解密。

核心命令其实并不复杂:

& $OpenSSL cms -decrypt -binary -inform DER `

    -in $Combined `

    -inkey $PrivateKey `

    -out $Archive

其中,$Combined 是合并后的加密文件,$PrivateKey 是本地解密私钥,$Archive 则是输出的归档路径。

当然,真正操作时还需要先校验文件完整性,并且妥善处理解密过程中产生的明文临时文件。

六、解密成功,还检查了数据库

这次 Codex 不只是执行一条 OpenSSL 命令。

它还完整读取了归档,验证 gzip 数据流,检查归档成员,并对五个 SQLite 数据库进行了检查。

最终结果如下:

检查项目 结果
五个加密分片 SHA256 全部通过
合并文件 SHA256 通过
CMS 解密认证 通过
归档项目 13,320 项
gzip/归档完整性 通过
五个 SQLite 数据库 全部通过
数据库外键异常 0
原始备份及私钥 未修改
临时明文文件 已清理

这里需要区分一个概念。

能够解密并通过完整性检查,不代表已经完成整台服务器的灾难恢复。

因为这份备份不是可直接启动的磁盘镜像。

真正恢复时,还需要准备 Linux 系统、软件依赖、服务账户、权限、网络环境等,然后恢复应用数据和配置。

不过,至少现在已经确认,加密文件可以正确解开,里面的数据也能够读取。

七、最重要的文件,反而不是备份本身

今天还有一个比较重要的认识:

加密备份和解密私钥必须分别保存。

GitHub 上存放的是加密数据,而真正用来恢复的私钥保存在我的 Windows 本地。

这意味着,即使 GitHub 上所有文件都完好无损,如果私钥丢失,也可能无法恢复。

而 GitHub 的二次验证,并不能代替这把密钥。

所以后续最应该做的,不是继续增加备份脚本,而是妥善保管现有私钥,并保留独立副本。

八、最终决定:不再增加自动化

折腾到最后,我也重新考虑了是否有必要增加 GitHub 自动备份任务。

最终决定是:暂时不需要。

原因很简单。

JP-IIJ 的代码和配置并不是每天都发生大量变化。

以后只有在升级程序、修改关键配置、部署新服务或进行重大维护时,再让 Codex 检查当前文件与 GitHub 的差异,完成敏感信息检查后提交一次即可。

这样既能保留 Git 历史,也不用增加额外的定时任务和维护成本。

10 月 16 日创建的完整 CMS 快照也继续保留,作为一个已经验证可解密的恢复时间点。

十、今天最大的收获

回顾今天的整个过程,一共做了几件事:

先完成了一次覆盖范围较广的加密备份,再通过审计找回了六月份的 GitHub 配置备份方式。

随后将当前最新的普通代码和配置同步到 GitHub,并验证远端提交成功。

最后又专门研究了加密文件的结构、解密方式和数据库恢复检查,真正完成了一次 Windows 本地解密演练。

前前后后并没有修改生产服务,也没有因为研究备份而影响服务器运行。

以前更多关注的是服务器怎么部署、怎么优化、怎么保持在线。

今天则把注意力放在了另一个方面:

如果哪一天服务器真的坏了,我手里到底有没有足够的资料,把重要服务重新搭起来?

现在可以说,至少在代码、配置、敏感备份和解密方法这几个方面,比之前有把握得多。

当然,完整的异机恢复演练还没有做,不能说已经百分之百具备整机恢复能力。

但对于今天的目标来说,已经足够。

备份不只是把文件存到另一个地方,更重要的是知道保存了什么、如何验证,以及真正需要的时候怎么取回来。

这就是今天折腾 JP-IIJ 最大的收获。