Ops-dashboard/deploy/server-backup/README.md
Janpeter Visser 2b03ee02e0 feat(server-backup): one-shot install script for ops-agent wiring
Adds deploy/server-backup/install-flows.sh — een idempotent installer die
de ops-agent-zijde van de server-backup feature aan elkaar plakt:

  1. wrappers/*.sh                 → /srv/backups/scripts/wrappers/
  2. flows.example/server_backup_* → /etc/ops-agent/flows/
  3. commands.yml.example commands → /etc/ops-agent/commands.yml (append, met backup)
  4. NOPASSWD-regels voor wrappers → /etc/sudoers.d/ops-agent (visudo-validated)
  5. systemctl restart ops-agent
  6. systemctl enable --now server-backup.timer

Wat het bewust *niet* doet (staat in scriptheader): restic env/password
aanmaken, repos initialiseren, base-scripts of systemd-units plaatsen —
die secrets-stappen blijven handwerk per README "Snelle installatie".

Re-run safe:
- cmp-check per file in stappen 1-2 (skip als identiek)
- grep-check op command-name in stap 3 (skip als al aanwezig)
- visudo-validatie in stap 4 voorkomt lockout bij syntax-fout
- backups van mutaties: commands.yml.bak.<ts> en sudoers.d/ops-agent.bak.<ts>

Regex-fix t.o.v. eerste handmatige run vandaag: command-block-extractie
gebruikt nu [a-z0-9_]+ ipv [a-z_]+, zodat namen met digits (restic_*_b2)
als losse blocks gezien worden. Het oude pattern miste ze maar sleepte
ze toevallig mee in het vorige block — eindresultaat correct, output
misleidend. Nieuwe versie faalt expliciet als een command echt ontbreekt.

README aangevuld met sectie "Ops-agent wiring (na stap 1-7)" die naar
het script verwijst.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:04:43 +02:00

141 lines
6.1 KiB
Markdown

# Server backup — deploy artefacten
Dagelijkse server-brede backup met restic naar **NAS** (lokaal) en **Backblaze B2** (offsite, Object Lock). Inclusief structured statusfile die de ops-dashboard kan lezen.
De volledige beschrijving — voorwaarden, B2 keys, Object Lock, Forgejo-restore-test, integriteits-schedule — staat in [`docs/runbooks/server-backup.md`](../../docs/runbooks/server-backup.md).
## Bestanden
| Bestand | Doel | Plek op host |
|---|---|---|
| `server-backup.sh` | hoofd-script (phase-based, flock, statusfile) | `/srv/backups/scripts/server-backup.sh` |
| `restore-test.sh` | restore latest snapshot + check critical files | `/srv/backups/scripts/restore-test.sh` |
| `server-backup.service` | systemd oneshot | `/etc/systemd/system/server-backup.service` |
| `server-backup.timer` | daily 03:30 + 10 min jitter | `/etc/systemd/system/server-backup.timer` |
| `restic-backup.env.example` | env-template (repos, B2 keys, Forgejo) | kopiëren naar `/etc/restic-backup.env` |
Bovendien aan te maken (niet in deze repo, omdat het secrets zijn):
- `/etc/restic-backup.password` — alleen het restic-wachtwoord (mode `0400 root:root`).
## Snelle installatie (zie runbook voor alle context)
```bash
# 1. Tools en directories
sudo apt update && sudo apt install -y restic jq
sudo mkdir -p /srv/backups/scripts /srv/backups/logs /srv/backups/status \
/var/backups/databases
sudo chmod 0750 /srv/backups/logs /srv/backups/status
# 2. Scripts plaatsen
sudo cp deploy/server-backup/server-backup.sh /srv/backups/scripts/
sudo cp deploy/server-backup/restore-test.sh /srv/backups/scripts/
sudo chmod 0750 /srv/backups/scripts/*.sh
sudo chown root:root /srv/backups/scripts/*.sh
# 3. Env + password
sudo cp deploy/server-backup/restic-backup.env.example /etc/restic-backup.env
sudo chmod 0600 /etc/restic-backup.env
sudo chown root:root /etc/restic-backup.env
# Genereer wachtwoord — bewaar dit OOK in je password manager.
sudo sh -c 'openssl rand -hex 24 > /etc/restic-backup.password'
sudo chmod 0400 /etc/restic-backup.password
# 4. Vul /etc/restic-backup.env (RESTIC_REPO_NAS, RESTIC_REPO_B2,
# B2_ACCOUNT_ID, B2_ACCOUNT_KEY, FORGEJO_*). Zie runbook deel A+B.
# 5. Repos initialiseren (zie runbook deel C voor Object Lock + key-capabilities)
sudo -E bash -c 'set -a; . /etc/restic-backup.env; set +a; \
export RESTIC_PASSWORD_FILE=/etc/restic-backup.password; \
restic -r "$RESTIC_REPO_NAS" init && \
restic -r "$RESTIC_REPO_B2" init'
# 6. Systemd
sudo cp deploy/server-backup/server-backup.service /etc/systemd/system/
sudo cp deploy/server-backup/server-backup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now server-backup.timer
systemctl list-timers | grep server-backup
# 7. Eerste run handmatig (volgen via journalctl)
sudo systemctl start server-backup.service
journalctl -u server-backup.service -f
```
### Ops-agent wiring (na stap 1-7)
Voor de **/flows/server-backup**-pagina en **/settings/backups** in het dashboard
moet ops-agent ook weten van de wrappers, commands, flow-YAMLs en de
NOPASSWD-sudoers-regels. Dat doet een idempotent install-script:
```bash
sudo bash deploy/server-backup/install-flows.sh
```
Wat het regelt (en wat het bewust **niet** doet) staat in de header van het
script. Re-run safe; backups van `commands.yml` en `sudoers.d/ops-agent` worden
bewaard met `.bak.<timestamp>`-suffix. Daarna is de UI op
`/flows/server-backup` direct te gebruiken.
## Verifiëren
```bash
# Statusfile
sudo jq . /srv/backups/status/last-run.json
# Snapshots
sudo -E bash -c 'set -a; . /etc/restic-backup.env; set +a; \
export RESTIC_PASSWORD_FILE=/etc/restic-backup.password; \
restic -r "$RESTIC_REPO_NAS" snapshots; \
restic -r "$RESTIC_REPO_B2" snapshots'
# Restore-test (NAS, niet-destructief — restored naar /tmp/restore-test)
sudo /srv/backups/scripts/restore-test.sh nas
sudo jq . /srv/backups/status/last-restore-test.json
```
## Statusfile-schema
Het script schrijft `/srv/backups/status/last-run.json` na elke run (success of failure), atomisch via temp + `mv`. De ops-dashboard leest deze file via `read_backup_status` (zie `ops-agent/commands.yml.example`).
```json
{
"schema_version": 1,
"overall_status": "success | partial_failure | failed",
"started_at": "2026-05-15T03:30:00+02:00",
"completed_at": "2026-05-15T03:48:21+02:00",
"duration_seconds": 1101,
"host": "scrum4me-srv",
"phases": {
"postgres_dump": { "status": "success", "exit_code": 0, "...": "..." },
"forgejo_dump": { "status": "skipped", "exit_code": 99, "...": "..." },
"forgejo_db_dump": { "status": "skipped", "exit_code": 99 },
"restic_nas": { "status": "success", "exit_code": 0, "snapshot_id": "abc123" },
"restic_b2": { "status": "degraded", "exit_code": 3, "error": "1 file unreadable" },
"forget_nas": { "status": "success", "exit_code": 0 },
"check_nas": { "status": "success", "exit_code": 0 },
"check_b2": { "status": "success", "exit_code": 0 }
}
}
```
Per phase `status`:
| status | betekenis | telt mee als |
|---|---|---|
| `success` | exit 0 | success |
| `skipped` | exit 99 — phase niet van toepassing (bv. Forgejo niet geïnstalleerd) | success |
| `degraded` | exit 3 — restic snapshot is gemaakt maar bepaalde files waren onleesbaar | partial_failure |
| `failed` | andere non-zero exit | partial_failure of failed (zie `overall_status`) |
| `pending` | phase niet gerund (script aborted vóór deze phase) | partial_failure |
`overall_status` regels:
- **`failed`** als `postgres_dump` faalt (DB-dump is autoritatief), of als **beide** restic repos falen.
- **`partial_failure`** bij enige `failed` of `degraded` phase die niet kritisch is (bv. één restic repo down, of forgejo_dump faalt terwijl postgres lukt).
- **`success`** als geen enkele phase `failed` of `degraded` is.
## Volgorde tov bestaande `ops-db-backup.timer`
De bestaande `deploy/ops-agent/ops-db-backup.timer` draait om **02:00** en doet alleen `pg_dump ops_dashboard` naar `/srv/ops/backups/`. Deze nieuwe `server-backup.timer` draait om **03:30** en pickt die map mee in zijn restic-backup. Beide blijven naast elkaar bestaan.