比较 Borg 拉取备份的反向隧道与文件系统方案

来源与署名:BorgBackup 文档贡献者 / The Borg Collective;SSHFS how-to 致谢 secuser;未完纪翻译整理。原文:Backing up in pull mode — Borg 1.4.5。本文为中文翻译整理;技术核对日期为 2026 年 10 月 5 日。

socat 拉取模式由仓库服务器发起 SSH 到客户端,客户端 Borg 经反向 Unix socket 将协议流传回服务器的 borg serve
socat 拉取模式由仓库服务器发起 SSH 到客户端,客户端 Borg 经反向 Unix socket 将协议流传回服务器的 borg serve(原创示意图,不是实测截图。)

Borg 的常见工作方式是推送:待备份机器上的 Borg 客户端主动通过 SSH 连接保存仓库的备份服务器。如果需要由备份服务器发起连接或定时触发任务,例如将远程服务器备份到自己的电脑,就需要原文介绍的几种拉取模式变通方案。

本文依据 Borg 1.4.5 的 Backing up in pull mode 完整整理 SSHFS、socat 与 ssh-agent 三条路线,保留其备份和恢复差异。这里的“客户端”是数据所在机器,“服务器”是 Borg 仓库所在机器。拉取说的是由谁发起任务,不一定表示 Borg 进程在服务器上读取挂载文件。所有命令仅作静态说明,本次没有建立隧道、挂载目录或执行备份。

SSHFS:把客户端文件系统挂到服务器

SSHFS 路线把待备份机器的文件系统远程挂在备份服务器,再由服务器读取。即使网络连接必须由客户端主动建立,也可以通过反向隧道形成这种布局;NFS、SMB 也可承担远程文件系统角色。原文认为 SSHFS 容易搭建且安全性较好,但这不是适用于所有部署的安全排序:真正需要核对的是身份验证、挂载权限及元数据语义。

若没有设置 UID/GID 映射,挂载后的属主和组可能改变;Borg 若不以 root 运行,也可能读不到部分文件。SSHFS 基于 FUSE 和 SFTP,具体 sshfs、libfuse、sftp 实现可能不支持某些文件名编码、ACL、扩展属性 xattr 或 flags。因此,备份中有文件内容,不等于能够完整恢复原系统的全部元数据。

  • 要挂载客户端根文件系统,通常需要客户端 root 权限。这改变了 Borg 常见的信任模型:备份服务器获得高权限后,可能对客户端造成任意损害。必须充分信任备份服务器。
  • 若 chroot 进入客户端根文件系统,服务器会执行来自客户端的二进制。因此还必须信任客户端;chroot 不是安全沙箱。
  • 服务器与客户端须有兼容的 CPU 架构,例如都为 x86_64,因为 /bin/sh 与 Borg 等程序来自客户端环境;架构不同可以考虑后面的 socat 路线。
  • chroot 的名字映射假设用户/组来自 /etc/passwd 和 /etc/group。使用 LDAP 等外部身份源时,该假设可能不成立。此时可考虑 –numeric-ids,仅记录数字 ID,而不保存名字,也无需为此 chroot。

SSHFS 备份:原文步骤与破坏性边界

Borg 在备份服务器上默认读取服务器自己的 passwd/group 来解释客户端文件的 ID。原文用 chroot 访问客户端的映射文件,并把独立 Borg 可执行文件复制到挂载后的客户端目录。以下是原文步骤,供理解,不宜直接执行:cp 的目标是客户端真实目录,可能覆盖已有 Borg 程序;后续 rm 还会删除它。若客户端已有适用的 Borg,应省略复制与删除,不能把清理命令无条件当作安全步骤。

# 原文:挂载客户端根文件系统
mkdir /tmp/sshfs
sshfs root@host:/ /tmp/sshfs
# 将备份仓库绑定挂载到 chroot 内
mkdir /tmp/sshfs/borgrepo
mount --bind /path/to/repo /tmp/sshfs/borgrepo
# 风险:可能覆盖客户端已有程序;已有 Borg 时应省略
cp /usr/local/bin/borg /tmp/sshfs/usr/local/bin/borg
cd /tmp/sshfs
for i in dev proc sys; do mount --bind /$i $i; done
chroot /tmp/sshfs

进入 chroot 后,当前仍在备份服务器运行,但看到客户端根目录与 /borgrepo 仓库。原文用以下命令备份根目录:

borg create --exclude borgrepo --files-cache ctime,size /borgrepo::archive /

示例为了简洁仅排除 borgrepo;实际应列出不应归档的路径。SSHFS 无法保证 inode 稳定,因此使用 –files-cache ctime,size 改变文件变化判定策略,而不能沿用依赖稳定 inode 的默认假设。

原文的退出与清理过程如下。执行前必须知道哪些挂载由本次建立、是否仍有进程占用,以及程序文件是否是本次新建;这段不应无条件放入自动清理脚本。

