2.3 KiB
backup_store
Pulls already-encrypted backup artefacts from every source host onto
small-backups-box, on a timer, and expires them per source.
Generalises the hand-written pull-backups.sh that had one hardcoded source
(arbret). That job's behaviour is preserved exactly: same source path, same
90 days, same destination directory.
This host holds no key
Everything pulled here is ciphertext produced by backup_source on the source
host. The box cannot read any of it — the age identity lives only on lapy. That
is deliberate: the machine holding every backup should not also be able to open
them.
One failing source must not stop the others
The script is set -uo pipefail, not -e. Each source runs in its own
function, failures are counted, and the script exits non-zero at the end so
systemd marks the unit failed. A dead host costs you that one source, not the
whole run.
This is the specific failure the whole plan exists to prevent: the laptop jobs aborted on first error and then silently produced empty directories for nine months.
Trust points one way
The box authenticates with ~/.ssh/id_pull to an unprivileged, dedicated
account on each source (backup-pull, or arbret on prd-arbret), authorised
with restrict. That account can read one directory and do nothing else — no
sudo, no pty, no forwarding. A compromised backup box cannot reach into
production.
Addressing: names, never IPs
Sources are addressed by name. The job this replaced hardcoded spacey's IP; the droplet was later rebuilt, the address was recycled to a stranger, and the backup failed silently from 2025-12-01 while the directory listing still looked healthy.
Two kinds of name are in play:
- Tailnet members (vipy, memos-box, …) → MagicDNS names. These require a
headscale ACL grant from
tag:small-backups-boxto the source's:22; without it the box cannot even resolve the peer, let alone reach it. - spacey is not a tailnet member — it is the headscale control server — so
its backup is pulled over the public internet via
headscale.contrapeso.xyz, which follows the host if the droplet is rebuilt.
Retention here is the long tail
Sources keep a few days locally; this box keeps 90 (or whatever the source entry says). Losing the source's local copy is expected.