Files
office_translator/scripts/backup-to-nas.sh
sepehr ce8e150a61 feat: homelab deployment - NPM + IONOS DNS + monitoring + NAS backup
- Restructured docker-compose for Nginx Proxy Manager (no custom nginx)
- Added domain wordly.art configuration
- Added Prometheus + Grafana monitoring stack with pre-configured dashboards
- Added PostgreSQL backup script to NAS (daily/weekly/monthly rotation)
- Added alert rules for backend, system, and Docker metrics
- Updated deployment guide for NPM + IONOS DNS homelab setup
- Added marketing plan document
- PDF translator and watermark support
- Enhanced middleware, routes, and translator modules

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 11:43:28 +02:00

288 lines
8.6 KiB
Bash

#!/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
#
# 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
# ============================================
set -euo pipefail
# ===========================================
# CONFIGURATION - MODIFY THESE VALUES
# ===========================================
# NAS settings (SMB/CIFS or NFS mount point)
NAS_BACKUP_DIR="/mnt/nas-backups/wordly"
# Docker container name for PostgreSQL
POSTGRES_CONTAINER="wordly-postgres"
POSTGRES_USER="translate"
POSTGRES_DB="translate_db"
POSTGRES_PASSWORD="yLLgkEvt6mvzGDdoqtQvI1vEgMmR-W75ZTPW5StaIAU"
# Backup retention
DAILY_RETENTION=7 # Keep 7 daily backups
WEEKLY_RETENTION=4 # Keep 4 weekly backups
MONTHLY_RETENTION=6 # Keep 6 monthly backups
# Notification (optional - leave empty to disable)
NOTIFICATION_WEBHOOK="" # Slack/Discord webhook URL
# ===========================================
# 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_MONTH=$(date +"%d")
BACKUP_NAME="wordly_db_${TIMESTAMP}.sql.gz"
BACKUP_PATH="${NAS_BACKUP_DIR}/${BACKUP_NAME}"
LOG_PREFIX="[Wordly Backup ${TIMESTAMP}]"
# Colors for terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# ===========================================
# 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}WARNING: $1${NC}"
}
send_notification() {
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
fi
}
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..."
# 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"
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"
exit 1
fi
}
cleanup_old_backups() {
log "Cleaning up old backups..."
# 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
# 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)"
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
if [ -z "${full_path}" ]; then
log_error "Backup file not found: ${backup_file}"
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"
}
# ===========================================
# MAIN
# ===========================================
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