exit # 离开 chroot
# 风险:仅当已确认此文件由本次新增且非原有文件时才考虑移除
rm /tmp/sshfs/usr/local/bin/borg
cd /tmp/sshfs
for i in dev proc sys borgrepo; do umount ./$i; done
rmdir borgrepo
cd ~
umount /tmp/sshfs
rmdir /tmp/sshfs

原文特别致谢 IRC 上的 secuser 提供此操作说明。编辑不将该路线改写成“安全默认”:元数据缺失、双方信任、执行客户端程序与覆盖文件的风险仍然存在。

恢复:部分、完整与 tar 流各有边界

拉取备份对应的恢复常是向客户端写回。部分恢复时,如果目标系统从备份后改变了用户与组的名字—ID 映射,例如重装了系统,照搬归档中的数字 ID 可能产生错误归属。原文使用与前述备份相同的 SSHFS 挂载、仓库 bind mount、Borg 复制(若确需)和 dev/proc/sys 绑定,再进入 chroot;只有核心命令改为:

borg extract /borgrepo::archive PATH

PATH 是要恢复的归档内路径,必须先核对目标与覆盖范围。完成后按上一节退出、卸载、清理;复制/删除 Borg 的风险同样成立。

完整恢复会同时还原 /etc/passwd、/etc/group 与其他文件,使用归档中的数字 UID/GID 可保持它们之间的一致性,因此不必为名字映射 chroot:

sshfs root@host:/ /mnt/sshfs
cd /mnt/sshfs
borg extract --numeric-ids /path/to/repo::archive
cd ~
umount /mnt/sshfs

这仍是写入目标文件系统的恢复操作,不应对正在使用的生产根文件系统随意执行。另一种更简单但有信息损失的方法是通过 tar 流恢复,不再使用 SSHFS:

borg export-tar /path/to/repo::archive - | ssh root@host 'tar -C / -x'

该命令会向远端根目录解包,可能覆盖系统文件,本文没有执行。tar 格式并不能还原 Borg 支持的全部高级特性,使用前应读 borg export-tar 的限制。备份/恢复链路是否完整,还取决于 SSHFS 或 tar 是否保留应用需要的元数据。

socat:保留客户端本地读取,通过反向 socket 传输

socat 路线仍让 Borg 在待备份机器上读取本地文件,而由备份服务器连接客户端,使用 SSH 反向转发连接两端 Unix socket。普通推送时,客户端通过 SSH 启动远端 borg serve,并经标准输入输出通信;这里把相同协议流接到 socket,从服务器侧发起连接。两端都需要 socat。

原文要求双方的 /run/borg 只有备份相关用户可访问。服务器上的 borgs 运行 borg serve 并写仓库;客户端上的 borgc 是 SSH 登录用户,需要创建转发 socket。

# 两端分别创建,实际所有者按机器角色设置
sudo mkdir -m 0700 /run/borg
# borg-server 上
sudo chown borgs /run/borg
# borg-client 上
sudo chown borgc /run/borg

这些是两台机器上分别执行的说明,不是同一台机器依次修改所有者。还要考虑 /run 通常在重启后重建,目录及服务生命周期须由部署配置管理。服务器端启动监听:

socat UNIX-LISTEN:/run/borg/reponame.sock,fork EXEC:"borg serve --append-only --restrict-to-path /path/to/repo"

有连接进入时,socat 启动 borg serve,把标准输入输出接到 Unix socket。–append-only 与 –restrict-to-path 不是协议必需项,但用于限制客户端影响范围;它们也不等于拥有该路径的主机管理员不能破坏仓库。原文还指出,可在服务器用 systemd 的 service 和 socket 单元替代 socat 监听,按需激活服务。

随后由服务器连向客户端,把客户端上的路径反向转发至服务器的同名路径:

ssh -R /run/borg/reponame.sock:/run/borg/reponame.sock borgc@borg-client

客户端进程连接这个 socket 后,SSH 会把字节流转回服务器。注意 OpenSSH 默认 StreamLocalBindUnlink=no,旧 socket 文件可能保留,第二次连接就会出现 remote port forwarding failed,随后 Borg 可能收到 Connection refused。每次清理前必须确认路径、所有者及是否仍有活动连接,不能删除未知 socket。编辑建议增加 -o ExitOnForwardFailure=yes,使转发建立失败时尽早退出;该修改尚未执行验证。

在客户端告诉 Borg 改用 socat,而不是默认 SSH:

export BORG_RSH="sh -c 'exec socat STDIO UNIX-CONNECT:/run/borg/reponame.sock'"
borg create ssh://borg-server/path/to/repo::archive /path_to_backup

