Describe ssh:// repository URLs for current repositories (served by a
remote "borg serve --rest") and for legacy borg 1.x repositories (with
--from-borg1, served by a remote "borg serve") and add a change log
entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
New deployment chapter deriving the borg passphrase from a YubiKey OTP
slot's HMAC-SHA1 challenge-response via BORG_PASSCOMMAND: only a random
challenge is stored, the passphrase itself is computed by the token on
every unlock (the long-proven KeePassXC-style mechanism, as also
documented for restic in the Arch wiki).
Covers programming a slot with and without touch (touchless =
unattended backups, --touch = presence-gated interactive use),
redundant twin YubiKeys via a shared slot secret, adding the derived
passphrase as an additional borg key to an existing repository, the
paper copy of the derived passphrase as the recovery path, and honest
caveats: Yubico-proprietary (not on other vendors' keys nor the
FIDO2-only Security Key series - the age guide covers those), two
scarce slots, device-bound vs presence-gated threat model, macOS Input
Monitoring permission, and ykman instead of the deprecated ykpers
tooling.
Co-Authored-By: Claude Fable 5 <[email protected]>
New deployment chapter showing how to combine BORG_PASSCOMMAND with
age: a strong random borg passphrase stored age-encrypted, decrypted on
demand - and, via the age plugin ecosystem, bound to hardware: any
FIDO2 security key (age-plugin-fido2-hmac, touch-gated by CTAP spec,
interactive use), YubiKey PIV (age-plugin-yubikey, policies allow
silent unattended use), TPM 2.0 (age-plugin-tpm) and Apple Secure
Enclave (age-plugin-se) for machine-bound unattended backups.
The guide starts with a plain no-hardware example to introduce the
pattern, gives an at-a-glance table of what works unattended vs. what
requires user presence, covers multi-recipient redundancy (several
YubiKeys / TPM unlocking the same passphrase), the offline paper copy
as the recovery path, revocation (change-passphrase, not just
re-encrypting) and an honest threat-model note (at-rest protection;
presence-gated options prevent silent unlocking, device-bound ones do
not).
This is the supported way to use hardware-backed key protection with
borg, instead of borg-native hardware integrations. See the discussion
in borgbackup/borg#8995 / borgbackup/borg#10399.
Co-Authored-By: Claude Fable 5 <[email protected]>
The example script used $DATE-$$-system / $DATE-$$-home archive names,
so every run created a new series and the per-series files cache never
matched, re-chunking everything. Use fixed names (system, home) so all
runs form one series per data set; runs are distinguished by archive ID
and timestamp (see borg repo-list).
Co-Authored-By: Claude Fable 5 <[email protected]>
Document that the forced command does not name the repository: the
client selects it via --backend FILE:<path> carried in
SSH_ORIGINAL_COMMAND, which borg serve validates against the
--restrict-to-* options. Also mention --restrict-to-path as the
multi-repo alternative and show the client-side URL form.
Co-Authored-By: Claude Fable 5 <[email protected]>
- pull-backup: switch the socat recipe to borg serve --rest with
rest:// URLs (mechanism verified end-to-end; note added that socat
pins the server-side --backend repo), fix the ssh-agent section
(borg repo-create --encryption aes256-ocb --key-location repokey
instead of borg init --encryption repokey, forced command with
--rest and --restrict-to-repository), and fix the remote-forwarding
example (rest:// URL, -r option, mandatory archive name).
- automated-local: borg init -> borg repo-create -r; convert the
borg1 repo::archive syntax in the create calls.
- hosting-repositories: the authorized_keys forced command needs
--rest (clients cannot supply it - it is deliberately not in the
client-arg allowlist), plus repo-create wording.
- image-backup: borg list -> borg repo-list -r for the archive
listing; add a note about archive series (names without timestamps
match multiple archives on extract; use aid:<id>).
All replacement commands were run against scratch repositories; the
forced-command semantics were verified via Archiver.get_args().
Co-Authored-By: Claude Fable 5 <[email protected]>
Like --remote-path, --rsh was only honoured on the legacy (borg 1.x, ssh://)
code path. For borg2 repositories the remote shell command comes from
borgstore, which reads its own BORGSTORE_RSH environment variable, so --rsh
silently had no effect there.
Remove the option; BORG_RSH is now the only way to set the remote shell
command. For borgstore-based repositories, borg gives the BORG_RSH value to
borgstore as BORGSTORE_RSH, except if that was set explicitly (then the user
wants a different command for borgstore).
borg benchmark crud does not need to inherit --rsh / --remote-path into its
sub-invocations any more (environment variables are inherited anyway), so the
parse_args wrapper is gone.
The modern client/server transport (RemoteRepository served by `borg serve`
over an msgpack RPC protocol) is now redundant for current (borg 2) repos:
its functionality is replaced by rest:// (which can tunnel over ssh to a
remote borgstore REST server).
Remove the modern RemoteRepository (both ssh:// and socket://) entirely.
Legacy v1 (borg 1.x) repos remain reachable over ssh:// via the separate
LegacyRemoteRepository client, and `borg serve` / RepositoryServer is kept,
trimmed to the legacy-only path, so a remote borg2 can still serve a v1 repo
for `borg transfer --from-borg1`.
Details:
- remote.py: delete RemoteRepository, SleepingBandwidthLimiter and the `api`
decorator; trim RepositoryServer to legacy-only (drop modern _rpc_methods,
socket serving, non-legacy open() branch); keep cache_if_remote /
RepositoryCache / RepositoryNoCache (used by all repos).
- get_repository(): non-legacy ssh:// now raises a clear "use rest://" error;
socket:// route and the global --socket option removed.
- parseformat: drop the socket:// scheme (now an invalid location).
- borg serve: keep the command (serves legacy v1 ssh only); update epilog.
- borg version: drop modern remote query; keep legacy ssh path.
- update isinstance/import sites (cache, archive, fuse/hlfuse, analyze/compact,
archiver __init__ -> LegacyRemoteRepository.RPCError).
- tests/docs updated; obsolete socket serve test removed.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Some features like append-only repositories rely on a server-side component
that enforces them (because that shall only be controllable server-side,
not client-side).
So, that can only work, if such a server-side component exists, which is the
case for borg 1.x ssh: repositories (but not for borg 1.x non-ssh: repositories).
For borg2, we currently have:
- fs repos
- sftp: repos
- rclone: repos (enabling many different cloud providers)
- s3/b3: repos
- ssh: repos using client/server rpc code similar as in borg 1.x
So, only for the last method we have a borg server-side process that could enforce some features, but not for any of the other repo types.
For append-only the current idea is that this should not be done within borg,
but solved by a missing repo object delete permission enforced by the storage.
borg create could then use credentials that miss permission to delete,
while borg compact would use credentials that include permission to delete.
Some features like repository quotas rely on a server-side component
that enforces them (because that shall only be controllable server-side,
not client-side).
So, that can only work, if such a server-side component exists, which is the
case for borg 1.x ssh: repositories (but not for borg 1.x non-ssh: repositories).
For borg2, we currently have:
- fs repos
- sftp: repos
- rclone: repos (enabling many different cloud providers)
- s3/b3: repos
- ssh: repos using client/server rpc code similar as in borg 1.x
So, only for the last method we have a borg server-side process that could enforce some features, but not for any of the other repo types.
For quotas the current idea is that this should not be done within borg,
but enforced by a storage specific quota implementation (like fs quota,
or quota of the cloud storage provider). borg could offer information
about overall repo space used, but would not enforce quotas within borg.
fixes#8641
In the example, setting SYSTEMD_WANTS instead of appending may prevent
other autostart services attached by earlier udev rules from launching.
This commit changes = to += to fix this behavior.
fixes#8639
The priority of 40 for the udev rules as stated in to documentation
applies the rule too early on some systems, which prevents the rule from
matching. This commit changes the priority to 80.
in borg 1.x, we used to put a timestamp into the archive name to make
it unique, because borg1 required that.
borg2 does not require unique archive names, but it encourages you
to even use an identical archive name within the same SERIES of archives.
that makes matching (e.g. for prune, but also at other places) much
simpler and borg KNOWS which archives belong to the same series.
borg1 needed this due to its transactional / rollback behaviour:
if there was uncommitted stuff in the repo, next repo opening automatically
rolled back to last commit. thus we needed checkpoint archives to reference
chunks and commit the repo.
borg2 does not do that anymore, unused chunks are only removed when the
user invokes borg compact.
thus, if a borg create gets interrupted, the user can just run borg create
again and it will find some chunks are already in the repo, making progress
even if borg create gets frequently interrupted.
One cannot "to not x", but one can "not to x".
Avoiding split infinitives gives the added bonus that machine
translation yields better results.
setup (n/adj) vs set(v) up. We don't "I setup it" but "I set it up".
Likewise for login(n/adj) and log(v) in, backup(n/adj) and back(v) up.