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

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-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.