Skip to content

fix(backrest): register the backup-contract hooks, don't just write them (2.1.0) - #162

Merged
pofallon merged 1 commit into
mainfrom
fix/backrest-autowire-hooks
Sep 20, 2026
Merged

pofallon merged 1 commit into
mainfrom
fix/backrest-autowire-hooks

Conversation

@pofallon

Copy link
Copy Markdown
Contributor

Closes #159.

The gap

2.0 writes /config/hola/backup-prepare.sh and backup-finalize.sh and stops.

Backrest has no global hooks. Hook hangs off Repo and Plan only (proto/v1/config.proto), so a script on disk is never called until it is attached to a repository — and at install time there is no repository to attach it to; the operator creates one later, in the UI.

So unless the operator hand-wired two Command hooks, the broker was never invoked and every snapshot was a live-file copy. Meanwhile Hola's dashboard — which only knows this app declares provides: backup@1 — reported the accepting apps as quiesced. Both facts the operator saw were wrong in the reassuring direction.

The fix

A third service, backrest-hola-autowire, that attaches both hooks to every repository and keeps them attached. A repository created next month is wired within 30 seconds.

Through Backrest's API, not the config file. ConfigManager caches the parsed config in memory and invalidates only on its own writes, so editing config.json under a running Backrest is read back only after a restart, and the next UI save silently overwrites it. SetConfig is the same path the UI takes: modno gives optimistic concurrency (a mismatch is refused; the next pass re-reads), and GetConfig does not redact repository passwords, so a read-modify-write round-trip preserves them.

At repository level, not plan level. TasksTriggeredByEvent iterates the repo's hooks for every plan backing onto it, so one wiring covers every plan the operator ever adds.

mikefarah/yq is the image Homepage's bolt-on already uses. It carries yq (native JSON), BusyBox wget (POST with headers) and a shell — the three things this needs, digest-pinned, no custom build.

BACKREST_HOLA_AUTOWIRE=false hands the hooks back to the operator: nothing is added, nothing already present is removed. Hand-wired hooks are recognised by command path and left alone, so an operator who followed the old README sees no change.

Backrest's own login is disabled by default (config.NewDefaultConfig sets auth.disabled) and Hola gates the app at Traefik, so the call needs no credential. If an operator enables Backrest's login, the reconciler says so once and keeps trying.

Verified, not assumed

Against a real backrest:v1.14.1 with a real restic repo and a stub broker:

  • hooks registered on the first pass, idempotent on the second
  • a repository added afterwards wired on the next pass; quiet in between
  • CONDITION_SNAPSHOT_START fired the prepare script, which POSTed /api/contracts/backup/prepare with its Bearer token, polled /status/:id until completed, and let the snapshot proceed
  • CONDITION_SNAPSHOT_END fired finalize
  • fail-closed holds: with the broker unreachable the prepare hook exited 1 and ON_ERROR_CANCEL cancelled the backup — snapshot start hook: task cancelled: exit status 1, no snapshot taken
  • repository password survived the round-trip
  • opt-out and an unreachable Backrest both stay running, no crash-loop

Clean against bin/validate-manifest.mjs and Hola's validateComposeDocument.

Still open after this

apps#153 (postiz's second Postgres has no participation) and its blocker apps#152 (the schema forbids plural participations). Separately, try-hola/hola#470 fixes the coverage recogniser that made postiz read quiesced instead of partial.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vck5KSX2CLxhohx14nb5Sh

…hem (#159)

2.0 wrote /config/hola/backup-{prepare,finalize}.sh and stopped. Backrest has
no global hooks — `Hook` hangs off `Repo` and `Plan` only — so a script on disk
is never called until it is attached to a repository, and at install time there
is no repository to attach it to. Unless the operator hand-wired two Command
hooks in the UI, the broker was never invoked and every snapshot was a live-file
copy, while Hola's dashboard (which only knows the app *declares*
`provides: backup@1`) reported the accepting apps as quiesced. Both facts the
operator saw were wrong in the reassuring direction.

Adds `backrest-hola-autowire`, a reconciler that attaches both hooks to every
repository through Backrest's own API and keeps them attached, so a repository
created next month is wired within 30 seconds.

Through the API, not the file: ConfigManager caches the parsed config in memory
and invalidates only on its own writes, so an edit to config.json under a
running Backrest is read back only after a restart and the next UI save
overwrites it. SetConfig is the path the UI uses — `modno` gives optimistic
concurrency, and GetConfig does not redact repository passwords, so a
read-modify-write round-trip preserves them.

At repo level, not plan level: TasksTriggeredByEvent iterates the repo's hooks
for every plan backing onto it, so one wiring covers every plan the operator
ever adds.

`mikefarah/yq` is the image Homepage's bolt-on already uses; it carries yq
(native JSON), BusyBox wget (POST with headers) and a shell — no custom build.

`BACKREST_HOLA_AUTOWIRE=false` hands the hooks back to the operator: nothing is
added, nothing already present is removed. Hand-wired hooks are recognised by
command path and left alone.

Verified against a real backrest v1.14.1 with a real restic repo and a stub
broker: hooks registered idempotently; a repository added later wired on the
next pass; SNAPSHOT_START fired the prepare script, which POSTed with its
Bearer token and polled to completion; SNAPSHOT_END fired finalize; with the
broker unreachable the prepare hook exited 1 and ON_ERROR_CANCEL cancelled the
snapshot, so fail-closed holds; repository password survived the round-trip;
opt-out and an unreachable Backrest both stay running without crash-looping.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vck5KSX2CLxhohx14nb5Sh
@pofallon
pofallon merged commit 2ad15bd into main Sep 20, 2026
5 checks passed
@pofallon
pofallon deleted the fix/backrest-autowire-hooks branch September 20, 2026 03:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

backrest: register the Hola backup hooks itself — today provides: backup@1 asserts coverage the bundle can't enforce

1 participant