原文:https://borgbackup.readthedocs.io/en/stable/deployment/hosting-repositories.html。作者/维护方:BorgBackup 文档贡献者。中文翻译整理与校注:未完纪;源文核对日期:2026-10-05。
把 Borg 仓库交给一台专用服务器,可以让备份客户端只接触自己的备份空间。真正需要设计的,是客户端用哪个系统账户登录、它的 SSH 密钥能启动什么程序,以及该程序可以访问哪一个仓库。本文合并翻译 Borg 1.4.5 文档的 Hosting repositories 与 Central repository server with Ansible or Salt,先讲面向不同用户的独立账户方案,再讲同一组织内共用账户的集中部署。

托管仓库:让每个用户拥有独立登录账户
仓库通过 SSH 访问。托管服务的每个用户应当有自己的登录账户,而且只能访问属于自己的文件。技术上可以让多个用户共用一个账户,但独立账户能增加隔离程度,为服务提供者和用户再加一道安全边界。例如,即使某个用户突破了 borg serve 的限制,只要系统不存在其他可利用的漏洞,损害也应限制在这个用户的权限范围内。这个条件非常重要,独立账户并不消除系统漏洞。
目录布局沿用操作系统的惯例:为每个用户分配 home 目录,把其仓库放在该目录中。关键配置位于 ~user/.ssh/authorized_keys。公钥由用户提供;服务端为这个公钥附加强制命令和 SSH 限制。
原文为了排版把下面的条目分成两行,实际文件中必须合并成一行:
command="borg serve --restrict-to-repository /home/<user>/repository",restrict
<key type> <key> <key host>
例如,使用具体账户后,条目结构如下。<key type>、<key> 和 <key host> 是待替换的公钥字段,不是可直接使用的凭据。
command="borg serve --restrict-to-repository /home/alice/repository",restrict <key type> <key> <key host>
restrict 启用 OpenSSH 的整组限制:禁止端口转发、代理转发、X11 转发、PTY 分配和执行 ~/.ssh/rc;今后 OpenSSH 给这一组选项新增的限制也会包含在内。command 则强制登录后执行指定命令,这里必须是 borg serve。--restrict-to-repository 允许访问一个精确仓库,可以重复给出以放行多个仓库。仓库不一定已经存在,客户端可以初始化它并选择加密。
如果通过网页控制台等方式自动更新公钥文件,不能把用户输入直接拼进去。原文要求去除公钥首尾空白,只接受没有控制字符的 ASCII 文本,按三个字段、单个空格分隔的格式验证,并确认里面没有换行。换行注入会改变 authorized_keys 的条目边界,可能绕过预设限制;主机名、仓库路径等被写入强制命令的变量同样需要验证。
按仓库设置存储配额
给 borg serve 添加 --storage-quota 可以限制仓库使用的空间。原例中的省略号只表示还有其他参数与公钥字段,不能照抄:
restrict,command="borg serve --storage-quota 20G ..." ...
不同仓库的配额互相独立。如果一个客户端能访问多个仓库,它可以把每个仓库分别写到规定上限。因此,这不是按用户累加的总配额。启用前要确认实际部署的所有 Borg 版本都支持存储配额,细节见官方 borg serve 说明。
追加模式由服务端密钥条目强制实施
通过 borg serve --append-only 执行 borg init,不会自动把仓库自身的配置永久设成追加模式。不过,authorized_keys 中的 --append-only 参数会覆盖仓库配置,因此可以针对不同密钥实施不同策略。若要约束不可信备份客户端,应当由服务端强制这个参数,而不是依赖客户端自觉选择。
管理仓库仍可能需要允许删除或整理的受控管理路径,但不要把这种管理密钥同时交给备份客户端。追加模式也不是容量保护的替代品,仍需配额、空间监控和恢复演练。
集中仓库服务器:一个账户,按客户端分目录
第二篇原文给出一个简单的集中部署范例:服务器为 backup01.srv.local,客户端分别为 John Doe 的桌面机 johndoe.clnt.local、Web 服务器 web01.srv.local 和应用服务器 app01.srv.local。
服务器只建立一个 UNIX 用户 backup,主组也叫 backup,home 目录为 /home/backup。登录 shell 使用 /bin/bash,或其他能运行 borg serve 的 shell。原文建议大多数客户端以 root 发起备份,以保留诸如 /home 中各用户、组和文件权限;这会扩大客户端进程可读取的范围,实践中应按实际备份需求确定权限。
目录结构如下:
/home/backup
└── repos
├── johndoe.clnt.local
├── web01.srv.local
└── app01.srv.local
为每个客户端的密钥设置 borg serve --restrict-to-path /home/backup/repos/<client fqdn>。与精确仓库限制不同,--restrict-to-path 允许该目录范围内的仓库以及子目录。重复这个参数可放行多个独立目录,例如同一个人需要访问自己几台机器的备份时。
范例为每个客户端分配一个 SSH 密钥,但所有客户端都登录 backup@backup01.srv.local。每个条目具有各自的强制命令:
command="cd /home/backup/repos/<client fqdn>;
borg serve --restrict-to-path /home/backup/repos/<client fqdn>",
restrict <keytype> <key> <host>
以上条目实际也必须写在同一行。命令先切换工作目录,再启动限制到该目录的 borg serve,并禁用不需要的 SSH 功能。这样客户端不必知道绝对路径,可以使用 ssh://<user>@<host>/./<repo>,其中 /./ 表示相对服务器当前工作目录。原文明确指出,这个配置忽略通常追加到 borg serve 后面的客户端命令行参数。
校注:这些客户端共享同一个系统用户,不能把基于强制命令的路径限制说成操作系统沙箱。独立账户仍是多用户托管中更强的隔离层。原例的 cd …; borg serve … 即使切换目录失败也会继续执行;经过环境适配时,可改成 cd … && borg serve …,使切换失败时停止启动。该修正不替代绝对路径限制,也没有在本次工作中执行验证。
初始化仓库与验证越界访问
原文先用相对地址初始化名为 pictures 的仓库,再给出绝对路径写法以说明地址关系:
borg init ssh://backup@backup01.srv.local/./pictures
borg init ssh://backup@backup01.srv.local/home/backup/repos/johndoe.clnt.local/pictures
版本校注:这两条原始 borg init 命令没有指定加密模式,不能视为完整的 Borg 1.4 初始化指令。采用 Borg 1.4 时应显式选择,例如 borg init --encryption=repokey-blake2 ssh://backup@backup01.srv.local/./pictures,并在初始化前明确密钥、口令及恢复副本的保管办法。这里给出的补充命令仅经过静态检查,不代表已创建仓库或备份成功。
如果桌面客户端尝试访问 Web 服务器目录,原文展示的失败示例如下:
borg init ssh://backup@backup01.srv.local/home/backup/repos/web01.srv.local/pictures
~~~ SNIP ~~~
Remote: borg.remote.PathNotAllowed: /home/backup/repos/web01.srv.local/pictures
~~~ SNIP ~~~
Repository path not allowed
这段输出属于上游文档的示例,表示路径限制拒绝了请求,不是本文运行得到的测试记录。
用 Ansible 描述集中部署
Ansible 示例负责安装软件、建立用户和组、创建仓库目录、写入带限制的公钥条目,并把 home、.ssh、仓库目录权限设为 0700,把 authorized_keys 设为 0600。下面保留原配置,以便看清全部步骤:
- hosts: backup01.srv.local
vars:
user: backup
group: backup
home: /home/backup
pool: "{{ home }}/repos"
auth_users:
- host: johndoe.clnt.local
key: "{{ lookup('file', '/path/to/keys/johndoe.clnt.local.pub') }}"
- host: web01.clnt.local
key: "{{ lookup('file', '/path/to/keys/web01.clnt.local.pub') }}"
- host: app01.clnt.local
key: "{{ lookup('file', '/path/to/keys/app01.clnt.local.pub') }}"
tasks:
- package: name=borg state=present
- group: name="{{ group }}" state=present
- user: name="{{ user }}" shell=/bin/bash home="{{ home }}" createhome=yes group="{{ group }}" groups= state=present
- file: path="{{ home }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
- file: path="{{ home }}/.ssh" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
- file: path="{{ pool }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
- authorized_key: user="{{ user }}"
key="{{ item.key }}"
key_options='command="cd {{ pool }}/{{ item.host }};borg serve --restrict-to-path {{ pool }}/{{ item.host }}",restrict'
with_items: "{{ auth_users }}"
- file: path="{{ home }}/.ssh/authorized_keys" owner="{{ user }}" group="{{ group }}" mode=0600 state=file
- file: path="{{ pool }}/{{ item.host }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
with_items: "{{ auth_users }}"
这是一份需要适配的历史示例。软件包名 borg 是否正确取决于发行版,不能直接假定;现代 Ansible 环境还应确认 authorized_key 所属集合、模块命名和参数支持。原配置中的 web01.clnt.local、app01.clnt.local 与前文机器清单中的 .srv.local 不一致,应统一为真实 FQDN,并让密钥文件名、目录和库存记录保持一致。不要为了匹配范例而猜测实际主机身份。
配置创建客户端目录的任务在写密钥任务之后,整个部署结束前不应开放客户端连接。批量模板化前还应验证 item.host 只能包含期望的主机名字符,禁止空白、换行和 shell 控制字符。本文保留原文以展示任务结构,并未宣称这份 playbook 可以不加修改地应用到生产环境。
用 Salt 描述同样的部署
Salt 版本针对 Debian,先安装 Python、编译工具、SSL、ACL、FUSE 相关软件包,再通过 pip 安装 Borg;随后创建 backup 用户,遍历 backupclients,从 Salt 文件服务器取公钥并设置强制命令。
Install borg backup from pip:
pkg.installed:
- pkgs:
- python3
- python3-dev
- python3-pip
- python-virtualenv
- libssl-dev
- openssl
- libacl1-dev
- libacl1
- build-essential
- libfuse-dev
- fuse
- pkg-config
pip.installed:
- pkgs: ["borgbackup"]
- bin_env: /usr/bin/pip3
Setup backup user:
user.present:
- name: backup
- fullname: Backup User
- home: /home/backup
- shell: /bin/bash
# CAUTION!
# If you change the ssh command= option below, it won't necessarily get pushed to the backup
# server correctly unless you delete the ~/.ssh/authorized_keys file and re-create it!
{% for host in backupclients %}
Give backup access to {{host}}:
ssh_auth.present:
- user: backup
- source: salt://conf/ssh-pubkeys/{{host}}-backup.id_ecdsa.pub
- options:
- command="cd /home/backup/repos/{{host}}; borg serve --restrict-to-path /home/backup/repos/{{host}}"
- restrict
{% endfor %}
校注:包名、系统 pip 的使用方式和 FUSE 依赖都需要按当前发行版复核;该示例没有完整建立 /home/backup/repos/<host> 及其权限,实际状态文件必须补齐,并建立合适的依赖顺序。模板变量也必须进行输入验证。
原代码注释提醒,修改 command= 不一定会正确更新服务器,甚至建议删除并重建整个 authorized_keys。本文不把删除整份公钥文件作为操作建议:它可能中断其他客户端或管理访问。应备份现有配置,采用能更新对应密钥选项的管理方式,在保留管理通道的条件下逐条核查远端实际文件,并确认限制已经生效。
扩展方向与权限边界
原文还讨论了一种改进:如果需要支持有限的客户端参数,可编写启动 borg serve 的包装程序;另一种设想是由 Borg 检查 SSH_ORIGINAL_COMMAND 以识别 SSH 场景,从而不需要额外解释器或包装脚本。这部分是原文留下的扩展方向,不代表应当直接执行该环境变量中的任意字符串。任何支持的参数都应列入明确的允许清单。
至此,两种方案的取舍很清楚:不同用户的托管服务优先用独立账户与精确仓库限制;集中备份可以用一个账户简化运维,但要接受隔离较弱的事实。配额按仓库计算,SSH 限制按密钥设置,而加密、追加模式、管理权限和恢复流程都需要分别确定。
来源、许可与核验说明
合并原文:Hosting repositories;Central repository server with Ansible or Salt。版本为本次读取的 Borg 1.4.5 stable 文档。进一步参考 sshd(8)、Ansible 与 Salt。
版权:Copyright © 2010–2014 Jonas Borgström;2015–2026 The Borg Collective。官方 Authors / License 保留了 BSD 三条款许可、署名条件和免责条款;本文下方的 BORG-LICENSE.txt 保留该许可全文。本稿没有执行 SSH、安装、初始化、删除或修改服务器配置,所有运行输出均明确来自原文。
版权与许可全文
以下保留本页涉及的来源材料或示例代码的版权、许可条件与免责声明;各自适用范围依原声明。中文翻译及编辑标注:未完纪,2026-10-05。
BORG-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.












暂无评论内容