#!/usr/bin/env bash # Donna — daily full backup (DB + configs + privileges) # Retention: 14 days. Atomic: archivo se renombra al final solo si todo salió bien. set -euo pipefail BACKUP_DIR="/var/backups/donna" ENV_FILE="/etc/donna/donna.env" RETENTION_DAYS=14 # shellcheck disable=SC1090 . <(grep -E '^(DB_NAME|DB_USER|DB_PASS|DB_HOST|DB_PORT)=' "$ENV_FILE") mkdir -p "$BACKUP_DIR" chmod 750 "$BACKUP_DIR" STAMP=$(date +%F_%H%M%S) TMP_DIR=$(mktemp -d -t donna-backup.XXXXXX) trap 'rm -rf "$TMP_DIR"' EXIT OUT_FINAL="$BACKUP_DIR/donna-$STAMP.tar.gz" OUT_TMP="$OUT_FINAL.tmp" # Layout dentro del tarball: # donna-$STAMP/ # ├── db/ # │ ├── schema-and-data.sql.gz ← DB completa de la app # │ └── grants.sql ← GRANTs para recrear el usuario donna_app # ├── etc/donna/donna.env # ├── etc/caddy/Caddyfile # └── etc/systemd/system/donna-backup.{service,timer} ROOT="$TMP_DIR/donna-$STAMP" mkdir -p "$ROOT/db" "$ROOT/etc/donna" "$ROOT/etc/caddy" "$ROOT/etc/systemd/system" # 1) Dump completo de la BD (schema + data + routines + triggers + events) MYSQL_PWD="$DB_PASS" mariadb-dump \ --host="${DB_HOST:-127.0.0.1}" \ --port="${DB_PORT:-3306}" \ --user="$DB_USER" \ --single-transaction \ --quick \ --routines \ --triggers \ --events \ --default-character-set=utf8mb4 \ "$DB_NAME" \ | gzip -9 > "$ROOT/db/schema-and-data.sql.gz" # 2) GRANTs del usuario de la app (para recrear permisos en un server nuevo) # Si corre como root con socket disponible usa el root del server, # si no, hace SHOW GRANTS como el propio usuario de la app. { if [ -S /run/mysqld/mysqld.sock ] && [ "$(id -u)" -eq 0 ]; then mariadb --batch --skip-column-names -e "SHOW GRANTS FOR '${DB_USER}'@'localhost';" 2>/dev/null \ | sed 's/$/;/' || true else MYSQL_PWD="$DB_PASS" mariadb --user="$DB_USER" --batch --skip-column-names \ -e "SHOW GRANTS;" 2>/dev/null | sed 's/$/;/' || true fi } > "$ROOT/db/grants.sql" # 3) Archivos de configuración fuera del repo cp -p /etc/donna/donna.env "$ROOT/etc/donna/donna.env" cp -p /etc/caddy/Caddyfile "$ROOT/etc/caddy/Caddyfile" cp -p /etc/systemd/system/donna-backup.service "$ROOT/etc/systemd/system/donna-backup.service" cp -p /etc/systemd/system/donna-backup.timer "$ROOT/etc/systemd/system/donna-backup.timer" # 4) Manifest (qué hay adentro + tamaños + checksum) { echo "Donna full backup — $(date -Is)" echo "Host: $(hostname -f 2>/dev/null || hostname)" echo "DB: $DB_NAME" echo echo "Contents:" ( cd "$ROOT" && find . -type f -exec ls -lh {} \; | awk '{print $5, $9}' ) echo echo "SHA-256:" ( cd "$ROOT" && find . -type f -exec sha256sum {} \; ) } > "$ROOT/MANIFEST.txt" # 5) Empaquetar y comprimir atómicamente tar -C "$TMP_DIR" -czf "$OUT_TMP" "donna-$STAMP" mv "$OUT_TMP" "$OUT_FINAL" chmod 640 "$OUT_FINAL" # 6) Retención find "$BACKUP_DIR" -type f -name 'donna-*.tar.gz' -mtime "+$RETENTION_DAYS" -delete # Limpieza de respaldos legacy (.sql.gz) más viejos que retención find "$BACKUP_DIR" -type f -name 'donna-*.sql.gz' -mtime "+$RETENTION_DAYS" -delete echo "[donna-backup] $(date -Is) wrote $OUT_FINAL ($(du -h "$OUT_FINAL" | cut -f1))"