# Runbook — the ep0 datastore copy on DooPlex (decision 70, R-342) ep0's PBS datastore `felhom-offsite` (`/mnt/pbs-datastore`, a separate Hetzner Volume that no server snapshot covers and Hetzner cannot snapshot) is pulled to DooPlex every night. The copy holds **ciphertext only** — each household's whole-box backups are encrypted with that household's own `encryption-key`; DooPlex cannot read them. ## What exists | Where | Object | Purpose | |---|---|---| | ep0 | API token `root@pam!dooplex-sync`, ACL `DatastoreReader` on `/datastore/felhom-offsite` | read-only pull. **The only change on ep0.** | | DooPlex | `felhom-ep0-pbs-tunnel.service` (systemd, runs as `kisfenyo`, `Restart=always`) | `ssh -N -L 127.0.0.1:18007:127.0.0.1:8007 root@ep0` — ep0's PBS listens on `wg0` only; DooPlex is not a WireGuard peer | | DooPlex PBS | remote `ep0` (127.0.0.1:18007, ep0's cert fingerprint pinned) | the pull source | | DooPlex PBS | datastore `ep0-copy` at `/mnt/5_hdd/backup/ep0-copy` | the copy | | DooPlex PBS | sync job `ep0-felhom-offsite`, daily 05:00, `remove-vanished false` | the nightly pull (ep0's prune runs 03:30). **It never removes what ep0 removed** — a deletion on ep0 does not reach the copy | | DooPlex PBS | verify job `verify-ep0-copy`, Saturdays 06:30 | reads the copy back | | DooPlex PBS | prune job `prune-ep0-copy`, daily 07:30, **keep-weekly 8**, all namespaces (decision 71) | keeps the last 8 weekly copies per group; runs after the 05:00 pull, never during it | | DooPlex PBS | garbage collection on `ep0-copy`, Sundays 08:30 | frees the chunks the prune released | | DooPlex PBS | notification target `felhom-operator` (SMTP via Resend → admin@felhom.eu) + matcher `felhom-operator-errors` (every error) | a failed pull or verify reaches the operator. Proven 2026-10-03 with a test mail | Secrets, all out of git: the ep0 token secret in `/etc/proxmox-backup/remote.cfg` (root:backup 0640, base64 — PBS's own format), the Resend key in `/etc/proxmox-backup/notifications-priv.cfg` (root:root 0600). ## Checks ```bash systemctl is-active felhom-ep0-pbs-tunnel.service curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1:18007/ # 200 = ep0's PBS reachable sudo proxmox-backup-manager task list --limit 10 | grep -E 'syncjob|verif' # last runs sudo find /mnt/5_hdd/backup/ep0-copy/ns -mindepth 4 -maxdepth 4 -type d | sort # snapshots per customer ``` ## If ep0 is lost — restore a household's whole box from the DooPlex copy The copy is a normal PBS datastore. Two routes, both needing the household's PBS `encryption-key` (escrowed, recovered with the household's recovery code — the same as restoring from ep0). ### Route 1 — the host reads DooPlex directly. **WALKED 2026-10-04 on demo-hp** (`audits/offsite-finish-2026-10-04/partD-restore-walk.txt`) 1. **On DooPlex:** a read-only token for the restore (`proxmox-backup-manager user generate-token root@pam `, then `acl update /datastore/ep0-copy DatastoreReader --auth-id 'root@pam!'`). Keep the secret in a file only. 2. **On the host:** put the token in `/etc/pve/priv/storage/.pw` (0600) and the household's PBS key in `/etc/pve/priv/storage/.enc`, then append the storage entry to `/etc/pve/storage.cfg`: `pbs: ` / `datastore ep0-copy` / `server ` / `content backup` / `fingerprint ` / `namespace ` / `username root@pam!`. **Do not use `pvesm add pbs` without `--password`:** it validates with the password from its command line, fails 401, and on failure DELETES the `.pw`/`.enc` files you placed (measured). Passing `--password` puts the token on argv. 3. `pvesm list ` → the household's snapshots (measured: 2 s). Then restore with the safe script (R-834): `felhom-restore-beside.sh :backup/ct//