Files
esh-pfi-infrastructure/configs/restic/ana-ml2/README.md
T
vh 7e7130172e vllm: rename stack from vllm-qwen3 → vllm + add Skywork reward classifier
Two related changes shipped together. The stack rename is independent
but adding `vllm-reward` to the existing `vllm-qwen3` would have made
that name actively misleading.

**Rename:** `stacks/vllm-qwen3/ → stacks/vllm/`. Updated all in-repo
references (README.md root, servers/ana-ml2/, stacks/llama-swap/,
configs/restic/ana-ml2/, docs/runbooks/disaster-recovery.md). Two
intentional history mentions retained (servers/ana-ml2 + stacks/vllm
README).

**Add `vllm-reward` service:** serves Skywork-Reward-V2-Llama-3.1-8B-AWQ
on port 8003. The AWQ output is a locally-quantized model (not from HF),
so bind-mounts `/tank/aimodels/llm:/local-models:ro` rather than the
shared HF cache. Model config.json declares LlamaForSequenceClassification
which vLLM's pooling runner picks up automatically — produces a single
reward score per input via /classify.

**Flag note:** the user's spec listed `--task classify`, but vLLM 0.19.1
deprecated --task in favor of --runner pooling (model architecture in
config.json drives the classification head). Compose uses --runner
pooling with a comment explaining the substitution.

**GPU memory:** no rebalance needed — production had already tuned
EMBED/RERANK down from 0.40 to 0.20 each (canonical .env.example now
matches reality). Adding REWARD at 0.30 totals 0.70, leaving ~14 GB
headroom on the 48 GB Ada.

**Server-side:** brought existing vllm-qwen3 down, mv'd
/opt/docker/compose/vllm-qwen3 → /opt/docker/compose/vllm, appended
REWARD_* lines to existing .env (preserving API_KEY/HF_TOKEN), deployed
new compose via scripts/deploy-stack.sh, brought all 3 services up.

**Smoke tests:**
- /health on 8001/8002/8003 → 200
- /v1/models on 8003 → lists Skywork/Skywork-Reward-V2-Llama-3.1-8B-AWQ
  with max_model_len 16384
- /classify with a sample conversation → returns LABEL_0 with prob 0.9999
  (single-output regression-style reward score, expected shape for a
  reward model)
2026-05-13 22:00:26 -07:00

252 lines
8.4 KiB
Markdown

# 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 <<EOF
RESTIC_REPOSITORY=rest:http://ana-ml2:<EXISTING-HTPASSWD-PASSWORD>@10.250.50.70:8000/ana-ml2/
EOF
sudo chmod 600 /etc/restic/restic.env &&
sudo tee /etc/restic/password > /dev/null <<EOF
<EXISTING-REPO-PASSPHRASE>
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 "<new-pw>"' \
| 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.