From 58475d9cba7fef62b45fa7d5610475e8ffb87f55 Mon Sep 17 00:00:00 2001 From: Julian Tabel Date: Tue, 10 Feb 2026 11:55:27 +0100 Subject: [PATCH] Add database backup script with daily cron and 7-day retention pg_dump-based backup script deployed alongside compose file. Deploy script now installs a daily cron job (03:00) on Unraid automatically. Co-Authored-By: Claude Opus 4.6 --- ...-tracker-48ds--database-backup-strategy.md | 5 +-- ...locke-tracker-ahza--deployment-strategy.md | 2 +- backup.sh | 33 +++++++++++++++++++ deploy.sh | 19 +++++++++-- 4 files changed, 54 insertions(+), 5 deletions(-) create mode 100755 backup.sh diff --git a/.beans/nuzlocke-tracker-48ds--database-backup-strategy.md b/.beans/nuzlocke-tracker-48ds--database-backup-strategy.md index c84fd90..b9f122f 100644 --- a/.beans/nuzlocke-tracker-48ds--database-backup-strategy.md +++ b/.beans/nuzlocke-tracker-48ds--database-backup-strategy.md @@ -1,10 +1,11 @@ --- # nuzlocke-tracker-48ds title: Database backup strategy -status: todo +status: completed type: task +priority: normal created_at: 2026-02-09T15:30:55Z -updated_at: 2026-02-09T15:30:55Z +updated_at: 2026-02-10T10:55:15Z parent: nuzlocke-tracker-ahza --- diff --git a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md index caf6b87..36b5766 100644 --- a/.beans/nuzlocke-tracker-ahza--deployment-strategy.md +++ b/.beans/nuzlocke-tracker-ahza--deployment-strategy.md @@ -54,5 +54,5 @@ Define and implement a deployment strategy for running the nuzlocke-tracker in p - [x] **Configure Nginx Proxy Manager** — add proxy host entries for Gitea and the nuzlocke-tracker frontend/API on the appropriate ports - [x] **Environment & secrets management** — deploy script auto-generates `.env` with `POSTGRES_PASSWORD` on Unraid if missing; file lives at `/mnt/user/appdata/nuzlocke-tracker/.env` - [ ] **Implement Gitea Actions CI/CD pipeline** — set up Gitea Actions runner on Unraid, create CI workflow (lint/test on `develop`) and deploy workflow (build/push/deploy on `main`); uses GitHub Actions-compatible syntax for portability -- [ ] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL data (e.g., cron + `pg_dump` script on Unraid) +- [x] **Database backup strategy** — set up a simple scheduled backup for the PostgreSQL data (e.g., cron + `pg_dump` script on Unraid) - [ ] **Document the deployment workflow** — README or docs covering how to deploy, redeploy, rollback, and manage the production instance \ No newline at end of file diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..d357281 --- /dev/null +++ b/backup.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ── Configuration ────────────────────────────────────────────── +DEPLOY_DIR="/mnt/user/appdata/nuzlocke-tracker" +BACKUP_DIR="${DEPLOY_DIR}/backups" +RETENTION_DAYS=7 +DB_SERVICE="db" +DB_NAME="nuzlocke" +DB_USER="postgres" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +BACKUP_FILE="${BACKUP_DIR}/nuzlocke-${TIMESTAMP}.sql.gz" + +# ── Create backup directory ─────────────────────────────────── +mkdir -p "$BACKUP_DIR" + +# ── Dump database ───────────────────────────────────────────── +cd "$DEPLOY_DIR" +docker compose exec -T "$DB_SERVICE" pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE" + +echo "Backup created: ${BACKUP_FILE}" + +# ── Rotate old backups ──────────────────────────────────────── +find "$BACKUP_DIR" -name "nuzlocke-*.sql.gz" -mtime +${RETENTION_DAYS} -delete + +REMAINING=$(find "$BACKUP_DIR" -name "nuzlocke-*.sql.gz" | wc -l) +echo "Backups retained: ${REMAINING}" + +# ── Restore procedure ──────────────────────────────────────── +# To restore from a backup: +# cd /mnt/user/appdata/nuzlocke-tracker +# gunzip -c backups/nuzlocke-YYYYMMDD-HHMMSS.sql.gz | \ +# docker compose exec -T db psql -U postgres nuzlocke diff --git a/deploy.sh b/deploy.sh index 44c46bc..f9b3d7f 100755 --- a/deploy.sh +++ b/deploy.sh @@ -55,10 +55,13 @@ done info "All images built and pushed." # ── Sync compose file to Unraid ────────────────────────────────── -info "Copying docker-compose.prod.yml to Unraid..." +info "Copying docker-compose.prod.yml and backup.sh to Unraid..." scp docker-compose.prod.yml "${UNRAID_SSH}:${UNRAID_DEPLOY_DIR}/docker-compose.yml" \ || error "Failed to copy compose file to Unraid." -info "Compose file synced." +scp backup.sh "${UNRAID_SSH}:${UNRAID_DEPLOY_DIR}/backup.sh" \ + || error "Failed to copy backup script to Unraid." +ssh "${UNRAID_SSH}" "chmod +x '${UNRAID_DEPLOY_DIR}/backup.sh'" +info "Compose file and backup script synced." # ── Ensure .env with Postgres password exists ──────────────────── info "Checking for .env on Unraid..." @@ -72,6 +75,18 @@ ssh "${UNRAID_SSH}" " fi " || error "Failed to check/create .env on Unraid." +# ── Ensure daily backup cron job exists ─────────────────────────── +info "Checking for backup cron job on Unraid..." +CRON_CMD="0 3 * * * ${UNRAID_DEPLOY_DIR}/backup.sh >> ${UNRAID_DEPLOY_DIR}/backups/cron.log 2>&1" +ssh "${UNRAID_SSH}" " + if ! crontab -l 2>/dev/null | grep -qF '${UNRAID_DEPLOY_DIR}/backup.sh'; then + (crontab -l 2>/dev/null; echo '${CRON_CMD}') | crontab - + echo 'Cron job installed (daily at 03:00)' + else + echo 'Cron job already exists, skipping' + fi +" || error "Failed to set up backup cron job on Unraid." + # ── Pull images and (re)start on Unraid ────────────────────────── info "Pulling images and starting containers on Unraid..." ssh "${UNRAID_SSH}" "cd '${UNRAID_DEPLOY_DIR}' && docker compose pull && docker compose up -d" \