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