Commit Graph
11456 Commits
Author SHA1 Message Date
Thomas Waldmann 82744708c0 update CHANGES 2.0.0b25 2026-09-27 23:02:37 +02:00
Thomas Waldmann 97f03619c7 build_usage build_man 2026-09-27 23:02:35 +02:00
Thomas WaldmannandClaude Opus 5.5 9e2e74f737 repo-create: avoid '*' in --id-hash help, fixes docutils warning
'authenticated-*' in the help text started an unterminated inline emphasis
in the generated rst usage docs and man page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 23:01:45 +02:00
TW 64e6f2fda3 Merge pull request #10419 from mr-raj12/check-repair-salvage-pack-10026
repository: add salvage_pack, keeping only the authenticated objects of a corrupt pack, refs #10026
2026-09-27 22:39:24 +02:00
Mrityunjay Raj 296c9a018e repository: salvage_pack docstring and test fixes, refs #10026 2026-09-28 01:00:46 +05:30
Mrityunjay Raj 82c31cc2cb repository: salvage_pack catches only read failures, refs #10026
SALVAGE_READ_ERROR now covers OSError, BackendConnectionError and
ReadRangeError. Other store backend errors, e.g. BackendMustBeOpen,
PermissionDenied or QuotaExceeded, propagate.
2026-09-28 00:18:06 +05:30
Mrityunjay Raj 5d4f5f60f1 repository: salvage_pack review fixes, refs #10026 2026-09-27 19:52:33 +02:00
Mrityunjay Raj d7c71def62 repository: salvage_pack calls before_old_pack_delete only if the old pack is deleted, refs #10026 2026-09-27 19:52:07 +02:00
Mrityunjay Raj 943aef01a4 repository: add salvage_pack, keeping only the authenticated objects of a corrupt pack, refs #10026 2026-09-27 19:52:00 +02:00
TW a68d9434ce Merge pull request #10441 from ThomasWaldmann/repo-defaults-mandatory
repo-create: always write config/defaults, so its removal is detected, refs #346
2026-09-27 17:43:46 +02:00
Thomas WaldmannandClaude Fable 5.1 26ff43fdcc repo-create: always write config/defaults, so its removal is detected, refs #346
Without the object, the commands silently fell back to the built-in defaults, so
anybody with write access to the store could remove a repository default (e.g. an
"obfuscate" compression) unnoticed: the key protects the content of the object,
not its presence.

Now "borg repo-create" always writes config/defaults (empty if no default was
given), and a missing object is an error (Repository.DefaultsMissing, rc 34) like
one that fails the authentication: the commands using the defaults refuse to run.
"borg check" reports such an object, "borg check --repair" replaces it by empty
defaults, so the repository can be used again (with the built-in defaults).

with_repository reads the defaults only after the security checks (assert_secure).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-27 17:09:59 +02:00
TW 57c0a6f349 Merge pull request #10440 from ThomasWaldmann/ci-cryptography-reinstall
CI: force-reinstall cryptography from source for the linux binaries
2026-09-27 17:07:00 +02:00
TW 6c1670437a Merge pull request #10442 from ThomasWaldmann/windows-repo-info-linesep
tests: use os.linesep in the repo-info default assertions
2026-09-27 17:06:35 +02:00
Thomas WaldmannandClaude Opus 5.5 e893f13ed1 tests: use os.linesep in the repo-info default assertions
On Windows, repo-info output lines end with CRLF, so the assertions
expecting a trailing "\n" failed there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 17:05:24 +02:00
Thomas WaldmannandClaude Opus 5.5 001b59cde5 CI: force-reinstall cryptography from source for the linux binaries
The moto S3 test server install already pulls in the cryptography
manylinux wheel, so a plain "pip install --no-binary cryptography"
kept that wheel (statically linked OpenSSL) and the dynamic libcrypto
check failed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 15:53:44 +02:00
TW bce686de68 Merge pull request #10439 from ThomasWaldmann/update-changes-master
update CHANGES
2026-09-27 15:27:01 +02:00
Thomas Waldmann 3e0acd9c9c update CHANGES 2026-09-27 15:19:58 +02:00
TW 88d0c42be9 Merge pull request #10438 from ThomasWaldmann/repo-default-compression
repo-create: store default compression and chunker params in the repository, fixes #346
2026-09-27 12:27:04 +02:00
Thomas WaldmannandClaude Opus 5.5 f694ba3e69 repo-create: store default compression and chunker params in the repository, fixes #346
"borg repo-create --compression SPEC --chunker-params PARAMS" stores repository
defaults in the new config/defaults store object.

