fix(backup): respaldar TODO (DB + secrets + caddy + systemd) atómicamente
Bug: el script previo solo respaldaba la BD donna. /etc/donna/donna.env, /etc/caddy/Caddyfile y los systemd units quedaban sin respaldo. Además, si mariadb-dump fallaba a media corrida el .sql.gz parcial quedaba como si fuera válido (se vio hoy con un archivo de 20 bytes). Cambios: - Output ahora es un solo tar.gz con DB + GRANTs + configs + MANIFEST (sha256 de cada archivo incluido) - Atómico: tar se escribe a .tmp y se renombra solo al final - Incluye SHOW GRANTS para poder recrear el usuario donna_app en un server nuevo sin tener que conocer el password de root del MariaDB - Limpieza de archivos .sql.gz legacy también en la retención - README documenta el procedimiento de restore en server nuevo
This commit is contained in:
@@ -336,11 +336,37 @@ Colores por tipo de evento manual:
|
|||||||
|
|
||||||
## Backups
|
## Backups
|
||||||
|
|
||||||
`donna-backup.timer` ejecuta diariamente a las 03:00 (jitter ±5min) y vuelca `donna` comprimido a `/var/backups/donna/donna-YYYY-MM-DD_HHMMSS.sql.gz`. Retención 14 días.
|
`donna-backup.timer` ejecuta diariamente a las 03:00 (jitter ±5min) y genera `/var/backups/donna/donna-YYYY-MM-DD_HHMMSS.tar.gz`. Retención 14 días. Atómico: el `.tmp` se renombra al final solo si todo salió bien (no quedan archivos parciales).
|
||||||
|
|
||||||
Restaurar:
|
Contenido del tarball:
|
||||||
|
```
|
||||||
|
donna-YYYY-MM-DD_HHMMSS/
|
||||||
|
├── MANIFEST.txt ← inventario + tamaños + SHA-256 de cada archivo
|
||||||
|
├── db/
|
||||||
|
│ ├── schema-and-data.sql.gz ← mariadb-dump completo (schema + data + routines + triggers + events)
|
||||||
|
│ └── grants.sql ← SHOW GRANTS del usuario donna_app
|
||||||
|
└── etc/
|
||||||
|
├── donna/donna.env ← secrets (OAuth, DB pass, APP_SECRET)
|
||||||
|
├── caddy/Caddyfile ← config del Caddy local
|
||||||
|
└── systemd/system/
|
||||||
|
├── donna-backup.service
|
||||||
|
└── donna-backup.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
Restaurar en un server nuevo:
|
||||||
```bash
|
```bash
|
||||||
gunzip -c /var/backups/donna/donna-YYYY-MM-DD_HHMMSS.sql.gz | mariadb donna
|
TAR=/path/to/donna-YYYY-MM-DD_HHMMSS.tar.gz
|
||||||
|
tar -xzf "$TAR" -C /tmp && cd /tmp/donna-*
|
||||||
|
# 1. Configs
|
||||||
|
cp etc/donna/donna.env /etc/donna/donna.env && chmod 640 /etc/donna/donna.env
|
||||||
|
cp etc/caddy/Caddyfile /etc/caddy/Caddyfile && systemctl reload caddy
|
||||||
|
cp etc/systemd/system/* /etc/systemd/system/ && systemctl daemon-reload
|
||||||
|
# 2. DB (crea DB y usuario primero)
|
||||||
|
mariadb -e "CREATE DATABASE donna CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||||
|
mariadb < db/grants.sql # recrea el usuario y sus permisos
|
||||||
|
gunzip -c db/schema-and-data.sql.gz | mariadb donna
|
||||||
|
# 3. Verificar
|
||||||
|
sha256sum -c <(grep -E '^[a-f0-9]{64}' MANIFEST.txt) # checksums OK?
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+61
-5
@@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Donna — daily MariaDB backup with 14-day retention
|
# Donna — daily full backup (DB + configs + privileges)
|
||||||
|
# Retention: 14 days. Atomic: archivo se renombra al final solo si todo salió bien.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BACKUP_DIR="/var/backups/donna"
|
BACKUP_DIR="/var/backups/donna"
|
||||||
@@ -13,8 +14,25 @@ mkdir -p "$BACKUP_DIR"
|
|||||||
chmod 750 "$BACKUP_DIR"
|
chmod 750 "$BACKUP_DIR"
|
||||||
|
|
||||||
STAMP=$(date +%F_%H%M%S)
|
STAMP=$(date +%F_%H%M%S)
|
||||||
OUT="$BACKUP_DIR/donna-$STAMP.sql.gz"
|
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 \
|
MYSQL_PWD="$DB_PASS" mariadb-dump \
|
||||||
--host="${DB_HOST:-127.0.0.1}" \
|
--host="${DB_HOST:-127.0.0.1}" \
|
||||||
--port="${DB_PORT:-3306}" \
|
--port="${DB_PORT:-3306}" \
|
||||||
@@ -26,10 +44,48 @@ MYSQL_PWD="$DB_PASS" mariadb-dump \
|
|||||||
--events \
|
--events \
|
||||||
--default-character-set=utf8mb4 \
|
--default-character-set=utf8mb4 \
|
||||||
"$DB_NAME" \
|
"$DB_NAME" \
|
||||||
| gzip -9 > "$OUT"
|
| gzip -9 > "$ROOT/db/schema-and-data.sql.gz"
|
||||||
|
|
||||||
chmod 640 "$OUT"
|
# 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
|
find "$BACKUP_DIR" -type f -name 'donna-*.sql.gz' -mtime "+$RETENTION_DAYS" -delete
|
||||||
|
|
||||||
echo "[donna-backup] $(date -Is) wrote $OUT ($(du -h "$OUT" | cut -f1))"
|
echo "[donna-backup] $(date -Is) wrote $OUT_FINAL ($(du -h "$OUT_FINAL" | cut -f1))"
|
||||||
|
|||||||
Reference in New Issue
Block a user