56 lines
2.3 KiB
Markdown
56 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.
|