Compression: the commands that compress data (create, recreate, import-tar,
transfer, repo-compress, debug put-obj) use the repository default if no
compression was given via the command line, the environment or default.yaml;
without a repository default, they use lz4 as before.

Chunker params: "--chunker-params default" now means the repository default,
else the built-in CHUNKER_PARAMS: ChunkerParams("default") returns the
DEFAULT_CHUNKER_PARAMS marker and with_repository replaces it by the real
chunker params. borg create and borg import-tar use the marker as their parser
default, so they use the repository default if no chunker params were given.
borg recreate and borg transfer still only rechunk if --chunker-params is
given; with "default", they rechunk to the repository default.

config/defaults is stored in the key's store object envelope, so it is
authenticated: nobody without the key can change the defaults (e.g. remove an
"obfuscate" compression) without being noticed. config/config can not hold
them, as it is read before the key is known and is not authenticated.
config/defaults is read at most once per Repository object.

borg repo-info shows the defaults (also in --json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 03:41:25 +02:00
TW 69f7bb23d8 Merge pull request #10437 from ThomasWaldmann/repo-list-missing-archive-10435
repo-list, compact: handle archives with a missing metadata object, fixes #10435
2026-09-27 01:40:48 +02:00
Thomas WaldmannandClaude Opus 5.5 9b3b7eb819 repo-list, compact: handle archives with a missing metadata object, fixes #10435
If an archive's metadata object is missing or invalid while its archives/ entry
still exists, Archives.list() yields a placeholder ArchiveInfo for it, but
opening it as an Archive raises Archive.DoesNotExist.

- ArchiveFormatter: use the placeholder values of the ArchiveInfo (and defaults)
  if the archive can not be opened, so repo-list (and prune --list) go on.
- compact: skip such a soft-deleted archive with the "cannot be fully preserved"
  warning, as documented in mark_soft_deleted_used(), instead of aborting.
- Archive.DoesNotExist: show a binary archive id as hex.
- check --format: keep using the fallback format for such archives.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 00:18:47 +02:00
TW e6803d2815 Merge pull request #10432 from ThomasWaldmann/tag-append-10430
tag options: one tag per option, fixes #10430
2026-09-27 00:08:59 +02:00
Thomas WaldmannandClaude Fable 5.1 cce5c99f4f tag: only rewrite the archive metadata if the tags changed
Previously, "borg tag" always rewrote the archive metadata (and thus the
archive entry in the repository), even when just listing the tags or when
the requested change was a no-op (including a refused --set).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-26 23:19:51 +02:00
Thomas WaldmannandClaude Opus 5.5 7ecf12df2b docs: explain the tags: archive match pattern
Document that tags: takes a comma-separated list of tags and matches
archives having all of them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:46 +02:00
Thomas WaldmannandClaude Opus 5.5 d76a58b729 tag: warn if --set is refused because it would remove special tags
Previously, borg silently did not set the tags, so it looked like the
given tags were just ignored, and the return code was 0.

Now borg emits a warning naming the special tags that would be removed
and exits with rc 1 (warning).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:43 +02:00
Thomas WaldmannandClaude Opus 5.5 2c058d1d89 tag: refuse to change the tags of all archives without a selection
Without NAME or archive filter options, "borg tag --set/--clear/--add/--remove"
silently changed the tags of all archives in the repository.

Now it aborts like "borg delete" does, unless archives are selected
explicitly, e.g. using -a 'sh:*' for all archives. Just showing the
tags of all archives still works without a selection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:41 +02:00
Thomas WaldmannandClaude Opus 5.5 83b6583dd7 create: replace --tags with --tag, taking one tag per option
--tags used nargs="+" and thus swallowed the archive NAME if it was given
after it, e.g. "borg create --tags foo NAME PATH" failed.

Now --tag takes exactly one tag and can be given multiple times,
e.g. "borg create --tag foo --tag bar NAME PATH".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:38 +02:00
Thomas WaldmannandClaude Opus 5.5 01d42bec43 tag: add --clear to remove all normal tags
Special tags like @PROT are kept. --clear can be combined with --add
to replace the normal tags, it is mutually exclusive with --set.

Previously, a bare "--set" cleared all tags, but that is not possible
anymore since --set now takes exactly one tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:35 +02:00
Thomas WaldmannandClaude Opus 5.5 2712d2f3bb tag: --set/--add/--remove take one tag per option, fixes #10430
They used nargs="*" and thus swallowed the archive NAME if it was given
after them, e.g. "borg tag --set keep aid:1ddaae55" failed.

Now each option takes exactly one tag and can be given multiple times,
e.g. "borg tag --add a --add b NAME".

Also add examples to the "borg tag" help.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 23:19:31 +02:00
TW 5aac325e48 Merge pull request #10436 from ThomasWaldmann/gather-archive-meta
load all archives' metadata with one Store.gather, reuse it for Archive
2026-09-26 23:12:23 +02:00
Thomas WaldmannandClaude Opus 5.5 925c55f3c8 reuse the listed archive metadata when opening an Archive
ArchiveInfo now carries the parsed ArchiveItem (as an attribute, not a
tuple field, so equality and hashing are unchanged). Archive accepts an
ArchiveInfo instead of a name or id; if it carries the metadata, the
archive is neither looked up again (1 list + 1 load) nor its metadata
loaded again (1 load). Without it, Archive looks the archive up by id as
before, e.g. for the archives of borg 1.x repositories.

ArchiveFormatter (repo-list, prune --list, ...), the per-archive loops
of info, list, tag, find, compact, recreate and analyze, and transfer
(for the source archives) pass the ArchiveInfo. repo-list with the
default format for 1000 archives with 50 ms emulated store latency takes
2.1 s instead of 238 s.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 22:24:47 +02:00
Thomas WaldmannandClaude Opus 5.5 1a9f864725 load all archives' metadata with Store.gather
Listing the archives (repo-list and every archive match) read each archive
metadata object with its own repository.get, i.e. one store roundtrip per
archive. Archive metadata objects are small and usually each in a tiny pack
of its own, which is the case Store.gather is made for.

Repository.gather_many reads objects in batches (at most 1000 objects, or
until 16 MiB) with one store.gather per batch, keeping the order of the ids.
Objects in cached packs are sliced from the cache; unknown or still buffered
ids and batches hitting a missing or truncated pack fall back to get().

Archives._infos uses it: repo-list --short for 1000 archives with 50 ms
emulated store latency takes 2.0 s instead of 56 s.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 20:20:34 +02:00
TW 8b5733db28 Merge pull request #10433 from ThomasWaldmann/borgstore-070
require borgstore 0.7.0
2026-09-26 12:46:35 +02:00
Thomas WaldmannandClaude Opus 5.5 dca232741b require borgstore 0.7.0
borgstore 0.7.0 adds Store.gather (many byte ranges with one call, one
roundtrip for REST) and fixes partial loads with a negative offset from
cached namespaces.

Store.defrag now requires all sources to be items of the given namespace,
which borg's defrag calls (hex pack names in "packs") already satisfy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-26 12:04:56 +02:00
TW 5f1d3cd15d Merge pull request #10431 from ThomasWaldmann/key-paper-hint-10428
key export --paper: fix restore hint for borg 2 syntax, fixes #10428
2026-09-25 22:04:11 +02:00
Thomas WaldmannandClaude Opus 5.5 10394b0efd key export --paper: fix restore hint for borg 2 syntax, fixes #10428
In borg 2, the positional argument of borg key import is the key backup
path, not the repository. Like most of the docs, assume BORG_REPO is set.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 21:34:36 +02:00
TW 5b2090b452 Merge pull request #10427 from ThomasWaldmann/update-changes-master
update CHANGES
2026-09-25 21:13:12 +02:00
Thomas Waldmann ce507ec480 update CHANGES 2026-09-25 21:05:38 +02:00
TW 73e2284a67 Merge pull request #10423 from ThomasWaldmann/encrypt-locks-8386
encrypt repository locks, fixes #8386
2026-09-25 20:31:53 +02:00
Thomas WaldmannandClaude Opus 5.5 dd902f8db5 docs: lock objects are protected by the repository key, #8386
Update the data structures and security docs: the lock objects are in the
store object envelope, locking needs the key (it is loaded before waiting for
the lock), unreadable lock objects are treated as exclusive locks, and what
the store still sees about the locks. Add a change log entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 19:16:40 +02:00
Thomas WaldmannandClaude Opus 5.5 cf05f9708c repository: protect the lock objects with the repository key, fixes #8386
The lock objects below locks/ were stored as plaintext JSON, named by the
store hash of that JSON. Anybody with read access to the store could see
which hosts and processes use the repository, and as the content is easy to
guess, even the names alone could reveal it.

Now the lock content is stored in the repository key's store object
envelope, like the index/ and cache/ objects (encrypted and authenticated in
the encrypting modes, authenticated only in the authenticated-* modes), bound
to the repository id and the locks namespace. The name is the store hash of
the envelope. Lock objects are encrypted with encrypt_oneshot(), as a
LockRefresher thread may refresh the lock while the main thread encrypts.

storelocking stays independent of the crypto code: Lock gets seal/unseal
callables, Repository provides them. A Lock keeps using the key it was made
with, so our own lock objects stay readable for us.

A lock object that can not be unsealed (corrupt, tampered with, written by
an older borg 2 beta, ...) is treated as a foreign exclusive lock (fail
closed): it only expires if the store's clock confirms that it was not
written for longer than the stale timeout; without store-side mtimes, only
break-lock removes it. borg warns once about it.

break-lock now loads the key, too (it shows the lock owners with --info).
repo-delete (also with --force) needs the key, as locking needs it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 19:16:35 +02:00
Thomas WaldmannandClaude Opus 5.5 f33a6fa9a9 load the repository key before locking the repository
Repository.open() now loads the key (via key_factory, or a key_loader given
to Repository) before it acquires the lock, see Repository.acquire_lock().
This is a preparation for protecting the lock objects with the key (#8386),
and it also means that borg asks for the passphrase before waiting for the
lock (and no lock is held while the user types the passphrase).

- key_factory() returns the repository's key if it was loaded already, so
  commands loading the key later (Manifest.load, check, ...) do not ask for
  the passphrase again.
- the "other" repository (--other-repo) gets a key_loader using the
  BORG_OTHER_* settings.
- repo-create locks after it created the key and before it writes the config
  (until then, the store is not a repository for other clients).
- tests opening a Repository directly: set a key (or key_loader) before the
  lock is taken, or open it with lock=False.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 19:16:33 +02:00
Thomas WaldmannandClaude Opus 5.5 e5b785a83d key: add encrypt_oneshot(), thread-safe encryption in a one-off session
AEADKeyBase.encrypt() uses the key's current session: the shared cipher and
its IV counter. Two threads calling it concurrently could encrypt two messages
with the same IV, which must never happen with AES-OCB or ChaCha20-Poly1305.

encrypt_oneshot() encrypts in a fresh session (new random session id, IV 0)
and neither uses nor changes the key's current session, so it is safe to call
from any thread. decrypt() reads the result like any other envelope.

This is for rare, small objects that may get written from a background
thread, like the repository lock objects (a LockRefresher thread refreshes
the lock). For the MAC-only (authenticated-*) keys, encrypt() has no state and
encrypt_oneshot() is the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-25 19:10:21 +02:00
TW ee1a01eaba Merge pull request #10416 from mr-raj12/repoobj-parse-header
repoobj: share one object header parser, refs #10318
2026-09-25 19:08:07 +02:00
TW 5b5adc2ff8 Merge pull request #10412 from mr-raj12/compact-skip-corrupt-packs
compact: do not rewrite or merge packs recorded corrupt, refs #10410
2026-09-25 18:46:10 +02:00
TW dd089c03be Merge pull request #10426 from mr-raj12/lock-acquire-slow-store
storelocking: try at least once before a lock acquire times out
2026-09-25 16:51:49 +02:00
Mrityunjay Raj 7b892ee9d0 storelocking: document the at-least-once lock acquire 2026-09-25 12:35:53 +05:30
Mrityunjay Raj e265935c25 storelocking: try at least once before a lock acquire times out, also with --lock-wait 0 2026-09-25 12:32:15 +05:30
Mrityunjay Raj 461c3127f7 storelocking: check for other locks at least once before an exclusive acquire times out 2026-09-25 12:23:26 +05:30
Mrityunjay Raj 7d2b0f2b38 repoobj: address review, catch get() errors in --find-lost-archives, refs #10318 2026-09-25 09:51:56 +05:30