fix(check-backup-freshness): age PBS snapshots by their name, not the directory mtime

PBS rewrites index.json.blob inside a snapshot directory at every verify
(the next 02:30, then every 30 days per verification.cfg outdated-after 30)
and on notes/protect edits, which moves the directory mtime forward.
Measured: ana-pve/vm/101's 2026-10-04T10:00:04Z snapshot read as 2026-10-06.
A covered guest whose backups stopped would have alarmed up to a day late,
then gone quiet for ~2 days after each monthly re-verify.

The age now comes from the snapshot name (its backup start time).
Verified: the default run exits 0 with 28 fresh PBS groups and vm/101 dated
2026-10-04. Positive control BACKUP_MAX_AGE_HOURS=5 flags 10 groups by name
age, exit 1.
This commit is contained in:
vh
2026-10-06 08:07:04 -07:00
parent 0a348e548c
commit d920cf3816
+8 -1
View File
@@ -232,10 +232,17 @@ for want in $EXPECTED_NH3; do
done
# --- Layer: PBS VM images (newest per guest, all namespaces) ---
# ⚠ The age comes from the snapshot's NAME (its backup time, e.g. 2026-10-04T10:00:04Z),
# never from the directory's mtime (2026-10-06). PBS rewrites index.json.blob inside the
# snapshot dir at every verify (first one at the next 02:30, then every 30 days, from
# verification.cfg outdated-after 30) and on notes or protect edits, which moves the
# dir mtime forward. Measured: ana-pve/vm/101's 10-04 snapshot read "2026-10-06" here.
# A guest whose backups had stopped would alarm up to a day late, then go quiet again
# for ~2 days after each monthly re-verify.
pbs=$($SSH pbs-ana 'for ns in /mnt/pbs-datastore/ns/*/; do n=$(basename "$ns")
for d in vm ct; do for g in "$ns$d"/*/; do [ -d "$g" ] || continue
nb=$(ls -d "$g"20*T* 2>/dev/null | sort | tail -1)
[ -n "$nb" ] && echo "$n/$d/$(basename "$g") $(stat -c %Y "$nb")"
[ -n "$nb" ] && echo "$n/$d/$(basename "$g") $(date -d "$(basename "$nb")" +%s)"
done; done; done' 2>/dev/null)
if [ -z "$pbs" ]; then errors+=("PBS-ANA: unreachable or no snapshots"); else
for ns in "${!NS_NODE[@]}"; do load_jobs "$ns" || errors+=("coverage for namespace '$ns' UNREADABLE — nothing in it will be suppressed"); done