feat: add NAS backup, verification, and DR scripts
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled
This commit is contained in:
@@ -1,287 +1,356 @@
|
||||
#!/bin/bash
|
||||
# ============================================
|
||||
# Wordly.art - PostgreSQL Backup to NAS
|
||||
# ============================================
|
||||
# CRON: Run daily at 03:00
|
||||
# 0 3 * * * /opt/wordly/scripts/backup-to-nas.sh >> /var/log/wordly-backup.log 2>&1
|
||||
# ==============================================================================
|
||||
# Wordly.art - PostgreSQL Backup vers NAS Synology via SSH/rsync
|
||||
# ==============================================================================
|
||||
# Sauvegarde la base PostgreSQL et l'archive DR sur le NAS via SSH/rsync.
|
||||
# Pas de montage CIFS — rsync SSH direct sur /volume1/backups/wordly.
|
||||
#
|
||||
# Usage:
|
||||
# ./backup-to-nas.sh # Default: daily backup
|
||||
# ./backup-to-nas.sh --full # Full backup with upload cleanup
|
||||
# ./backup-to-nas.sh --restore FILE # Restore from specific backup
|
||||
# ============================================
|
||||
# CRON (installé par install-crontab.sh) :
|
||||
# 0 */6 * * * bash /opt/wordly/scripts/backup-to-nas.sh >> /var/log/wordly-backup.log 2>&1
|
||||
#
|
||||
# Usage :
|
||||
# ./backup-to-nas.sh # Backup complet → NAS
|
||||
# ./backup-to-nas.sh --full # Identique (alias explicite)
|
||||
# ./backup-to-nas.sh --list # Lister les archives disponibles sur le NAS
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ===========================================
|
||||
# CONFIGURATION - MODIFY THESE VALUES
|
||||
# ===========================================
|
||||
# NAS settings (SMB/CIFS or NFS mount point)
|
||||
NAS_BACKUP_DIR="/mnt/nas-backups/wordly"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Docker container name for PostgreSQL
|
||||
POSTGRES_CONTAINER="wordly-postgres"
|
||||
POSTGRES_USER="translate"
|
||||
POSTGRES_DB="translate_db"
|
||||
POSTGRES_PASSWORD="yLLgkEvt6mvzGDdoqtQvI1vEgMmR-W75ZTPW5StaIAU"
|
||||
# ==============================================================================
|
||||
# CHARGER LE .env
|
||||
# ==============================================================================
|
||||
ENV_FILE="${PROJECT_ROOT}/.env"
|
||||
if [ -f "${ENV_FILE}" ]; then
|
||||
set -a
|
||||
source "${ENV_FILE}"
|
||||
set +a
|
||||
else
|
||||
echo "ERROR: .env introuvable : ${ENV_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup retention
|
||||
DAILY_RETENTION=7 # Keep 7 daily backups
|
||||
WEEKLY_RETENTION=4 # Keep 4 weekly backups
|
||||
MONTHLY_RETENTION=6 # Keep 6 monthly backups
|
||||
# ==============================================================================
|
||||
# CONFIGURATION (depuis .env)
|
||||
# ==============================================================================
|
||||
|
||||
# Notification (optional - leave empty to disable)
|
||||
NOTIFICATION_WEBHOOK="" # Slack/Discord webhook URL
|
||||
# NAS SSH
|
||||
NAS_HOST="${NAS_HOST:-192.168.1.146}"
|
||||
NAS_USER="${NAS_USER:-wordly-backup}"
|
||||
NAS_PATH="${NAS_PATH:-/volume1/backups/wordly}"
|
||||
NAS_SSH_PORT="${NAS_SSH_PORT:-22}"
|
||||
NAS_SSH_KEY="${NAS_SSH_KEY:-/root/.ssh/wordly_nas_key}"
|
||||
|
||||
# ===========================================
|
||||
# PostgreSQL
|
||||
POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-wordly-postgres}"
|
||||
POSTGRES_USER="${POSTGRES_USER:-translate}"
|
||||
POSTGRES_DB="${POSTGRES_DB:-translate_db}"
|
||||
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:?POSTGRES_PASSWORD doit être défini dans .env}"
|
||||
|
||||
# Rétention sur le NAS (nombre d'archives à garder)
|
||||
DAILY_RETENTION=${DAILY_RETENTION:-7}
|
||||
WEEKLY_RETENTION=${WEEKLY_RETENTION:-4}
|
||||
MONTHLY_RETENTION=${MONTHLY_RETENTION:-6}
|
||||
|
||||
# Telegram
|
||||
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
||||
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
|
||||
|
||||
# ==============================================================================
|
||||
# INTERNALS
|
||||
# ===========================================
|
||||
# ==============================================================================
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
DATE_ONLY=$(date +"%Y-%m-%d")
|
||||
DAY_OF_WEEK=$(date +"%u") # 1=Mon, 7=Sun
|
||||
DAY_OF_WEEK=$(date +"%u") # 1=Lun, 7=Dim
|
||||
DAY_OF_MONTH=$(date +"%d")
|
||||
BACKUP_NAME="wordly_db_${TIMESTAMP}.sql.gz"
|
||||
BACKUP_PATH="${NAS_BACKUP_DIR}/${BACKUP_NAME}"
|
||||
LOG_PREFIX="[Wordly Backup ${TIMESTAMP}]"
|
||||
SNAPSHOT_NAME="wordly_dr_${TIMESTAMP}.tar.gz"
|
||||
LOCAL_TMP="/tmp/wordly_backup_${TIMESTAMP}"
|
||||
SSH_CMD="ssh -i ${NAS_SSH_KEY} -p ${NAS_SSH_PORT} -o BatchMode=yes -o ConnectTimeout=10"
|
||||
RSYNC_CMD="rsync -az -e 'ssh -i ${NAS_SSH_KEY} -p ${NAS_SSH_PORT} -o BatchMode=yes -o ConnectTimeout=10'"
|
||||
|
||||
# Colors for terminal output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
LOG_PREFIX="[Backup ${TIMESTAMP}]"
|
||||
|
||||
# ===========================================
|
||||
# FUNCTIONS
|
||||
# ===========================================
|
||||
log() { echo "${LOG_PREFIX} $1"; }
|
||||
log_success() { echo -e "${LOG_PREFIX} ${GREEN}✅ $1${NC}"; }
|
||||
log_error() { echo -e "${LOG_PREFIX} ${RED}❌ ERROR: $1${NC}"; }
|
||||
log_warning() { echo -e "${LOG_PREFIX} ${YELLOW}⚠️ $1${NC}"; }
|
||||
|
||||
log() {
|
||||
echo "${LOG_PREFIX} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${LOG_PREFIX} ${GREEN}$1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${LOG_PREFIX} ${RED}ERROR: $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${LOG_PREFIX} ${YELLOW}WARNING: $1${NC}"
|
||||
}
|
||||
|
||||
send_notification() {
|
||||
# ==============================================================================
|
||||
# TELEGRAM
|
||||
# ==============================================================================
|
||||
send_telegram() {
|
||||
local message="$1"
|
||||
if [ -n "${NOTIFICATION_WEBHOOK}" ]; then
|
||||
curl -s -X POST "${NOTIFICATION_WEBHOOK}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"text\": \"${message}\"}" > /dev/null 2>&1 || true
|
||||
if [ -n "${TELEGRAM_BOT_TOKEN}" ] && [ -n "${TELEGRAM_CHAT_ID}" ]; then
|
||||
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${TELEGRAM_CHAT_ID}" \
|
||||
-d "text=${message}" \
|
||||
-d "parse_mode=Markdown" \
|
||||
>/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# PRÉREQUIS
|
||||
# ==============================================================================
|
||||
check_prerequisites() {
|
||||
# Check NAS mount
|
||||
if [ ! -d "${NAS_BACKUP_DIR}" ]; then
|
||||
log_error "NAS backup directory not found: ${NAS_BACKUP_DIR}"
|
||||
log "Attempting to mount NAS..."
|
||||
log "Vérification des prérequis..."
|
||||
|
||||
# Try to mount if configured via /etc/fstab
|
||||
mount "${NAS_BACKUP_DIR}" 2>/dev/null || true
|
||||
|
||||
if [ ! -d "${NAS_BACKUP_DIR}" ]; then
|
||||
log_error "Cannot mount NAS. Aborting."
|
||||
send_notification "Wordly Backup FAILED: NAS not mounted at ${NAS_BACKUP_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check Docker
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "${POSTGRES_CONTAINER}"; then
|
||||
log_error "PostgreSQL container '${POSTGRES_CONTAINER}' is not running."
|
||||
send_notification "Wordly Backup FAILED: PostgreSQL container not running"
|
||||
# Clé SSH
|
||||
if [ ! -f "${NAS_SSH_KEY}" ]; then
|
||||
log_error "Clé SSH introuvable : ${NAS_SSH_KEY}"
|
||||
log_error "Lancez d'abord : sudo bash scripts/setup-nas.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Prerequisites OK"
|
||||
}
|
||||
|
||||
create_backup() {
|
||||
log "Starting backup of '${POSTGRES_DB}'..."
|
||||
|
||||
# Create backup directory structure
|
||||
mkdir -p "${NAS_BACKUP_DIR}/daily"
|
||||
mkdir -p "${NAS_BACKUP_DIR}/weekly"
|
||||
mkdir -p "${NAS_BACKUP_DIR}/monthly"
|
||||
|
||||
# Run pg_dump inside Docker container
|
||||
docker exec "${POSTGRES_CONTAINER}" pg_dump \
|
||||
-U "${POSTGRES_USER}" \
|
||||
-d "${POSTGRES_DB}" \
|
||||
--format=custom \
|
||||
--compress=9 \
|
||||
--no-owner \
|
||||
--no-acl \
|
||||
2>/dev/null | gzip > "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}"
|
||||
|
||||
local backup_size=$(du -h "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}" | cut -f1)
|
||||
|
||||
if [ -f "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}" ]; then
|
||||
log_success "Backup created: ${BACKUP_NAME} (${backup_size})"
|
||||
|
||||
# Copy to weekly/monthly if applicable
|
||||
if [ "${DAY_OF_WEEK}" = "7" ]; then
|
||||
cp "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}" "${NAS_BACKUP_DIR}/weekly/"
|
||||
log "Weekly backup copied"
|
||||
fi
|
||||
|
||||
if [ "${DAY_OF_MONTH}" = "01" ]; then
|
||||
cp "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}" "${NAS_BACKUP_DIR}/monthly/"
|
||||
log "Monthly backup copied"
|
||||
fi
|
||||
|
||||
send_notification "Wordly Backup SUCCESS: ${BACKUP_NAME} (${backup_size})"
|
||||
else
|
||||
log_error "Backup file was not created!"
|
||||
send_notification "Wordly Backup FAILED: pg_dump produced no output"
|
||||
# Connectivité SSH vers le NAS
|
||||
if ! ${SSH_CMD} "${NAS_USER}@${NAS_HOST}" "echo OK" >/dev/null 2>&1; then
|
||||
log_error "Impossible de se connecter au NAS ${NAS_HOST} via SSH."
|
||||
log_error "Vérifiez : ssh -i ${NAS_SSH_KEY} ${NAS_USER}@${NAS_HOST}"
|
||||
send_telegram "🚨 *Wordly Backup ÉCHOUÉ*
|
||||
NAS inaccessible : ${NAS_HOST}
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
exit 1
|
||||
fi
|
||||
log_success "NAS SSH : OK"
|
||||
|
||||
# Docker + container PostgreSQL
|
||||
if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${POSTGRES_CONTAINER}$"; then
|
||||
log_error "Container PostgreSQL '${POSTGRES_CONTAINER}' n'est pas en cours d'exécution !"
|
||||
send_telegram "🚨 *Wordly Backup ÉCHOUÉ*
|
||||
PostgreSQL container non trouvé
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
exit 1
|
||||
fi
|
||||
log_success "PostgreSQL container : OK"
|
||||
}
|
||||
|
||||
cleanup_old_backups() {
|
||||
log "Cleaning up old backups..."
|
||||
# ==============================================================================
|
||||
# BACKUP POSTGRESQL
|
||||
# ==============================================================================
|
||||
backup_postgres() {
|
||||
log "Dump PostgreSQL de '${POSTGRES_DB}'..."
|
||||
mkdir -p "${LOCAL_TMP}"
|
||||
|
||||
# Daily: keep last N days
|
||||
local daily_count=$(ls -1 "${NAS_BACKUP_DIR}/daily/" 2>/dev/null | wc -l)
|
||||
if [ "${daily_count}" -gt "${DAILY_RETENTION}" ]; then
|
||||
ls -1t "${NAS_BACKUP_DIR}/daily/" | tail -n +$((DAILY_RETENTION + 1)) | while read -r f; do
|
||||
rm -f "${NAS_BACKUP_DIR}/daily/${f}"
|
||||
log " Deleted daily: ${f}"
|
||||
done
|
||||
fi
|
||||
local dump_file="${LOCAL_TMP}/db_${TIMESTAMP}.dump.gz"
|
||||
|
||||
# Weekly: keep last N weeks
|
||||
local weekly_count=$(ls -1 "${NAS_BACKUP_DIR}/weekly/" 2>/dev/null | wc -l)
|
||||
if [ "${weekly_count}" -gt "${WEEKLY_RETENTION}" ]; then
|
||||
ls -1t "${NAS_BACKUP_DIR}/weekly/" | tail -n +$((WEEKLY_RETENTION + 1)) | while read -r f; do
|
||||
rm -f "${NAS_BACKUP_DIR}/weekly/${f}"
|
||||
log " Deleted weekly: ${f}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Monthly: keep last N months
|
||||
local monthly_count=$(ls -1 "${NAS_BACKUP_DIR}/monthly/" 2>/dev/null | wc -l)
|
||||
if [ "${monthly_count}" -gt "${MONTHLY_RETENTION}" ]; then
|
||||
ls -1t "${NAS_BACKUP_DIR}/monthly/" | tail -n +$((MONTHLY_RETENTION + 1)) | while read -r f; do
|
||||
rm -f "${NAS_BACKUP_DIR}/monthly/${f}"
|
||||
log " Deleted monthly: ${f}"
|
||||
done
|
||||
fi
|
||||
|
||||
log_success "Cleanup done"
|
||||
}
|
||||
|
||||
verify_backup() {
|
||||
log "Verifying backup integrity..."
|
||||
|
||||
if gzip -t "${NAS_BACKUP_DIR}/daily/${BACKUP_NAME}" 2>/dev/null; then
|
||||
log_success "Backup integrity OK"
|
||||
else
|
||||
log_error "Backup integrity check FAILED!"
|
||||
send_notification "Wordly Backup WARNING: Integrity check failed for ${BACKUP_NAME}"
|
||||
# Don't delete - let admin investigate
|
||||
fi
|
||||
}
|
||||
|
||||
restore_backup() {
|
||||
local backup_file="$1"
|
||||
|
||||
if [ -z "${backup_file}" ]; then
|
||||
log_error "Usage: $0 --restore <backup_file>"
|
||||
echo ""
|
||||
echo "Available backups:"
|
||||
echo "=== Daily ==="
|
||||
ls -lht "${NAS_BACKUP_DIR}/daily/" 2>/dev/null || echo " (none)"
|
||||
echo "=== Weekly ==="
|
||||
ls -lht "${NAS_BACKUP_DIR}/weekly/" 2>/dev/null || echo " (none)"
|
||||
echo "=== Monthly ==="
|
||||
ls -lht "${NAS_BACKUP_DIR}/monthly/" 2>/dev/null || echo " (none)"
|
||||
if ! docker exec \
|
||||
-e PGPASSWORD="${POSTGRES_PASSWORD}" \
|
||||
"${POSTGRES_CONTAINER}" \
|
||||
pg_dump \
|
||||
-U "${POSTGRES_USER}" \
|
||||
-d "${POSTGRES_DB}" \
|
||||
--format=custom \
|
||||
--no-owner \
|
||||
--no-acl \
|
||||
2>/dev/null | gzip > "${dump_file}"; then
|
||||
log_error "pg_dump a échoué !"
|
||||
send_telegram "🚨 *Wordly Backup ÉCHOUÉ*
|
||||
pg_dump error sur ${POSTGRES_DB}
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
rm -rf "${LOCAL_TMP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Find the file
|
||||
local full_path=""
|
||||
for dir in daily weekly monthly; do
|
||||
if [ -f "${NAS_BACKUP_DIR}/${dir}/${backup_file}" ]; then
|
||||
full_path="${NAS_BACKUP_DIR}/${dir}/${backup_file}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Vérification taille
|
||||
local size_bytes
|
||||
size_bytes=$(stat -c %s "${dump_file}" 2>/dev/null || stat -f %z "${dump_file}")
|
||||
local min_bytes=$((1024 * 1024)) # 1MB minimum
|
||||
|
||||
if [ -z "${full_path}" ]; then
|
||||
log_error "Backup file not found: ${backup_file}"
|
||||
if [ "${size_bytes}" -lt "${min_bytes}" ]; then
|
||||
log_error "Dump trop petit ($(numfmt --to=iec ${size_bytes})) — base de données vide ?"
|
||||
send_telegram "🚨 *Wordly Backup ÉCHOUÉ*
|
||||
Dump PostgreSQL trop petit : $(numfmt --to=iec ${size_bytes})
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
rm -rf "${LOCAL_TMP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_warning "RESTORE MODE - This will OVERWRITE the current database!"
|
||||
echo " File: ${full_path}"
|
||||
echo " Database: ${POSTGRES_DB}"
|
||||
echo ""
|
||||
read -p "Are you sure? Type 'YES' to confirm: " confirm
|
||||
|
||||
if [ "${confirm}" != "YES" ]; then
|
||||
log "Restore cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Restoring from ${full_path}..."
|
||||
|
||||
# Create a safety backup first
|
||||
log "Creating safety backup before restore..."
|
||||
SAFETY_NAME="wordly_db_pre_restore_${TIMESTAMP}.sql.gz"
|
||||
docker exec "${POSTGRES_CONTAINER}" pg_dump \
|
||||
-U "${POSTGRES_USER}" \
|
||||
-d "${POSTGRES_DB}" \
|
||||
--format=custom \
|
||||
--compress=9 \
|
||||
2>/dev/null | gzip > "${NAS_BACKUP_DIR}/daily/${SAFETY_NAME}"
|
||||
log "Safety backup: ${SAFETY_NAME}"
|
||||
|
||||
# Restore
|
||||
gunzip -c "${full_path}" | docker exec -i "${POSTGRES_CONTAINER}" \
|
||||
pg_restore \
|
||||
-U "${POSTGRES_USER}" \
|
||||
-d "${POSTGRES_DB}" \
|
||||
--clean \
|
||||
--if-exists \
|
||||
--no-owner \
|
||||
--no-acl \
|
||||
2>/dev/null || true
|
||||
|
||||
log_success "Restore completed!"
|
||||
log_warning "Restart backend: docker restart wordly-backend"
|
||||
log_success "Dump PostgreSQL : $(numfmt --to=iec ${size_bytes})"
|
||||
echo "${dump_file}"
|
||||
}
|
||||
|
||||
# ===========================================
|
||||
# ==============================================================================
|
||||
# CRÉER L'ARCHIVE DR (dump + .env + docker-compose + configs)
|
||||
# ==============================================================================
|
||||
create_dr_archive() {
|
||||
local dump_file="$1"
|
||||
log "Construction de l'archive DR..."
|
||||
|
||||
# Copier les fichiers de config
|
||||
[ -f "${PROJECT_ROOT}/.env" ] && cp "${PROJECT_ROOT}/.env" "${LOCAL_TMP}/.env.production"
|
||||
[ -f "${PROJECT_ROOT}/docker-compose.yml" ] && cp "${PROJECT_ROOT}/docker-compose.yml" "${LOCAL_TMP}/"
|
||||
[ -d "${PROJECT_ROOT}/docker" ] && cp -r "${PROJECT_ROOT}/docker" "${LOCAL_TMP}/"
|
||||
|
||||
# Compresser
|
||||
local archive_path="/tmp/${SNAPSHOT_NAME}"
|
||||
tar -czf "${archive_path}" -C "${LOCAL_TMP}" .
|
||||
rm -rf "${LOCAL_TMP}"
|
||||
|
||||
# Vérification intégrité
|
||||
if ! gzip -t "${archive_path}" 2>/dev/null; then
|
||||
log_error "Archive DR corrompue !"
|
||||
rm -f "${archive_path}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local size
|
||||
size=$(du -h "${archive_path}" | cut -f1)
|
||||
log_success "Archive DR créée : ${SNAPSHOT_NAME} (${size})"
|
||||
echo "${archive_path}|${size}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# ENVOYER SUR LE NAS VIA SCP/rsync SSH
|
||||
# ==============================================================================
|
||||
push_to_nas() {
|
||||
local archive_path="$1"
|
||||
local size="$2"
|
||||
|
||||
log "Transfert vers le NAS via rsync SSH..."
|
||||
log " Source : ${archive_path}"
|
||||
log " Dest : ${NAS_USER}@${NAS_HOST}:${NAS_PATH}/snapshots/${SNAPSHOT_NAME}"
|
||||
|
||||
# Dossier quotidien/hebdo/mensuel sur le NAS
|
||||
local nas_dest="${NAS_PATH}/snapshots"
|
||||
|
||||
# Transfer principal
|
||||
if ! rsync -az \
|
||||
-e "ssh -i ${NAS_SSH_KEY} -p ${NAS_SSH_PORT} -o BatchMode=yes -o ConnectTimeout=30" \
|
||||
"${archive_path}" \
|
||||
"${NAS_USER}@${NAS_HOST}:${nas_dest}/${SNAPSHOT_NAME}"; then
|
||||
log_error "rsync vers le NAS a échoué !"
|
||||
send_telegram "🚨 *Wordly Backup ÉCHOUÉ*
|
||||
rsync SSH vers ${NAS_HOST} a échoué
|
||||
Fichier local conservé : ${archive_path}
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
# Garder le fichier local comme fallback
|
||||
mkdir -p "${PROJECT_ROOT}/backups/emergency"
|
||||
mv "${archive_path}" "${PROJECT_ROOT}/backups/emergency/${SNAPSHOT_NAME}"
|
||||
log_warning "Archive conservée localement : ${PROJECT_ROOT}/backups/emergency/${SNAPSHOT_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Archive transférée sur le NAS : ${nas_dest}/${SNAPSHOT_NAME}"
|
||||
|
||||
# Copie hebdomadaire (dimanche)
|
||||
if [ "${DAY_OF_WEEK}" = "7" ]; then
|
||||
${SSH_CMD} "${NAS_USER}@${NAS_HOST}" \
|
||||
"cp ${nas_dest}/${SNAPSHOT_NAME} ${NAS_PATH}/snapshots/weekly_${SNAPSHOT_NAME}" 2>/dev/null || true
|
||||
log "Archive hebdomadaire copiée."
|
||||
fi
|
||||
|
||||
# Copie mensuelle (1er du mois)
|
||||
if [ "${DAY_OF_MONTH}" = "01" ]; then
|
||||
${SSH_CMD} "${NAS_USER}@${NAS_HOST}" \
|
||||
"cp ${nas_dest}/${SNAPSHOT_NAME} ${NAS_PATH}/snapshots/monthly_${SNAPSHOT_NAME}" 2>/dev/null || true
|
||||
log "Archive mensuelle copiée."
|
||||
fi
|
||||
|
||||
# Nettoyage local
|
||||
rm -f "${archive_path}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# ROTATION DES ARCHIVES SUR LE NAS
|
||||
# ==============================================================================
|
||||
cleanup_nas() {
|
||||
log "Rotation des archives sur le NAS (conservation : ${DAILY_RETENTION} jours)..."
|
||||
|
||||
# Supprimer les archives wordly_dr_* plus vieilles que DAILY_RETENTION
|
||||
${SSH_CMD} "${NAS_USER}@${NAS_HOST}" \
|
||||
"find ${NAS_PATH}/snapshots -name 'wordly_dr_*.tar.gz' -mtime +${DAILY_RETENTION} -delete 2>/dev/null; \
|
||||
find ${NAS_PATH}/snapshots -name 'weekly_*.tar.gz' | sort -r | tail -n +$((WEEKLY_RETENTION + 1)) | xargs rm -f 2>/dev/null; \
|
||||
find ${NAS_PATH}/snapshots -name 'monthly_*.tar.gz' | sort -r | tail -n +$((MONTHLY_RETENTION + 1)) | xargs rm -f 2>/dev/null; \
|
||||
echo OK" | grep -q "OK"
|
||||
|
||||
log_success "Rotation des archives OK"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# SYNCHRONISER LES SCRIPTS SUR LE NAS (pour restauration depuis .98)
|
||||
# ==============================================================================
|
||||
sync_scripts() {
|
||||
rsync -az \
|
||||
-e "ssh -i ${NAS_SSH_KEY} -p ${NAS_SSH_PORT} -o BatchMode=yes" \
|
||||
--exclude="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
"${SCRIPT_DIR}/" \
|
||||
"${NAS_USER}@${NAS_HOST}:${NAS_PATH}/scripts/" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# LISTER LES ARCHIVES DISPONIBLES
|
||||
# ==============================================================================
|
||||
list_archives() {
|
||||
log "Archives disponibles sur le NAS :"
|
||||
${SSH_CMD} "${NAS_USER}@${NAS_HOST}" \
|
||||
"ls -lht ${NAS_PATH}/snapshots/wordly_dr_*.tar.gz 2>/dev/null || echo '(aucune archive)'"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ===========================================
|
||||
# ==============================================================================
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
--list)
|
||||
ENV_FILE="${PROJECT_ROOT}/.env"
|
||||
[ -f "${ENV_FILE}" ] && { set -a; source "${ENV_FILE}"; set +a; }
|
||||
list_archives
|
||||
exit 0
|
||||
;;
|
||||
--full|*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "${1:-}" in
|
||||
--restore)
|
||||
restore_backup "${2:-}"
|
||||
;;
|
||||
--full)
|
||||
check_prerequisites
|
||||
create_backup
|
||||
verify_backup
|
||||
cleanup_old_backups
|
||||
log_success "Full backup cycle complete!"
|
||||
;;
|
||||
*)
|
||||
check_prerequisites
|
||||
create_backup
|
||||
verify_backup
|
||||
cleanup_old_backups
|
||||
log_success "Backup complete!"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
echo "================================================================="
|
||||
echo " Wordly.art — Backup → NAS Synology 192.168.1.146"
|
||||
echo " DB : ${POSTGRES_DB}"
|
||||
echo " NAS : ${NAS_USER}@${NAS_HOST}:${NAS_PATH}"
|
||||
echo " $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "================================================================="
|
||||
echo ""
|
||||
|
||||
check_prerequisites
|
||||
|
||||
# 1. Dump PostgreSQL
|
||||
local dump_file
|
||||
dump_file=$(backup_postgres)
|
||||
|
||||
# 2. Créer l'archive DR
|
||||
local archive_info
|
||||
archive_info=$(create_dr_archive "${dump_file}")
|
||||
local archive_path="${archive_info%%|*}"
|
||||
local archive_size="${archive_info##*|}"
|
||||
|
||||
# 3. Envoyer sur le NAS via rsync SSH
|
||||
push_to_nas "${archive_path}" "${archive_size}"
|
||||
|
||||
# 4. Rotation
|
||||
cleanup_nas
|
||||
|
||||
# 5. Sync scripts
|
||||
sync_scripts
|
||||
|
||||
# 6. Notification Telegram
|
||||
send_telegram "✅ *Wordly.art Backup OK*
|
||||
Archive : \`${SNAPSHOT_NAME}\`
|
||||
Taille : ${archive_size}
|
||||
NAS : \`${NAS_PATH}/snapshots/\`
|
||||
Date : $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
echo ""
|
||||
log_success "================================================================="
|
||||
log_success "Backup complet terminé !"
|
||||
log_success " Archive : ${NAS_PATH}/snapshots/${SNAPSHOT_NAME}"
|
||||
log_success " Lister : bash scripts/backup-to-nas.sh --list"
|
||||
log_success "================================================================="
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user