#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" . "$ROOT_DIR/scripts/lib.sh" load_env BACKUP_DIR="$ROOT_DIR/data/backups" TIMESTAMP="$(date +%Y%m%d%H%M%S)" BACKUP_NAME="xuqm-backup-${PRIVATE_VERSION:-unknown}-${TIMESTAMP}" BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME" audit "backup" "STARTED" "backup=$BACKUP_NAME" progress "backup" "STARTED" "backup=$BACKUP_NAME" mkdir -p "$BACKUP_PATH" # Config backup (excluding secrets.env for security) tar --exclude='config/secrets.env' -czf "$BACKUP_PATH/config.tar.gz" \ -C "$ROOT_DIR" VERSION .env config .deploy-state audit "backup" "CONFIG_DONE" "config.tar.gz" # MySQL backup if [ "${MYSQL_MODE:-external}" = "managed" ]; then MYSQL_CTR="$(compose ps -q mysql 2>/dev/null | head -1 || true)" if [ -n "$MYSQL_CTR" ]; then docker exec "$MYSQL_CTR" \ mysqldump -u root -p"${MYSQL_ROOT_PASSWORD:-}" \ --single-transaction --routines --triggers --events \ "${MYSQL_DATABASE:-xuqm_private}" \ > "$BACKUP_PATH/mysql.sql" audit "backup" "MYSQL_DONE" "mysqldump from managed container" else audit "backup" "MYSQL_SKIPPED" "managed mysql container not running" printf '# managed MySQL container not running at backup time\n' > "$BACKUP_PATH/mysql.sql" fi elif [ -n "${MYSQL_HOST:-}" ] && [ "${MYSQL_HOST:-}" != "127.0.0.1" ]; then if command -v mysqldump >/dev/null 2>&1; then mysqldump -h "${MYSQL_HOST}" -P "${MYSQL_PORT:-3306}" \ -u "${MYSQL_USERNAME:-}" -p"${MYSQL_PASSWORD:-}" \ --single-transaction --routines --triggers --events \ "${MYSQL_DATABASE:-xuqm_private}" \ > "$BACKUP_PATH/mysql.sql" audit "backup" "MYSQL_DONE" "mysqldump from external host" else printf '# mysqldump not available on this host. Run manually:\n' > "$BACKUP_PATH/mysql.sql" printf '# mysqldump -h %s -P %s -u %s -p --single-transaction %s > mysql.sql\n' \ "${MYSQL_HOST}" "${MYSQL_PORT:-3306}" "${MYSQL_USERNAME:-}" "${MYSQL_DATABASE:-}" >> "$BACKUP_PATH/mysql.sql" audit "backup" "MYSQL_SKIPPED" "mysqldump not available; manual instructions written" fi else audit "backup" "MYSQL_SKIPPED" "external mode with localhost — run mysqldump manually" printf '# External MySQL on localhost. Run mysqldump manually.\n' > "$BACKUP_PATH/mysql.sql" fi # Redis backup if [ "${REDIS_MODE:-external}" = "managed" ]; then REDIS_CTR="$(compose ps -q redis 2>/dev/null | head -1 || true)" if [ -n "$REDIS_CTR" ]; then docker exec "$REDIS_CTR" redis-cli -a "${REDIS_PASSWORD:-}" BGSAVE 2>/dev/null || true sleep 2 docker cp "$REDIS_CTR:/data/dump.rdb" "$BACKUP_PATH/redis-dump.rdb" 2>/dev/null || true audit "backup" "REDIS_DONE" "bgsave + copy from managed container" else audit "backup" "REDIS_SKIPPED" "managed redis container not running" fi else if command -v redis-cli >/dev/null 2>&1 && [ -n "${REDIS_HOST:-}" ]; then redis-cli -h "${REDIS_HOST}" -p "${REDIS_PORT:-6379}" -a "${REDIS_PASSWORD:-}" \ --no-auth-warning BGSAVE 2>/dev/null || true audit "backup" "REDIS_TRIGGERED" "BGSAVE triggered on external Redis" else audit "backup" "REDIS_SKIPPED" "redis-cli not available" fi fi # Write manifest MANIFEST="$BACKUP_PATH/manifest.json" cat > "$MANIFEST" < "$BACKUP_PATH/sha256sums.txt") audit "backup" "DONE" "backup=$BACKUP_NAME path=$BACKUP_PATH" progress "backup" "DONE" "backup=$BACKUP_NAME" printf 'Backup complete: %s\n' "$BACKUP_PATH"