Ops-dashboard/deploy/server-backup/wrappers/restic-snapshots.sh
Madhura68 ab87c0fada feat(server-backup): restic dual-repo backup (NAS + B2) with dashboard UI
Adds a server-wide backup capability beyond the existing ops_dashboard
pg_dump flow:

- Daily systemd timer (03:30) runs pg_dumpall + Forgejo dump, then restic
  to a local NAS repo and an offsite Backblaze B2 repo with Object Lock.
  Phase-based script with single-instance flock, structured statusfile,
  systemd hardening, and live-datadir excludes (Postgres / Forgejo) so
  the dumps stay authoritative.
- Ops-agent gets nine new read-only/trigger commands (snapshots, stats,
  status, logs, plus two triggers) backed by sudoers-whitelisted wrapper
  scripts that source /etc/restic-backup.env so the agent never sees the
  restic password or B2 keys.
- Two new flows (server_backup_full, server_backup_restore_test) drive
  the dashboard's "Backup now" and "Restore test" buttons.
- /settings/backups gains a Server backup section with overall + per-phase
  status, NAS / B2 snapshot tables, restore-size / raw-data / dedup-ratio
  stats, and the last restore-test result. The existing pg_dump section
  is preserved unchanged.
- Runbook docs/runbooks/server-backup.md follows the tailscale-setup
  pattern (plan + addendum) and covers B2 Object Lock + scoped keys,
  Forgejo subplan with isolated restore-test stack, the off-server
  maintenance flow for B2 prune, and the integrity-check schedule.

Code-only change — installation on scrum4me-srv follows the runbook.

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

39 lines
1.1 KiB
Bash

#!/usr/bin/env bash
# List recent restic snapshots from a labelled repo. Output: JSON array.
# Usage: restic-snapshots.sh nas|b2
set -uo pipefail
LABEL="${1:-}"
if [ "$LABEL" != "nas" ] && [ "$LABEL" != "b2" ]; then
echo '{"error":"label must be nas or b2"}' >&2
exit 2
fi
# Load env (idempotent — systemd already loaded it for service contexts).
if [ -z "${RESTIC_REPO_NAS:-}" ] && [ -r /etc/restic-backup.env ]; then
set -a; . /etc/restic-backup.env; set +a
fi
case "$LABEL" in
nas) REPO="${RESTIC_REPO_NAS:?RESTIC_REPO_NAS not set}" ;;
b2) REPO="${RESTIC_REPO_B2:?RESTIC_REPO_B2 not set}" ;;
esac
export RESTIC_PASSWORD_FILE="${RESTIC_PASSWORD_FILE:-/etc/restic-backup.password}"
# Show last 30 snapshots, newest first, with the fields the UI needs.
restic -r "$REPO" snapshots --json 2>/dev/null \
| jq --arg repo "$LABEL" '
sort_by(.time) | reverse | .[0:30]
| map({
id: .id,
short_id: (.short_id // (.id[0:8])),
time: .time,
hostname: .hostname,
tags: (.tags // []),
paths: (.paths // []),
summary: (.summary // null),
repo: $repo
})
'