personal_infra/ansible/roles/backup_store/README.md
2026-09-12 16:02:00 +02:00

55 lines
2.3 KiB
Markdown

# `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-box` to 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.