Files
office_translator/scripts/backup-to-nas.sh
sepehr 9bb02927c3
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m42s
fix: redirect logs to stderr and reduce size thresholds in backup/verify scripts
2026-06-07 11:16:45 +02:00

359 lines
13 KiB
Bash

#!/bin/bash
# ==============================================================================
# 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.
#
# 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
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# ==============================================================================
# CHARGER LE .env
# ==============================================================================
ENV_FILE="${PROJECT_ROOT}/.env"
if [ -f "${ENV_FILE}" ]; then
set -a
set +u
source "${ENV_FILE}"
set -u
set +a
else
echo "ERROR: .env introuvable : ${ENV_FILE}" >&2
exit 1
fi
# ==============================================================================
# CONFIGURATION (depuis .env)
# ==============================================================================
# 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")
DAY_OF_WEEK=$(date +"%u") # 1=Lun, 7=Dim
DAY_OF_MONTH=$(date +"%d")
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'"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
LOG_PREFIX="[Backup ${TIMESTAMP}]"
log() { echo "${LOG_PREFIX} $1" >&2; }
log_success() { echo -e "${LOG_PREFIX} ${GREEN}$1${NC}" >&2; }
log_error() { echo -e "${LOG_PREFIX} ${RED}❌ ERROR: $1${NC}" >&2; }
log_warning() { echo -e "${LOG_PREFIX} ${YELLOW}⚠️ $1${NC}" >&2; }
# ==============================================================================
# TELEGRAM
# ==============================================================================
send_telegram() {
local message="$1"
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() {
log "Vérification des prérequis..."
# 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
# 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"
}
# ==============================================================================
# BACKUP POSTGRESQL
# ==============================================================================
backup_postgres() {
log "Dump PostgreSQL de '${POSTGRES_DB}'..."
mkdir -p "${LOCAL_TMP}"
local dump_file="${LOCAL_TMP}/db_${TIMESTAMP}.dump.gz"
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
# 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 # 1KB minimum (safe for new/small databases)
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
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
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 "$@"