diff --git a/deploy/ops-agent/ops-db-backup.service b/deploy/ops-agent/ops-db-backup.service new file mode 100644 index 0000000..492e0fb --- /dev/null +++ b/deploy/ops-agent/ops-db-backup.service @@ -0,0 +1,20 @@ +[Unit] +Description=Daily backup of ops_dashboard database +After=network.target ops-agent.service + +[Service] +Type=oneshot +User=ops-agent +Group=ops-agent +# Reads the shared secret and POSTs to ops-agent to trigger the backup flow. +# ops-agent must be running and backup_ops_db.yml must be installed in /etc/ops-agent/flows/. +ExecStart=/usr/bin/bash -c '\ + SECRET=$(cat /etc/ops-agent/secret); \ + curl -sf -X POST http://127.0.0.1:3099/agent/v1/flow \ + -H "Authorization: Bearer $SECRET" \ + -H "Content-Type: application/json" \ + -d "{\"flow_key\":\"backup_ops_db\"}" \ +' +StandardOutput=journal +StandardError=journal +SyslogIdentifier=ops-db-backup diff --git a/deploy/ops-agent/ops-db-backup.timer b/deploy/ops-agent/ops-db-backup.timer new file mode 100644 index 0000000..bb2c5ef --- /dev/null +++ b/deploy/ops-agent/ops-db-backup.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Daily backup of ops_dashboard database (timer) + +[Timer] +# Run every day at 02:00 local time. +OnCalendar=*-*-* 02:00:00 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/ops-agent/commands.yml.example b/ops-agent/commands.yml.example index e899e0e..4c7cd2f 100644 --- a/ops-agent/commands.yml.example +++ b/ops-agent/commands.yml.example @@ -203,3 +203,36 @@ commands: - -c - "code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 https://thuis.jp-visser.nl/api/products); echo \"HTTP $code\"; [ \"$code\" = \"200\" ] || [ \"$code\" = \"401\" ]" description: "Smoke test: /api/products must return 200 or 401" + + # ── Ops-dashboard database backup ──────────────────────────────────────── + + pg_dump_ops_db: + cmd: + - sh + - -c + - | + mkdir -p /srv/ops/backups + FNAME="/srv/ops/backups/ops_db_$(date +%Y%m%d_%H%M).dump" + docker exec postgres pg_dump -Fc ops_dashboard > "$FNAME" + echo "Backup written: $FNAME" + ls -lh "$FNAME" + description: "Dump ops_dashboard DB via docker exec postgres to /srv/ops/backups/" + + list_ops_backups: + cmd: + - sh + - -c + - "find /srv/ops/backups -maxdepth 1 -name '*.dump' -printf '%f\\t%s\\n' 2>/dev/null | sort -r || true" + description: "List ops_dashboard backup files (filename TAB size_bytes, newest-first)" + + cleanup_ops_backups: + cmd: + - find + - /srv/ops/backups + - -name + - "*.dump" + - -mtime + - "+30" + - -delete + - -print + description: "Delete ops_dashboard backup files older than 30 days" diff --git a/ops-agent/flows.example/backup_ops_db.yml b/ops-agent/flows.example/backup_ops_db.yml new file mode 100644 index 0000000..f0c654a --- /dev/null +++ b/ops-agent/flows.example/backup_ops_db.yml @@ -0,0 +1,22 @@ +# Backup the ops_dashboard database. +# Copy to /etc/ops-agent/flows/backup_ops_db.yml on the host. +# +# Prerequisites: +# - ops-agent user must be in the docker group (to run docker exec) +# - /srv/ops/backups/ directory or its parent must be writable by ops-agent +# +# Steps: +# 1. Dump ops_dashboard via pg_dump inside the postgres container +# 2. Remove backup files older than 30 days (retention policy) +# +# Run on a schedule via ops-db-backup.timer (see deploy/ops-agent/). +# Or trigger manually via the Ops Dashboard → Settings → Backups. + +name: Backup Ops DB +description: Dump ops_dashboard database and apply 30-day retention policy +steps: + - command_key: pg_dump_ops_db + on_failure: abort + + - command_key: cleanup_ops_backups + on_failure: continue