Fold restic backups into genebean.services.restic, fix fleet-wide prune performance#699
Merged
Conversation
Same bug as the system-level fix already in place (upstream systemd/dbus-broker#37515 - Type=notify-reload without the RELOADING=1/READY=1 handshake), but for the separate per-user-session dbus-broker instance. Hit during a real deploy to nixnuc: the user-session reload hung, failed the whole activation, and the automatic rollback then tripped over the identical hang on its way back to the previous generation. NixOS's systemd.user.services.* mirrors systemd.services.* for the user instance, so the same reloadIfChanged/restartIfChanged override applies there too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iUoDbZzHLBGu7jD3pVGz2
…ces.restic Triggering kiosk-gene-desk's restic backup manually to verify the kiosk-backups consolidation (PR #698) revealed it running 37+ minutes on a Pi 4 actively serving the kiosk display. The actual backup succeeded in seconds - what ran long was prune, a whole-repository operation (restic dedupes data into blobs shared across every host's snapshots, so it has to scan all of them regardless of which host's forget triggered it) that NixOS's services.restic.backups.<name>.pruneOpts always bundles into `forget --prune`. Every host sharing the repo (nixnuc, hetznix01, both kiosks) was redundantly re-running that same expensive scan on its own daily schedule. Fix: split forget (cheap, --host scoped, safe to run everywhere) from prune (expensive, whole-repo) via raw systemd units bypassing the module's bundling. nixnuc - the one always-on host - becomes the sole place prune ever runs, gated by a new enablePruneJob option. A pre-commit check (scripts/check-restic-prune-singleton.sh) enforces exactly one host has it set, since running prune on zero hosts means space is never reclaimed and running it on more than one wastes redundant work. Replaces the bare modules/shared/nixos/restic.nix import with a proper genebean.services.restic module (home owns options, nixos reads back - same shape as chromium-kiosk/tailscale/kiosk-backups), since this is now genuinely reusable, per-host-configurable infrastructure rather than a one-size-fits-all shared import. kiosk-backups.nix's own pre-reinstall prune job gets the same forget/prune split and now asserts its dependency on genebean.services.restic.enable rather than failing obscurely without it. Verified via deploy-rs across all four affected hosts (kiosk-entryway, kiosk-gene-desk, hetznix01, nixnuc): dry-activate, real deploy with magic-rollback confirmation, zero failed units post-deploy, backup paths unchanged on kiosk-gene-desk, and the forget-only job completing in ~21s on kiosk-gene-desk (down from 37+ minutes). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iUoDbZzHLBGu7jD3pVGz2
Caught live on nixnuc: restic-fleet-prune ran 5+ hours without finishing, still stuck on "finding data that is still in use for 190 snapshots" - the read-only scan phase. The unit had no HOME or RESTIC_CACHE_DIR set, so restic logged "unable to locate cache directory: neither $XDG_CACHE_HOME nor $HOME are defined" and "running prune without a cache, this may be very slow!" and paid the full remote-index cost on every operation instead of reusing a local cache. The module-generated restic-backups-daily.service already maintains a cache at /var/cache/restic-backups-daily via its own CacheDirectory. Every raw unit (restic-forget-daily, restic-forget-pre-reinstall-backstop, restic-fleet-prune, restic-forget-kiosk-pre-reinstall) now points at that same cache via RESTIC_CACHE_DIR, and each also declares its own CacheDirectory (same name) rather than relying on restic-backups-daily having already run first - CacheDirectory is idempotent, so multiple units sharing the same one is safe. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iUoDbZzHLBGu7jD3pVGz2
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
prune, a whole-repository operation that every host sharing the repo was redundantly re-running on its own daily scheduleforget(cheap,--host-scoped, safe everywhere) fromprune(expensive, whole-repo) via raw systemd units, since NixOS'spruneOptsalways bundles them -nixnucbecomes the sole host runningprune, gated by a newenablePruneJoboption enforced to be true on exactly one host via a new pre-commit check (scripts/check-restic-prune-singleton.sh)modules/shared/nixos/restic.niximport with a propergenebean.services.resticmodule (home owns options, nixos reads back - same shape aschromium-kiosk/tailscale/kiosk-backups)Test plan
nix flake check --all-systemspasses--dry-activateon all four affected hosts (kiosk-entryway, kiosk-gene-desk, hetznix01, nixnuc) before any real deployrestic-fleet-prune.servicetriggered manually on nixnuc and completes successfully🤖 Generated with Claude Code
https://claude.ai/code/session_018iUoDbZzHLBGu7jD3pVGz2