• v0.10.0 764421f636

    v0.10.0
    All checks were successful
    CI / Build / Publish / Shellcheck (push) Successful in 2s
    CI / Build / Publish / Validate CHANGELOG structure (push) Successful in 1s
    CI / Build / Publish / PR build & smoke test (push) Has been skipped
    CI / Build / Publish / Round-trip + scheduler smoke (push) Successful in 4m0s
    CI / Build / Publish / Workflow summary (push) Successful in 1s
    CI / Build / Publish / Bats unit tests (push) Successful in 6s
    CI / Build / Publish / Host-driven integration (entrypoint + storage sidecars) (push) Successful in 1m51s
    CI / Build / Publish / Build multi-arch and push (push) Successful in 2m6s
    CI / Build / Publish / Reconcile releases across registries (push) Successful in 55s
    Stable

    Cody Bryant released this 2026-07-12 11:49:44 -07:00 | 9 commits to main since this release

    Breaking

    • Backups and maintenance are now two independent pipelines. The backup run is hooks → backup → copies, nothing else; storage check and prune moved to a separate maintenance pipeline with its own schedule, lock, and log (maintenance.log). Nightly-cost consequence: the multi-hour storage-wide chunk listings that used to run inside every backup (check on every storage plus prune -exhaustive on every storage — up to three full sftp/B2 enumerations per storage per night) are gone from the backup path entirely; secondaries are verified by the copy itself (its destination enumeration re-uploads anything missing, healing instead of just reporting). The two pipelines coordinate through per-storage locks, so a check/prune never overlaps a copy on the same storage (the copy-vs-prune concurrency duplicacy does not document as safe is excluded structurally); the waiting side logs who holds the storage. A maintenance overrun can never delay the next backup.
    • CRON_SCHEDULE was renamed to BACKUP_SCHEDULE, and the new MAINTENANCE_SCHEDULE drives the maintenance pipeline (unset = maintenance only runs via archiver maintenance). A container started with CRON_SCHEDULE set fails fast with a rename message — silently ignoring it would mean no scheduled backups. The scheduled crontab now invokes the synchronous verbs (archiver backup / archiver maintenance), so supercronic sees real job durations and adds its own overlap-skip on top of the pipeline locks. A scheduled deployment without MAINTENANCE_SCHEDULE gets a prominent startup warning, and healthcheck warns when any storage has gone more than 8 days without a successful check or prune.
    • ROTATE_BACKUPS was renamed to PRUNE_BACKUPS (old name still translated silently for old bundle configs, with a startup warning when set as an env var), joined by CHECK_BACKUPS (default true) so shared-storage deployments can disable either phase independently, and PRUNE_EXHAUSTIVE_FREQUENCY (off/daily/weekly/monthly, default monthly): the expensive -exhaustive chunk-listing garbage collection now runs on its own interval per storage instead of on every prune, tracked in a state file and fired belatedly at the next maintenance run if the schedule is sparser than the frequency. archiver maintenance exhaustive forces it.
    • archiver start and archiver restart were removed (they error with guidance): archiver backup --detach is the background form, with the visible already-running refusal. The prune|retain arguments to backup were removed likewise. archiver stop takes a target: backup, maintenance, or all (default).

    Added

    • archiver maintenance [exhaustive] — the maintenance pipeline verb: per storage, check -all -fossils -resurrect (when CHECK_BACKUPS) then prune -all with the retention policy (when PRUNE_BACKUPS), recording per-storage last-success timestamps that archiver status displays and healthcheck monitors. Also available to external schedulers as run maintenance (synchronous, exit code propagates).
    • Secondary copies now run in parallel. Each duplicacy copy to a secondary is launched concurrently (still direct children, so pause/stop keep working); on an upload-capped link one storage's bandwidth-free destination enumeration overlaps another's upload, so the copy phase approaches max(legs) instead of sum(legs).
    • Phase durations in the logs: each service backup, each copy, and each check/prune logs how long it took, so slow nights are diagnosable from the summary instead of by archaeology.

    Deprecated

    • Bundle mode remains supported through 0.10.x.

    Fixed

    • archiver backup --detach (the background form, formerly archiver start) now refuses visibly when a backup is already running (non-zero exit and the same "A backup is already running (PID …)" message the synchronous path prints). Previously the dispatcher backgrounded main.sh with its output discarded and printed the success line unconditionally, so a refused start looked like a successful one. A stale lock (dead PID) still starts normally.
    • The copy phase now holds the primary storage's lock (as source) for its whole duration, not just each destination's lock, so a maintenance prune can no longer fossilize chunks a concurrent copy is still reading from the primary — closing the one copy-vs-prune overlap the per-storage locks had left open.
    • The container clears any leftover pipeline lock and stop-flag files at startup, so a lock left by an unclean prior shutdown can neither fake a live holder (which would refuse every scheduled backup after a PID recycle) nor abort the first backup via a stale stop flag. A stale pipeline lock now reports as a healthcheck warning (self-healing, reaped on the next run) rather than flipping the container UNHEALTHY.

    Dependencies

    • chore(deps): update docker/login-action action to v4.4.0
    Downloads