# restic / ana-ml2 **Bare metal** — ana-ml2 runs directly on a server chassis, not on any Proxmox hypervisor, so vzdump doesn't cover it. This is the sole backup for the host's configuration and is the highest-stakes backup target in the fleet. ## What's backed up | Path | Purpose | |---|---| | `/opt/docker` | Compose files + config bind mounts (~110 MB) | | `/etc` | Host config — systemd, ssh, chrony, apparmor, apt, etc. | | `/root` | Root's ad-hoc scripts, shell history, ssh keys | | `/var/lib/docker/volumes` | Named volumes used by running stacks (small — models live elsewhere) | ## What's **not** backed up (by design) - **`/tank/*`** — model caches (HF hub, GGUFs, ComfyUI checkpoints, Kokoro voices, Vibevoice weights, Parakeet weights). Regenerable from upstream; storing them would blow the repo size budget. - `/root/.cache`, `/root/.npm`, shell-noise caches - Any `*.log`, `*.pid`, `.../logs/` directories ## Stacks on this host All inference/AI-adjacent — none use relational databases, so no pre-backup DB hook is required. Contrast with `configs/restic/ana-docker/` where synapse/seafile/vaultwarden DB dumps run first. - `llama-swap` — GGUF swapper (llama.cpp) - `vllm` — embedding + rerank + reward classifier - `comfyui`, `kokoro`, `parakeet`, `vibevoice` - `beszel-agent-ana`, `dozzle-agent-ana`, `dockge` ## Deploy (one-time setup) ### 1. Credentials (repo already exists — reuse) The Anaheim rest-server already has an `ana-ml2` entry in its `.htpasswd` and a repo at `/ana-ml2/` from a prior pass. Reusing both keeps the snapshot history consolidated. On ana-ml2, install the two secret files from the existing values (both live in your password manager): ```bash ssh -t ana-ml2 ' sudo install -d -o root -g root -m 0700 /etc/restic /var/lib/restic && sudo tee /etc/restic/restic.env > /dev/null <@10.250.50.70:8000/ana-ml2/ EOF sudo chmod 600 /etc/restic/restic.env && sudo tee /etc/restic/password > /dev/null < EOF sudo chmod 600 /etc/restic/password ' ``` ### 2. Verify creds against the existing repo Before deploying the profile, prove the secrets are correct. The `/etc/restic/restic.env` and `/etc/restic/password` files are root:600, so the env-file read AND the restic invocation both need to run inside the same sudo shell — otherwise `$(cat …)` runs as the login user, fails silently, and restic complains about a missing repository: ```bash ssh -t ana-ml2 ' sudo bash -c " set -a . /etc/restic/restic.env set +a RESTIC_PASSWORD_FILE=/etc/restic/password restic snapshots " ' ``` Expected: either a list of prior snapshots (from the earlier docker-files pass) or `no snapshots found` — both mean auth is good. If you see `Fatal: wrong password or no key found`, the passphrase in `/etc/restic/password` doesn't match the repo — check your password manager or rotate (see "Recreating the repo" below). **Do not run `restic init`** — the repo is already initialized; init against an existing repo errors out deliberately. ### Recreating the repo (lost passphrase / starting fresh) If `restic snapshots` returns `wrong password or no key found` and the passphrase can't be recovered, the old repo's data is unrecoverable and the cleanest path is to wipe and reinit. If you're truly starting from zero (no prior htpasswd entry or repo at all), skip the `htpasswd` step — only the init is needed. The rest-server's data root is `/mnt/backup/restic/repo/ana/` on ana-docker (NFS-mounted from the Debian NAS at 10.250.50.50). Per-host repos are direct subdirs — for `ana-ml2` that's `/mnt/backup/restic/repo/ana/ana-ml2/`. See `stacks/rest-server-ana/README.md` for the full sibling layout (`ana/`, `esh/`, `nh3/`). ```bash # 1. Wipe the old repo content on ana-docker. `--append-only` blocks # restic's own delete ops but not direct filesystem removal — this # is intentional for exactly this case. ssh -t ana-docker ' sudo rm -rf /mnt/backup/restic/repo/ana/ana-ml2 && sudo docker restart rest-server ' # 2. Optional: rotate the htpasswd password for ana-ml2 at the same time. # Produces a new hash; replace in place. (Skip if the existing # htpasswd password is still trusted.) ssh ana-docker 'docker run --rm httpd:2.4-alpine htpasswd -nbB ana-ml2 ""' \ | ssh -t ana-docker 'sudo sed -i "/^ana-ml2:/d" /mnt/backup/restic/repo/ana/.htpasswd && sudo tee -a /mnt/backup/restic/repo/ana/.htpasswd >/dev/null' # 3a. Init the repo — interactively prompt for a passphrase you generated # in your password manager ahead of time. restic asks twice (init + # confirm). This keeps the passphrase out of shell history / transcripts. ssh -t ana-ml2 ' sudo bash -c " set -a . /etc/restic/restic.env set +a restic init " ' # 3b. Install the same passphrase into /etc/restic/password so timers can # run unattended. `cat > file` + Ctrl-D avoids the passphrase ever # landing in your shell history or the ssh command line. ssh -t ana-ml2 'sudo bash -c "cat > /etc/restic/password && chmod 600 /etc/restic/password"' # Terminal waits for input: # - paste the same passphrase # - press Enter # - press Ctrl-D on an empty line # (restic strips the trailing newline when reading the file.) # 3c. Verify the file-based passphrase agrees with what init registered. ssh -t ana-ml2 ' sudo bash -c " set -a . /etc/restic/restic.env set +a RESTIC_PASSWORD_FILE=/etc/restic/password restic snapshots " ' # Expect: "no snapshots found" — repo is initialized and both paths # (interactive and file-based) decrypt it. ``` **Alternative (random machine-generated passphrase):** if you want restic to generate the passphrase rather than supplying one from your password manager, replace 3a/3b with: ```bash ssh -t ana-ml2 ' sudo bash -c " openssl rand -base64 48 | tr -d \"\\n\" > /etc/restic/password chmod 600 /etc/restic/password cat /etc/restic/password # copy this to your password manager NOW echo set -a; . /etc/restic/restic.env; set +a RESTIC_PASSWORD_FILE=/etc/restic/password restic init " ' ``` The passphrase prints to the terminal exactly once — don't close the window before copying it. Losing it = losing this repo. ### 3. Install resticprofile on ana-ml2 The host has `restic` but not `resticprofile`. From the resticprofile docs: ```bash ssh -t ana-ml2 ' curl -sfL https://raw.githubusercontent.com/creativeprojects/resticprofile/master/install.sh | sudo sh -s -- -b /usr/local/bin /usr/local/bin/resticprofile --version ' ``` ### 4. Deploy the profile ```bash scp configs/restic/ana-ml2/profiles.yaml ana-ml2:/tmp/profiles.yaml ssh -t ana-ml2 ' sudo install -o root -g root -m 0644 /tmp/profiles.yaml /etc/restic/profiles.yaml && rm /tmp/profiles.yaml ' ``` ### 5. Generate + enable the systemd timers ```bash ssh -t ana-ml2 ' sudo resticprofile --config /etc/restic/profiles.yaml schedule --all && systemctl list-timers "resticprofile*" ' ``` `resticprofile schedule` writes the unit files into `/etc/systemd/system/` and enables them. You should see three timers: backup, forget, check. ### 6. Fire a manual backup to verify ```bash ssh -t ana-ml2 'sudo resticprofile --config /etc/restic/profiles.yaml backup --verbose' ``` Expected output: a fresh snapshot id and a line summarizing added data size + file count. Cross-check from the hub: ```bash ssh ana-docker 'docker exec backrest ls /repos/ana-ml2/' ``` Or in Backrest UI (http://10.250.50.70:9898), `ana-ml2` shows up as a new repo with one snapshot. ## Ongoing Timers run at 01:00 / 03:00 / Sunday 05:00 (matching the rest of the fleet). Monitor via the `schedule-log` entries or Backrest's web UI. ## Restoring ```bash # List snapshots ssh -t ana-ml2 'sudo resticprofile --config /etc/restic/profiles.yaml snapshots' # Restore a single path ssh -t ana-ml2 'sudo resticprofile --config /etc/restic/profiles.yaml restore --target /tmp/restore latest --path /opt/docker' ``` ## Gotchas specific to this host - **Large `/tank` is deliberately excluded** — don't edit source paths to include it without a plan for the ~TB of model data. - **Repo passphrase is irreplaceable.** Losing `/etc/restic/password` without a copy elsewhere = losing every snapshot in the `ana-ml2` repo. Store a copy in your password manager on day one. - **`/var/lib/docker/volumes` assumes docker uses the default data root.** If you ever switch to a custom dockerd data-root, update the source list.