用 Borg append-only 保留误删后的恢复机会

备份归档从列表里消失,并不一定意味着对应数据已经从仓库中抹去。Borg 的归档删除、段文件中的删除标记,以及最终回收磁盘空间,是不同层次的动作。理解这个区别,才能在误执行 prune、recreate,或备份客户端被入侵后,判断还剩多少恢复余地。

本文完整整理 BorgBackup 1.4.5 Additional Notes 中与这一任务直接相关的 Separate compaction、Append-only mode、Rolling back a transaction,以及其风险与补充说明。文档维护者为 BorgBackup 文档贡献者,页面版权标示 Jonas Borgström 与 The Borg Collective;核对日期为 2026 年 10 月 5 日。

Borg 恢复窗口示意图:备份段文件在逻辑删除后仍可能保留旧数据,append-only 禁止 compact;管理员确认并执行 compact 后,旧事务的恢复窗口可能关闭。
原创示意图:逻辑删除不会立即等同于物理回收;是否已经压缩,是恢复判断的关键条件。

从 Borg 1.2 开始,压缩回收是独立步骤

Borg 1.2.0 起,写仓库命令在事务提交时不再自动压缩段文件。这个变化要求客户端和服务器都至少为 1.2.0;旧客户端仍可能触发自动压缩。因此,很多时候普通仓库也表现得像“主要追加”:直到执行 borg compact,已标记删除的数据才有机会被真正清除。

这里的 compaction 指仓库段文件的整理与空间回收,不是备份数据的压缩算法。独立执行它带来几项后果:

  • 删除或 prune 归档之后,磁盘空间不会立即减少。
  • 常规写入命令可以更快完成,因为提交时不再顺带整理旧段。
  • 仓库在 compact 前可能保有历史 manifest、多个提交和较顺序化的数据,损坏后的恢复余地可能更大。
  • 管理员可以选择何时回收空间;应定期安排,但不必每个命令之后都执行。
  • compact 可以从客户端或服务器发起,回收操作本身不需要仓库加密密钥。
  • 用 rsync、rclone 等同步仓库副本时,通常可减少由频繁整理段文件引起的传输变化。

原文提供的回收入口是 borg compact。但一旦怀疑误删或入侵,先停止所有可能修改仓库的任务并保留现场,不要为了“整理一下”先运行它。历史数据是否仍存在,是后面的恢复步骤能否成立的前提。

append-only 保留的是什么

把仓库设为 append-only 后,Borg 不再覆盖或删除已经提交的段文件数据,也会拒绝完整删除仓库。但这只约束仓库底层结构:读取、borg delete 和 borg prune 仍然允许执行,归档仍可能在当前列表中消失。

一个容易误判的细节是:对 append-only 仓库执行 borg compact,可能既没有警告也没有报错,只是没有真正进行回收。因此退出码为 0 不能作为“磁盘空间已经回收”的证据。

在远程备份场景中,客户端通过受限的 borg serve 访问服务器。即使客户端被攻破,攻击者也只能沿着这个受限入口操作,无法借 Borg 的正常接口永久清除已提交段中的历史数据。这是一项有条件的保护,不是 WORM 存储,也不能抵御服务器自身失陷、宿主文件系统被修改或硬件损坏。

启用仓库级保护

borg config /path/to/repo append_only 1

/path/to/repo 必须替换成已核对的仓库路径。该设置可通过 borg config 再改回普通模式,并不是不可逆的锁定。在 append-only 模式下,仓库还会创建 transactions 文件,为每次事务记录编号和 UTC 时间,这对寻找可信回退点很重要。

给普通备份与管理操作使用不同入口

borg serve --append-only 可以让某一次服务会话按 append-only 规则工作,即使仓库自身没有设置该选项。原文用 SSH 的强制命令区分普通客户端密钥与管理密钥:

command="borg serve --append-only ..." ssh-rsa <key used for not-always-trustworthy backup clients>
command="borg serve ..." ssh-rsa <key used for backup management>

这是访问分工示意,不是可直接粘贴的完整 authorized_keys 配置;省略号与公钥占位符需替换,并按实际 SSH 与 Borg 部署进一步限制访问路径及转发能力。管理密钥应与普通备份客户端分离,不能同样保存在可能被攻破的客户端上。

