Anchoring the rotation on the newest directory name let the VPS steer
it: one future-dated directory (VPS clock off, or a VPS gone rogue)
pushed the window forward and deleted every real backup, with exit 0
and an ok report. Two more inputs from the outbox could do harm: rsync
-rt rewrote an already pulled .age whenever the VPS served another
version, and an impossible dated name (2026-13-45) failed `date -d`
under set -e, stopping every later pass for good.
- latest is the newest valid dated directory not after today; the
rotation keeps it and the 29 days before it;
- a name is dated only if `date` gives it back unchanged (busybox turns
2026-02-30 into 2026-03-02); an invalid or future name is logged,
never counted, never removed, and the pass goes on;
- --ignore-existing: a pulled file is never rewritten. The VPS writes a
.tmp and renames it, and the filter drops the .tmp.
Refs maximus/la-compagnie-maximus#220
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rotation cutoff was TODAY minus 29 days. When the VPS stops
producing (dead cron, failing pg_dump) or this machine's clock jumps
ahead, the last off-site copy aged out after 30 days, the pass exited 0
and the report said ok on a directory it had just deleted. The cutoff
now comes from the newest dated directory held here, which is never
removed.
Review suggestions:
- --prune-empty-dirs: a dated directory with no .age file is not
created, and one found empty logs a warning;
- files counts the dated directory only (-maxdepth 1), not partial
files left in .rsync-partial/;
- an EXIT trap removes the header file holding the ingest token, with
TERM/INT/HUP turned into an exit so a stop by systemd runs it.
Refs maximus/la-compagnie-maximus#220
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CI runs the tests in node:22-alpine, where 9 of the 12 cases failed:
no ssh on PATH, and busybox has neither `find -printf` nor
`date -d "... days"`. The newest dated directory is now found by a loop
over the glob, and the rotation cutoff by epoch arithmetic, which GNU
date and busybox both read. The tests add a fake ssh, and a fake flock
where the host has none.
The rotation_failed case failed the pass through a read-only directory,
which root ignores: it now uses a fake rm.
Review suggestions: the rsync argv must put --include=*/ before
--exclude=* (without it nothing crosses and the pass still says ok), and
a case runs the real rsync through the fake ssh when rsync is present.
A pass whose newest directory is not today's logs a warning.
Refs maximus/la-compagnie-maximus#220
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POSIX sh agent run by a systemd user timer (Persistent=true, retries on
failure): rsync over the classic sshd on 100.86.30.21:2222 with a pinned
host key, .age files only, 30-day rotation that fails loudly, and a
report to POST /backups/pull when the ingest token is configured (sent
through a header file, never argv). Never decrypts.
Refs maximus/la-compagnie-maximus#220
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>