Borg 会根据仓库 URL 给远程 shell 加上主机名等参数;socat 不认识这些 SSH 参数,因此这里通过 sh -c 包装固定命令来忽略它们。不要把不可信路径、主机名或用户输入拼接进该 shell 字符串,否则会产生命令注入风险。

原文给出非交互式合并写法,在远端创建备份后删除 socket:

ssh \
   -R /run/borg/reponame.sock:/run/borg/reponame.sock \
   borgc@borg-client \
   borg create \
   --rsh "sh -c 'exec socat STDIO UNIX-CONNECT:/run/borg/reponame.sock'" \
   ssh://borg-server/path/to/repo::archive /path_to_backup \
   ';' rm /run/borg/reponame.sock

静态审核发现:这里的分号允许清理在 Borg 失败后继续进行,而且最终退出状态可能是 rm 的结果,从而掩盖失败。自动化应分别记录备份和清理的退出状态,再返回备份失败或清理失败;配合 ExitOnForwardFailure、预先核验的主机密钥、独占运行和受限目录。本文不把尚未验证的完整脚本当成可用修正版。下面仅展示保存状态的逻辑,cleanup_socket_safely 是说明性步骤名,不是现成命令:

backup_status = run_borg_create()
cleanup_status = cleanup_socket_safely()
record(backup_status, cleanup_status)
return backup_status if backup_status != 0 else cleanup_status

ssh-agent:理解为何要极其谨慎

第三条路线由 borg-server 连接 borg-client 并转发认证 agent。客户端再用这个 agent 反向 SSH 回服务器,启动 borg serve,形式上类似推送。borgs 要有仓库读写权限,borgc 要有读取待备份文件的权限。原文甚至考虑客户端不可信的情况,并使用专用密钥与强制命令限制危害。

原文服务端准备步骤为:

install -m 700 -d ~/.ssh/
ssh-keygen -N '' -t rsa -f ~/.ssh/borg-client_key
{ echo -n 'command="borg serve --append-only --restrict-to-repo ~/repo",restrict '; cat ~/.ssh/borg-client_key.pub; } >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

它创建权限为 700 的 .ssh 目录、一个空私钥口令的专用 RSA 密钥,并把带强制命令与 restrict 的公钥追加到 authorized_keys。强制命令只能操作指定仓库且为 append-only;delete、prune、compact 等管理工作必须在服务器本地或通过另一条更高权限、严格控制的通道完成。原文也提出每次拉取使用独立临时密钥的更复杂方案。

原文一次拉取操作以子 shell 隔离 agent 环境,启动 agent、明确加载专用密钥,再经 SSH 传递 Borg 仓库口令,最后终止 agent:

(
  eval $(ssh-agent) > /dev/null
  ssh-add -q ~/.ssh/borg-client_key
  echo 'your secure borg key passphrase' | \
    ssh -A -o StrictHostKeyChecking=no borgc@borg-client "BORG_PASSPHRASE=\$(cat) borg --rsh 'ssh -o StrictHostKeyChecking=no' init --encryption repokey ssh://borgs@borg-server/~/repo"
  kill "${SSH_AGENT_PID}"
)

这是风险示例,不能整体照抄。StrictHostKeyChecking=no 弱化主机身份校验;示例口令只是占位值,真实口令不能硬编码进脚本或命令历史。客户端的 borgc 或 root 若能访问转发 socket,就能调用 agent 使用其中的身份;即使私钥文件没有复制过去,也已经让远端获得可利用的认证能力。

  • 必须预先核验两端主机密钥,保留严格主机身份检查。
  • 不向不可信主机转发通用 agent;密钥不得用于其他主机或其他用途,应明确指定要加载的专用文件。
  • authorized_keys 的强制命令、仓库限制和权限必须匹配实际路径,并维持最小权限。
  • 口令传入、日志脱敏、超时及 agent 清理都要另做可靠设计;子 shell 只隔离变量,不能消除远端使用 agent 的风险。

原文解释,括号用于避免干扰当前会话可能已有的 ssh-agent;专用 Bash 进程下可不加括号。ssh-add 用于加载密钥,ssh://borgs@borg-server/~/repo 指向服务器上 borgs 主目录中的仓库,kill 结束本次 agent。本文保留这些机制说明,但不建议复制该分支作为生产部署方案。

版本、代码与许可证

本文固定 Borg 1.4.5 文档,使用 repo::archive 的 1.x 命令语义;跨大版本迁移须另读对应手册。没有测量吞吐、验证恢复结果或确认任何 SSH 选项在读者环境可用。三条路线都必须先在独立测试环境验证文件内容、所有权、ACL、xattr、flags 和恢复后的应用一致性。

版权:© 2010–2014 Jonas Borgström;© 2015–2026 The Borg Collective,参见 AUTHORS 与许可证。SSHFS 操作说明致谢 secuser(IRC)。下方保留 Borg BSD 三条款许可与免责声明。

原始版权与许可

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 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容