还要区分“会话受限”和“仓库配置”:通过 borg serve --append-only 执行普通 borg init,不会自动创建一个仓库级 append-only 仓库;执行 borg init --append-only 才会在初始化时设置仓库级属性,不受服务器会话配置是否包含该选项的影响。

事务提交为何留下回退机会

Borg 仓库采用事务机制。一条修改仓库的命令要么完成并提交,要么失败或被中断而不提交修改。从 1.2.0 开始,即使一次成功提交把数据标记为删除,只要尚未回收相关段文件,旧数据和历史 manifest 仍可能留在底层。

因此,错误的 recreate、prune 或攻击者通过 append-only 入口实施的逻辑破坏,可能通过退回先前事务来撤销。这里的“可能”不能省略:不能在事后已经执行 compact、段文件被外部删除,或现存数据不足的情况下保证恢复。

借 transactions 找到最后一个可信事务

原文给出下面的事务日志,时间均为 UTC:

transaction 1, UTC time 2016-03-31 T15:53:27.383532
transaction 5, UTC time 2016-03-31 T15:53:52.588922
transaction 11, UTC time 2016-03-31 T15:54:23.887256
transaction 12, UTC time 2016-03-31 T15:55:54.022540
transaction 13, UTC time 2016-03-31 T15:55:55.472564

假设独立的安全日志表明攻击者在 15:54:00 获得访问权,随后删除或替换备份,那么事务 11 及之后的状态都应视为不可信。事务 5 是示例中的最后一个已知可信点。事务编号是该事务最后一个段文件的编号,所以事务 11 在这里跨越的是文件 6 到 11,不能只删除编号 11 的单个文件。

可信时间必须来自事件调查,而不能仅凭当前仓库“看起来正常”推断。还应考虑系统时钟偏差、攻击者更早的驻留,以及日志是否也遭到修改。

先保留独立副本,再研究如何回退

真实事故中,应保留被破坏仓库,用于分析攻击路径,也为恢复操作本身提供回退机会。原文认为,因为后面的步骤主要是删除文件,cp -al 创建的硬链接副本已经足够。本文修订这一操作建议:硬链接共享同一 inode,未来任何原地改写都可能同时影响所谓“副本”。应先停止所有写入,再保存独立、最好不可变或只读的副本;若使用文件系统快照,也需确认快照的隔离和保留机制。

以下描述是原文恢复逻辑的完整展开,涉及手工修改仓库结构,仅适合在事故恢复副本上由了解该版本格式的人操作。本次没有执行删除、回退、检查或 compact。

  1. 确定最后一个可信事务,在本例中为 5。
  2. 在恢复副本中,将与当前状态对应的 hints.N、index.N、integrity.N 移出仓库目录,并在隔离位置保留。原文要求移除这些可重建的索引文件,示例中的 N 为 13;这里改为移出并保留,属于安全改写,避免旧索引留在仓库内而与回退状态不一致。
  3. 逐一核对 data/ 中的段文件,隔离或移走事务 5 提交之后创建的所有段,即示例编号 6 到 13。
  4. 保留原仓库与隔离文件,记录每项改动,随后在恢复副本上验证结构、归档与业务内容。

原文以 rm data/**/{6..13} 展示第三步。这个表达式依赖 shell 的 globstar 与 brace expansion 行为,且直接删除不可撤销;工作目录错误、编号误判或 shell 差异都可能造成进一步损失。这里保留它用于辨认原文,而不把它列成可照抄的恢复命令。实际恢复应先形成精确文件清单并检查每个路径,再在副本上按已确认边界操作。

没有 append-only 日志,也可以辨认事务边界

即使仓库未启用 append-only,只要所需段文件仍在且没有相关回收,也可能手工回退。原文的步骤是先备份仓库目录,不执行 compact,再从编号最高的段文件向前查看:

  1. 进入仓库的 data/ 目录理解布局。段文件以数字命名,分组存放在数字子目录内。
  2. 寻找事务边界。一次修改通常由含 PUT 数据块或 DEL 删除标记的数据段、较小的 manifest,以及标志提交结束的 commit 组成。
  3. 识别当前事务的提交和 manifest,再继续向前找到上一个事务的提交。
  4. 确认想保留的提交后,隔离该提交之后产生的全部段文件。

原文描述的数据段可达约 500 MB,manifest 通常只有几 KB,独立 commit 文件常见为 17 字节。这些是辅助辨认线索,而不是“所有 17 字节文件都可作回退点”的判定规则。仓库版本、段组合方式及实际标签结构仍需核对,不能只按文件大小决定删除范围。

