If the source snapshot is not encrypted, the target snapshot however
is pre-existing and there is no decryption key which would detect the
mismatch, the unencrypted source will overwrite the encrypted target
due to resync because of mismatching raw manifests.
Likewise, regular syncs from a different source whose snapshot for
the same backup-time has different content used to silently overwrite
the local target, since byte-wise manifest comparison detects the
mismatch and triggers a resync.
Protect against this by extending the key matching checks, refusing
to overwrite any locally encrypted by a not decrypted source as well
as comparing content by calculating the signature using a dummy key
if both, source and pre-existing target are not encrypted.
Reported-by: Daniel Kral <d.kral@proxmox.com>
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20260429143740.886870-1-c.ebner@proxmox.com
[TL: reword bail messages, expand dummy-key comment, fix typos]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
The previous wording might suggest that mount is attempted for all
devices or that all devices belong to a datastore.
Reported-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Link: https://lore.proxmox.com/20260429140155.300518-1-f.ebner@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
The errors returned by the encryption-keys and tape-encryption-keys
endpoints are not plain strings, so the alerts displayed on API2 errors
like failed permission checks end up being empty and therefore not very
helpful.
Extract the error messages to display them correctly in alerts.
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
Link: https://lore.proxmox.com/20260429101139.163532-1-a.bied-charreton@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Avoids an uncaught TypeError on datastores whose status payload omits
the history array, e.g. fresh stores with no RRD samples yet.
Reported-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Introduced in 87a819311 ("datastore: manifest: add helper for change
detection fingerprint") the name was found to be confusing due to the
potential mental clash with the totally unrelated client parameter
named `change-detection-mode`, especially in log output.
The new field name `sync-source-signature` better reflects that this
is the signature of the sync source snapshot stored on the target.
The previous field is kept for backwards compatibility with already
synced snapshots storing the field, the methods, comments and log
messages all updated accordingly.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20260428143642.1012584-1-c.ebner@proxmox.com
[TL: resolved merge conflict with context changes, squashed in
s/sync-souruce-fingerprint/sync-source-signature/ typo fix in
pull_snapshot, renamed setter's parameter from `fingerprint` to
`signature` for consistency, and fixed typos in commit message]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
The change-detection-fingerprint (CDF) is an HMAC stored in a
manifest's unprotected section, used to skip resync of unchanged
snapshots. Two flows write it: encrypt-push stores HMAC over the
source's plain manifest, while decrypt-pull copies the source
manifest's signature into the local target's CDF. Plain signed or
encrypted snapshots carry only their manifest signature, no CDF.
In the sequence
1. signed backup (A)
2. encrypted push (B)
3. decrypting pull (C)
B and C carry a CDF but A does not, and on a subsequent decrypt-pull
from B the source-side check fails: B's CDF was hashed over A's plain
manifest with crypt-mode 'sign-only' on each index/archive, while
target.signature recomputed against C's plain manifest hashes 'none'
instead (decrypt-pull strips the sign-only marker). Same key,
different canonical input, different HMACs - even though the snapshot
content has not changed.
Check both fingerprints and accept on either match: source-side HMAC
reproducibility, or the source's current signature against the
target's stored CDF (set by the previous decrypt-pull). Bail only when
neither identity proves the snapshot unchanged.
Reported-by: Christian Ebner <c.ebner@proxmox.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
[TL: expand commit message a bit]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Avoid mixed usage of "overlap" and "mismatch" terminology,
consistently use "mismatch", as basically all of
check_merge_invariants already did.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
to make it clear when the context switches to a particular group, since not all
warnings will include its name.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20260427120234.634681-4-f.gruenbichler@proxmox.com
[TL: move unrelated s/overlap/mismatch/ to previous commit]
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
this is now logged as
2026-04-27T13:49:49+02:00: oldest source snapshot: 2023-03-15T08:00:13Z
2026-04-27T13:49:49+02:00: conflicting target snapshot: 2026-03-24T10:27:24Z
2026-04-27T13:49:49+02:00: TASK ERROR: cannot merge group 'ct/999' from 'foo' into 'bar': snapshot time mismatch
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20260427120234.634681-3-f.gruenbichler@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
After a partial namespace move it could happen that some groups that
were in the current were actually moved, so reload the view and
namespace selector in any case.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260428131722.3386349-1-d.csapak@proxmox.com
Since the introduction of the namespace/group move, the action column
got an additional icon. The current width is now to narrow, therefore
potentially not showing important icons like the single file browser.
Increase the width so the icon is visible again without manual
intervention.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260427102721.380440-1-c.ebner@proxmox.com
The writer encodes ctime via 'i64::to_le()'; DynamicIndexReader was
copying the field straight out without the matching 'i64::from_le()',
unlike FixedIndexReader. On all platforms PBS targets the round-trip
is a no-op, so this is not a regression in practice, just an asymmetry
that would flip the byte order on a hypothetical big-endian build.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
It looks like this was accidentally changed during a refactor in 2020.
The ctime field is public, so it is not obvious whether anyone depends
on the broken behavior. The index_ctime accessor only seems to be used
by inspect_file in proxmox_backup_debug.
Fixes: 6a7be83efe ("avoid chrono dependency, depend on proxmox 0.3.8")
Signed-off-by: Robert Obkircher <r.obkircher@proxmox.com>
Link: https://lore.proxmox.com/20260428122544.19899-2-r.obkircher@proxmox.com
all the other errors here are constructed like this, and call sites do not log
the context yet, so the error is incomplete otherwise..
this changes the error for concurrent group/snapshot creation from:
Error: while creating locked backup group "/store/pbs/ns/namespace/host/group"'
to:
Error: unable to acquire backup group lock "/run/proxmox-backup/locks/store/namespace/host-group" while creating locked backup group "/store/pbs/ns/namespace/host/group"
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20260428110259.766084-2-f.gruenbichler@proxmox.com
The interpretation of change-detection-fingerprint depends on which
sync flow set it: encrypt-push records an HMAC of the source's plain
manifest under the encryption key, while decrypt-pull from a regular
encrypted backup records the source's encrypted-manifest signature
verbatim. The two are not interchangeable, but the existing
doc-comment did not say so, and the recently-added decrypt-pull
fallback called the value loaded from the local target's CDF
'source_signature', even though no source-side data is involved at
that point.
Spell both interpretations out on the setter and rename the local
in the fallback to 'stored_source_fp', so the comparison reads as
'current == stored_source_fp' against the current source signature.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
A double click on a namespace change it by first setting the namespace
value to the namespace selector and then triggering a reload.
When a new namespace is added to the view with a manual click on
'reload', this new namespace does not yet exist in the namespace
selectors store. So a double click can't properly set this as a value,
and thus does not change the namespace.
To fix this, always reload the namespace selector at the beginning of a
reload, so they should be in sync.
To keep the callback we use for the namespace selector, move it to the
reload function and pass it through to the load there.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260428110520.2343917-1-d.csapak@proxmox.com
when decrypt-pulling a snapshot that was not created by encrypt-pushing, but by
a regular encrypted backup, resyncing requires matching the local plain text
manifest and the remote encrypted one.
in addition to storing a signature of the plain manifest when encrypt-pushing,
store the already existing signature of the encrypted source manifest when
decrypt-pulling, but only use it for matching if the former does not exist.
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Reviewed-by: Christian Ebner <c.ebner@proxmox.com>
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Tested-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260428094437.596604-1-f.gruenbichler@proxmox.com
Builds a synthetic three-chunk dynamic index with a digest collision
at distinct positions and asserts lookup_dynamic_entries populates
end_offset from dynamic_entry.end(). A regression that swaps in a
range-relative value (size, prev_end) is caught here, before it would
manifest as backwards PXAR_PAYLOAD_REF offsets downstream.
It's narrow on purpose, well more due to the limitations of current
test harness, anyhow: pins the identity the chunk-reuse predicate
relies on, but does not exercise the predicate's call sites, the
debug-mode underflow at the threshold-padding subtraction, or
end-to-end PXAR_PAYLOAD_REF monotonicity. A behavioral test on the
full flush path is followup work.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Chris found a more elegant and correct solution for my fix in
ebd0e469e ("client: pxar: inject held chunk on cache-range
discontinuity").
flush_cached_reusing_if_below_threshold checked chunk identity by
digest only at two sites: the padding-ratio threshold check and the
post-flush absorb branch. On a dedup collision (matching digest at a
different end_offset in the previous archive) the absorb branch writes
a backwards PXAR_PAYLOAD_REF that pxar 1.0.1 rejects, and the
threshold check subtracts an unrelated chunk's bytes from the new
range's padding, which underflows 'padding -= used' in debug.
Compare (digest, end_offset) at both sites via a small helper on
ReusableDynamicEntry. Replaces the reverted ebd0e469e ("client: pxar:
inject held chunk on cache-range discontinuity"), which fixed the
absorb-side symptom by forcing injection on every cache-range hole but
over-fired on innocent holes (e.g. a vanished tiny file).
We can further improve the interface here through factoring out size,
digest and index in an e.g. ReusableDynamicEntryIdentity struct that
derives Eq in the future, but lets keep the fix here for now minimal.
Suggested-by: Christian Ebner <c.ebner@proxmox.com>
Co-developed-by: Christian Ebner <c.ebner@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This reverts commit ebd0e469e2, Chris
came up with a more elegant, robust and efficient solution.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Patterns which start with ./ or for that matter contain /../ or similar
constructs will never match, since they get compared to sanitized paths
from directory traversal, sanitize those patterns too. Implement this
for both .pxarexclude files and the --exclude command line option. Log
a warning if a path was sanitized.
Signed-off-by: Manuel Federanko <m.federanko@proxmox.com>
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=5247
Link: https://lore.proxmox.com/20260428080634.79054-1-m.federanko@proxmox.com
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
pull_snapshot returned Ok(Some(default)) on a verified-only or
encrypted-only filter rejection, making pull_snapshot_from log a
misleading 'sync done' for a snapshot that was not actually synced.
This is a theoretical edge case since pull_group's pre-filter
normally catches such snapshots at list time, but the encrypted-only
branch has also been silent since 793c4368f removed its info!().
Reuse the Ok(None) vanished-source path so target-dir cleanup and
'sync done' suppression fall out for free, and emit an explicit
'skipped, did not match filter' log line.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
A snapshot can be pruned or forgotten between pull_group's up-front
list_backup_snapshots and the per-snapshot load. RemoteSourceReader
already maps the resulting 404 to Ok(None) so pull_snapshot can log
'skipped because vanished' and continue with the next snapshot;
LocalSourceReader instead propagated ENOENT as a regular IO error,
aborting the entire group with a confusing 'No such file or directory'
on what is a perfectly normal race against a concurrent prune.
Map NotFound to Ok(None) so both source kinds behave uniformly.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Stale .tmp files and ghost snapshot directories piled up on the target
after prune-during-sync races, and the post-sync 'sync done' log line
fired for snapshots that never actually synced. None of this caused
corruption, but it accumulated cruft on the target datastore and
muddied the task log for operators investigating sync behavior.
Encode 'source vanished' as Ok(None) from pull_snapshot so
pull_snapshot_from can fold it into the existing error-cleanup path,
and unlink the orphaned <archive>.tmp before bailing in
pull_single_archive. Unlink the <manifest>.tmp before the log call so
a collapsing log channel cannot leave it behind for !is_new re-syncs.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
allowing Ok(None) here only makes sense for manifests (where we can treat this
as a disappeared snapshot).
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20260427084952.303245-3-f.gruenbichler@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
since only manifests are valid blobs, indices are not. this also makes a
CRC error manifest itself as error, instead of masking as "snapshot
disappeared".
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Link: https://lore.proxmox.com/20260427084952.303245-2-f.gruenbichler@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
By passing the endtime from the grid to the task log window.
If that's not done, the task log window uses the current time to
calculate the duration, which is always wrong for already finished
tasks.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260427120543.2692503-1-d.csapak@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Since commit 07cb3e7f7 ("client: pxar: optionally split metadata and
payload streams") there are possibly multiple upload streams (data
and metadata archive), errors for each propagated accordingly.
Since this reformatted the error without explicitly including the
context, the resulting error messages are lacking crucial information.
Reformat the error with full context.
Fixes: 07cb3e7f7 ("client: pxar: optionally split metadata and payload streams")
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Link: https://lore.proxmox.com/20260427125955.653377-1-c.ebner@proxmox.com
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
The strict payload-offset check added in pxar 1.0.1 rejects any
payload_offset not strictly greater than the encoder's previously
recorded one. Older client versions could write metadata archives
with duplicate or non-monotonic PXAR_PAYLOAD_REF offsets via the
producer-side bug fixed in the previous commit. Without the encode-
time guard such an archive looked fine on disk, but every later
metadata-mode incremental backup now aborts on the strict check until
the chain is cycled out via --change-detection-mode=data.
Track the highest previously-recorded payload offset globally on the
Archiver and treat any prev-archive entry with a smaller-or-equal
offset as non-reusable, so it is re-encoded instead. The chain
self-heals on the next run, and a single warning per affected file
is emitted to make the fall-back visible. Tracking is intentionally
global, not per cache range, so inversions that span ranges (because
the next backup's cache flushes happen between the two inverted
offsets) are caught too.
Healthy archives written on or after pxar 1.0.1 cannot trigger this
fall-back since the encoder already enforces strict monotonicity at
write time.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
When the cached payload range cannot be extended because a non-reusable
file or directory end opens a hole, the previous flush may have held
back its last chunk for continuation. If the new range's first chunk
shares a digest with the held chunk via dedup, but the two correspond
to different physical positions in the previous archive, the next
flush's digest-match fast path collapses both into one without ever
injecting the held chunk's bytes. The encoder's payload_write_position
then trails the previous_payload_offset recorded during this flush,
and the next add_payload_ref produces a backwards PXAR_PAYLOAD_REF
offset, which the strict offset check introduced in pxar 1.0.1
rejects.
Inject any held chunk explicitly when a cache-range discontinuity is
detected, before update_range, so no held chunk can survive across a
hole. Healthy archives are unaffected: the held chunk only exists
when keep_last_chunk = true, and the inject path is the same one used
by every other branch that already advances the payload position.
The added test pins the cache contract this fix relies on:
update_range alone must not drop last_chunk, so the explicit
take_last_chunk + inject pair has to stay on the hole path.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Restoring a tape key should not require an existing tape key to select,
since it will be a new key, not overwriting the existing one.
(In the tape backup -> encryption keys view it's also always enabled)
To do that, remove the disabled/enableFn properties, but then the
component must be a 'button' (a proxmoxButton needs a record for it
to be enabled).
To make it a bit clearer that it does not have anything to do with the
listed keys, rename it to 'Restore Tape Key' and move it over to the
right of the toolbar.
While touching this, rename 'restoreEncryptionKey' to
'restoreTapeEncryptionKey' to make it also clearer in the code what it
does.
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
Link: https://lore.proxmox.com/20260424115215.2377890-1-d.csapak@proxmox.com