回退之后,客户端缓存也需要处理

曾访问过较新状态的客户端,其缓存与安全记录仍记着回退前的仓库。原文要求清理这个仓库的客户端缓存,让 Borg 从恢复后的状态重建:

borg delete --cache-only repo

repo 是仓库占位符,不是归档名;执行前必须确认仍带有 --cache-only。缓存重建可能耗时较长,取决于仓库大小与归档数量。原文随后还要求移除对应仓库的 ~/.config/borg/security/REPOID/manifest-timestamp。

删除这个安全时间戳会绕过“仓库退回旧状态”的一项检测,不能写入日常维护脚本,也不能为了消除报错而盲目处理整个 security 目录。应核对 REPOID,并在已确认的恢复流程中只处理相应记录。对于加密仓库,历史状态回滚还涉及加密计数器与 nonce 安全;不能把“能够读出旧归档”直接推成“可以原地继续写入”。应先恢复和核验所需数据,再根据对应版本的安全恢复流程迁入新的独立仓库或采取经确认的处理。

保留恢复窗口也有代价

append-only 只追加、不移除历史段,所以 prune 和 delete 不会立即释放空间。容量监控与有计划的维护仍不可少。如果空间被耗尽,后续备份也会受影响。

原文 Drawbacks 中保留了一段旧表述,称“只要以非 append-only 模式写入,包括 create、prune 或 delete,就会永久移除已删除对象”。它与同页的 Borg 1.2+ 独立 compact 说明不一致。本文按已核对的 1.4.5 文档语境解释:是否关闭恢复窗口,要看实际客户端与服务器版本,以及是否发生真正的 compact 或旧版自动回收,不能笼统认为每次普通写入都会立刻清除历史段。

这个修正并不降低管理入口的风险。一个自动化任务如果以管理权限定期执行 compact,仍可能清掉攻击者刚刚标记删除的数据,使 append-only 的恢复机会消失。临时切换到普通模式维护时,原文明确要求禁止远程访问,避免维护窗口被不可信客户端利用。

先做完整性检查,再决定是否回收

归档仍出现在列表里,也可能已有部分数据块被破坏。原文提出在解除限制并进行会关闭恢复窗口的操作前检查仓库与数据:

# 原文维护流程:在非 append-only 模式下
borg check --verify-data repo && borg compact repo

&& 只保证检查成功后才运行 compact,并不保证备份内容真实、没有被业务层面的恶意替换。本篇不建议在疑似事故后直接复制这条连写命令:先独立保留证据,完成数据校验和人工抽查,再由管理员明确决定是否回收。check --verify-data 检查的是仓库和数据完整性,不能证明一份被合法写入但内容已被篡改的文件仍是正确业务版本。

Borg 之外的访问同样要受控

append-only 不会约束 rm、普通文件写入或其他工具。备份客户端应仅通过受限的 borg serve 访问仓库;若同一密钥还能打开 shell、直接挂载文件系统或取得管理员权限,保护边界就被绕开了。服务器被攻破也属于这一机制无法单独处理的风险。

原文将文件系统快照、包装 borg serve 以调整新文件权限或 ACL 等措施列为可进一步考虑的保护,但这些超出 Borg 自身保证。它们需要独立设计、演练与容量管理。append-only 的价值,是让逻辑破坏与真正清除之间多保留一道可以调查和恢复的间隔,而不是承诺任何错误都能撤销。

本文未运行任何仓库命令,未修改备份、缓存或安全时间戳。原文:BorgBackup 1.4.5 官方文档;版权归 Jonas Borgström、The Borg Collective 及相应贡献者,项目使用 BSD 许可,具体条款以项目 LICENSE 为准。

版权与许可全文

以下保留本页涉及的来源材料或示例代码的版权、许可条件与免责声明;各自适用范围依原声明。中文翻译及编辑标注:未完纪,2026-10-05。

source-license.txt

Copyright (C) 2015-2026 The Borg Collective (see AUTHORS file)

Copyright (C) 2010-2014 Jonas Borgström <jonas@borgstrom.se>

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions

are met:

1. Redistributions of source code must retain the above copyright

notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright

notice, this list of conditions and the following disclaimer in

the documentation and/or other materials provided with the

distribution.

3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior

written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT

LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR

A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT

OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,

SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,

DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY

THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT

(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE

OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容