Files
momentry_core/scripts/backup_all.sh
Warren 95b44f1e55 fix: backup monitoring and PATH environment issues
- Fix backup_monitor.sh find command to sort by modification time
- Fix grep -oP syntax error (change to grep -oE)
- Adjust tier rotation threshold from -mtime +7 to +6
- Add backup_all.sh script with PATH fixes for crontab
- Add mysql-client/bin to PATH for mysqldump command
- Fix backup status check for v2 naming patterns
2026-03-30 04:11:02 +08:00

822 lines
24 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
export PATH="/usr/local/bin:/opt/homebrew/bin:/opt/homebrew/opt/postgresql@18/bin:/usr/bin:/bin:/sbin:/opt/homebrew/opt/mysql-client/bin:$PATH"
#===============================================================================
# Momentry 統一備份腳本
# 路徑: /Users/accusys/momentry/scripts/backup_all.sh
#
# 命名規範 (v2):
# {service}_{type}_v2_{YYYYMMDD}_{HHMMSS}.{ext}
#
# 版本說明:
# v1: 初始備份架構(不包含新架構組件)
# v2: 新架構備份(包含 monitor_jobs, processor_results, Output 目錄)
#
# 使用方式:
# ./backup_all.sh [service|all] [type] [timestamp]
#
# 參數:
# service - 特定服務 (postgresql, redis, mariadb, wordpress, n8n, qdrant, gitea, ollama, caddy, sftpgo, mongodb, php, momentry_output)
# all - 備份所有服務 (默認)
# type - 備份類型 (full, db, cfg, data)
# timestamp - 指定時間戳 (格式: YYYYMMDD_HHMMSS)
#
# 示例:
# ./backup_all.sh # 備份所有服務 (v2)
# ./backup_all.sh postgresql # 只備份 PostgreSQL
# ./backup_all.sh all full # 完整備份所有服務 (v2)
# ./backup_all.sh mariadb db # 只備份 MariaDB 數據庫
# ./backup_all.sh restore 20260316_101215 # 恢復到指定斷點
#
# ⚠️ v2 版本差異:
# - 新增 monitor_jobs, processor_results 表
# - 新增 Output 目錄備份
# - MongoDB 路徑修正
#
# 排程範例 (crontab):
# # 每天凌晨 3 點執行所有備份
# 0 3 * * * /Users/accusys/momentry/scripts/backup_all.sh >> /Users/accusys/momentry/log/backup.log 2>&1
#
# # 每週日凌晨 3 點執行完整備份
# 0 3 * * 0 /Users/accusys/momentry/scripts/backup_all.sh all full >> /Users/accusys/momentry/log/backup.log 2>&1
#===============================================================================
set -e
# 載入密碼配置
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/load_credentials.sh" ]; then
source "$SCRIPT_DIR/load_credentials.sh"
fi
# 確保路徑正確Crontab 環境可能缺少 PATH
export PATH="/usr/local/bin:/opt/homebrew/bin:/opt/homebrew/opt/postgresql@18/bin:/sbin:/usr/sbin:/usr/bin:/bin:/opt/homebrew/opt/mysql-client/bin"
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 路徑配置
BACKUP_ROOT="/Users/accusys/momentry/backup/daily"
LOG_DIR="/Users/accusys/momentry/log"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 備份版本 (v2 = 新架構)
BACKUP_VERSION="v2"
# 時間戳 (v2 格式: v2_YYYYMMDD_HHMMSS)
if [ -n "$3" ]; then
TIMESTAMP="$3"
else
TIMESTAMP="${BACKUP_VERSION}_$(date +%Y%m%d_%H%M%S)"
fi
# 服務列表 (v2 新增 momentry_output)
SERVICES=("postgresql" "redis" "mariadb" "wordpress" "n8n" "qdrant" "gitea" "ollama" "caddy" "sftpgo" "mongodb" "php" "momentry_output")
#===============================================================================
# 日誌函數
#===============================================================================
log() {
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DIR/backup.log"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] ✅ $1${NC}" | tee -a "$LOG_DIR/backup.log"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] ❌ $1${NC}" | tee -a "$LOG_DIR/backup.log"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] ⚠️ $1${NC}" | tee -a "$LOG_DIR/backup.log"
}
#===============================================================================
# 通用函數
#===============================================================================
ensure_backup_dir() {
local service=$1
mkdir -p "$BACKUP_ROOT/$service"
}
backup_file() {
local service=$1
local type=$2
local file=$3
ensure_backup_dir "$service"
if [ -f "$file" ]; then
local filename=$(basename "$file")
local dest="$BACKUP_ROOT/$service/${service}_${type}_${TIMESTAMP}_${filename}"
cp "$file" "$dest"
# 壓縮
if [[ "$filename" == *.sql ]]; then
gzip "$dest"
dest="${dest}.gz"
fi
# SHA256
sha256sum "$dest" >"${dest}.sha256"
log_success "$service $type: $(basename "$dest")"
return 0
fi
return 1
}
backup_directory() {
local service=$1
local type=$2
local dir=$3
ensure_backup_dir "$service"
if [ -d "$dir" ]; then
local dest="$BACKUP_ROOT/$service/${service}_${type}_${TIMESTAMP}.tar.gz"
tar -czf "$dest" -C "$(dirname "$dir")" "$(basename "$dir")" 2>/dev/null || true
# SHA256
sha256sum "$dest" >"${dest}.sha256"
log_success "$service $type: $(basename "$dest")"
return 0
fi
return 1
}
#===============================================================================
# 服務備份函數
#===============================================================================
# PostgreSQL
backup_postgresql() {
local type=${1:-db}
log "開始 PostgreSQL 備份..."
# momentry 數據庫
PGPASSWORD="$PG_PASSWORD" pg_dump -U "$PG_USER" -d momentry | gzip >"$BACKUP_ROOT/postgresql/postgresql_db_momentry_${TIMESTAMP}.sql.gz"
sha256sum "$BACKUP_ROOT/postgresql/postgresql_db_momentry_${TIMESTAMP}.sql.gz" >"$BACKUP_ROOT/postgresql/postgresql_db_${TIMESTAMP}.sha256"
# video_register 數據庫
PGPASSWORD="$PG_PASSWORD" pg_dump -U "$PG_USER" -d video_register | gzip >"$BACKUP_ROOT/postgresql/postgresql_db_video_register_${TIMESTAMP}.sql.gz"
sha256sum "$BACKUP_ROOT/postgresql/postgresql_db_video_register_${TIMESTAMP}.sql.gz" >>"$BACKUP_ROOT/postgresql/postgresql_db_${TIMESTAMP}.sha256"
log_success "PostgreSQL: 數據庫備份完成"
}
# Redis
backup_redis() {
local type=${1:-rdb}
log "開始 Redis 備份..."
redis-cli -a "$REDIS_PASSWORD" SAVE >/dev/null 2>&1
cp /opt/homebrew/var/db/redis/dump.rdb "$BACKUP_ROOT/redis/redis_rdb_${TIMESTAMP}.rdb"
sha256sum "$BACKUP_ROOT/redis/redis_rdb_${TIMESTAMP}.rdb" >"$BACKUP_ROOT/redis/redis_rdb_${TIMESTAMP}.sha256"
log_success "Redis: RDB 備份完成"
}
# MariaDB (包含 WordPress)
backup_mariadb() {
local type=${1:-db}
log "開始 MariaDB 備份..."
# 所有數據庫
mysqldump -u "$MARIADB_USER" -p"$MARIADB_PASSWORD" --all-databases | gzip > \
"$BACKUP_ROOT/mariadb/mariadb_db_all_${TIMESTAMP}.sql.gz"
sha256sum "$BACKUP_ROOT/mariadb/mariadb_db_all_${TIMESTAMP}.sql.gz" >"$BACKUP_ROOT/mariadb/mariadb_db_${TIMESTAMP}.sha256"
# WordPress 數據庫
mysqldump -u "$MARIADB_USER" -p"$MARIADB_PASSWORD" wordpress | gzip > \
"$BACKUP_ROOT/mariadb/mariadb_db_wordpress_${TIMESTAMP}.sql.gz"
sha256sum "$BACKUP_ROOT/mariadb/mariadb_db_wordpress_${TIMESTAMP}.sql.gz" >>"$BACKUP_ROOT/mariadb/mariadb_db_${TIMESTAMP}.sha256"
log_success "MariaDB: 數據庫備份完成 (包含 WordPress)"
}
# WordPress 文件
backup_wordpress_files() {
local wordpress_dir="/Users/accusys/wordpress/web"
local backup_dir="$BACKUP_ROOT/wordpress"
log "開始 WordPress 文件備份..."
# 確保備份目錄存在
mkdir -p "$backup_dir"
# 排除不必要的目錄
if [ -d "$wordpress_dir" ]; then
tar --exclude='wp-content/cache/*' \
--exclude='wp-content/uploads/cache/*' \
--exclude='.git/*' \
-czf "$backup_dir/wordpress_files_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/wordpress web/
sha256sum "$backup_dir/wordpress_files_${TIMESTAMP}.tar.gz" >>"$backup_dir/wordpress_${TIMESTAMP}.sha256" 2>/dev/null ||
sha256sum "$backup_dir/wordpress_files_${TIMESTAMP}.tar.gz" >"$backup_dir/wordpress_${TIMESTAMP}.sha256"
log_success "WordPress: 文件備份完成"
else
log_error "WordPress 目錄不存在: $wordpress_dir"
fi
}
# n8n
backup_n8n() {
local type=${1:-full}
log "開始 n8n 備份..."
# 數據庫
PGPASSWORD="$PG_PASSWORD" pg_dump -U "$PG_USER" -d n8n | gzip >"$BACKUP_ROOT/n8n/n8n_db_${TIMESTAMP}.sql.gz"
# 數據目錄
if [ -d "/Users/accusys/momentry/var/n8n" ]; then
tar -czf "$BACKUP_ROOT/n8n/n8n_data_${TIMESTAMP}.tar.gz" -C /Users/accusys/momentry/var n8n/
fi
# SHA256
sha256sum "$BACKUP_ROOT/n8n"/n8n_* >"$BACKUP_ROOT/n8n/n8n_${TIMESTAMP}.sha256"
log_success "n8n: 完整備份完成"
}
# Qdrant
backup_qdrant() {
local type=${1:-full}
log "開始 Qdrant 備份..."
# 嘗試使用 Snapshots API
COLLECTIONS=$(curl -s -H "api-key: $QDRANT_API_KEY" \
http://localhost:6333/collections | jq -r '.result[].name' 2>/dev/null || echo "")
if [ -n "$COLLECTIONS" ] && [ "$COLLECTIONS" != "null" ]; then
for COLLECTION in $COLLECTIONS; do
curl -X POST -H "api-key: $QDRANT_API_KEY" \
"http://localhost:6333/collections/${COLLECTION}/snapshots" \
-o "$BACKUP_ROOT/qdrant/qdrant_snapshot_${COLLECTION}_${TIMESTAMP}.tar.gz" 2>/dev/null || true
done
else
# 數據目錄備份
tar -czf "$BACKUP_ROOT/qdrant/qdrant_data_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/var qdrant/ 2>/dev/null || true
fi
# SHA256
sha256sum "$BACKUP_ROOT/qdrant"/qdrant_* >"$BACKUP_ROOT/qdrant/qdrant_${TIMESTAMP}.sha256"
log_success "Qdrant: 備份完成"
}
# Gitea
backup_gitea() {
local type=${1:-full}
log "開始 Gitea 備份..."
# 數據目錄
if [ -d "/Users/accusys/momentry/var/gitea" ]; then
tar -czf "$BACKUP_ROOT/gitea/gitea_data_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/var gitea/
fi
# 配置目錄
if [ -d "/Users/accusys/momentry/etc/gitea" ]; then
tar -czf "$BACKUP_ROOT/gitea/gitea_cfg_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/etc gitea/
fi
# SHA256
sha256sum "$BACKUP_ROOT/gitea"/gitea_* >"$BACKUP_ROOT/gitea/gitea_${TIMESTAMP}.sha256"
log_success "Gitea: 完整備份完成"
}
# Ollama
backup_ollama() {
local type=${1:-cfg}
log "開始 Ollama 備份..."
# 配置目錄
if [ -d "/Users/accusys/momentry/etc/ollama" ]; then
tar -czf "$BACKUP_ROOT/ollama/ollama_cfg_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/etc ollama/
fi
# 環境變數
if [ -f "/Users/accusys/momentry/var/ollama/environment.txt" ]; then
cp /Users/accusys/momentry/var/ollama/environment.txt "$BACKUP_ROOT/ollama/ollama_env_${TIMESTAMP}.txt"
fi
# SHA256
sha256sum "$BACKUP_ROOT/ollama"/ollama_* >"$BACKUP_ROOT/ollama/ollama_${TIMESTAMP}.sha256"
log_success "Ollama: 配置備份完成"
}
# Caddy
backup_caddy() {
local type=${1:-cfg}
log "開始 Caddy 備份..."
# 配置
if [ -f "/Users/accusys/momentry/etc/Caddyfile" ]; then
tar -czf "$BACKUP_ROOT/caddy/caddy_cfg_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/etc Caddyfile
fi
# SHA256
sha256sum "$BACKUP_ROOT/caddy"/caddy_* >"$BACKUP_ROOT/caddy/caddy_${TIMESTAMP}.sha256"
log_success "Caddy: 配置備份完成"
}
# SftpGo
backup_sftpgo() {
local type=${1:-cfg}
log "開始 SftpGo 備份..."
# 配置
if [ -d "/Users/accusys/momentry/etc/sftpgo" ]; then
tar -czf "$BACKUP_ROOT/sftpgo/sftpgo_cfg_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/etc sftpgo/
fi
# PostgreSQL 數據庫 (SFTPGo 已遷移到 PostgreSQL)
PGPASSWORD="$SFTPGO_PASSWORD" pg_dump -U "$SFTPGO_USER" -h localhost -d sftpgo | gzip >"$BACKUP_ROOT/sftpgo/sftpgo_db_${TIMESTAMP}.sql.gz"
# SHA256
sha256sum "$BACKUP_ROOT/sftpgo"/sftpgo_* >"$BACKUP_ROOT/sftpgo/sftpgo_${TIMESTAMP}.sha256"
log_success "SftpGo: 配置和數據庫備份完成"
}
# MongoDB
backup_mongodb() {
local type=${1:-full}
log "開始 MongoDB 備份..."
# 使用 mongodump 備份 (避免文件鎖問題)
local MONGO_BACKUP_DIR="/tmp/mongodb_backup_${TIMESTAMP}"
mkdir -p "$MONGO_BACKUP_DIR"
# mongodump 需要認證
if [ -n "$MONGODB_PASSWORD" ]; then
mongodump --uri="mongodb://localhost:27017" \
--username="$MONGODB_USER" \
--password="$MONGODB_PASSWORD" \
--authenticationDatabase=admin \
--out="$MONGO_BACKUP_DIR" 2>/dev/null || true
else
mongodump --uri="mongodb://localhost:27017" \
--out="$MONGO_BACKUP_DIR" 2>/dev/null || true
fi
# 打包
if [ -d "$MONGO_BACKUP_DIR" ] && [ "$(ls -A $MONGO_BACKUP_DIR 2>/dev/null)" ]; then
tar -czf "$BACKUP_ROOT/mongodb/mongodb_data_${TIMESTAMP}.tar.gz" \
-C "$MONGO_BACKUP_DIR" .
rm -rf "$MONGO_BACKUP_DIR"
log "MongoDB: mongodump 備份完成"
else
log_warn "MongoDB: mongodump 備份失敗或數據庫為空"
rm -rf "$MONGO_BACKUP_DIR"
fi
# SHA256
sha256sum "$BACKUP_ROOT/mongodb"/mongodb_* >"$BACKUP_ROOT/mongodb/mongodb_${TIMESTAMP}.sha256"
log_success "MongoDB: 備份完成"
}
# PHP
backup_php() {
local type=${1:-cfg}
log "開始 PHP 備份..."
# 配置
if [ -d "/Users/accusys/momentry/etc/php/8.5" ]; then
tar -czf "$BACKUP_ROOT/php/php_cfg_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry/etc php/8.5
fi
# SHA256
sha256sum "$BACKUP_ROOT/php"/php_* >"$BACKUP_ROOT/php/php_${TIMESTAMP}.sha256"
log_success "PHP: 配置備份完成"
}
# Momentry Output 目錄 (v2 新增)
backup_momentry_output() {
local type=${1:-data}
log "開始 Momentry Output 備份..."
# Output 目錄
local OUTPUT_DIR="/Users/accusys/momentry/output"
if [ -d "$OUTPUT_DIR" ]; then
tar -czf "$BACKUP_ROOT/momentry/momentry_output_${TIMESTAMP}.tar.gz" \
-C /Users/accusys/momentry output/
log "Momentry Output: 備份 $OUTPUT_DIR"
else
log_warn "Momentry Output: 目錄不存在或為空 ($OUTPUT_DIR)"
fi
# SHA256
sha256sum "$BACKUP_ROOT/momentry"/momentry_output_* >"$BACKUP_ROOT/momentry/momentry_output_${TIMESTAMP}.sha256" 2>/dev/null || true
log_success "Momentry Output: 備份完成"
}
#===============================================================================
# 恢復函數
#===============================================================================
restore_postgresql() {
local timestamp=$1
log "恢復 PostgreSQL..."
# 找到對應的備份文件
local backup_file=$(ls "$BACKUP_ROOT/postgresql"/postgresql_db_momentry_${timestamp}.sql.gz 2>/dev/null | head -1)
if [ -n "$backup_file" ]; then
gunzip -c "$backup_file" | PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -d momentry
log_success "PostgreSQL 恢復完成"
else
log_error "找不到 PostgreSQL 備份文件: $timestamp"
fi
}
restore_redis() {
local timestamp=$1
log "恢復 Redis..."
local backup_file=$(ls "$BACKUP_ROOT/redis"/redis_rdb_${timestamp}.rdb 2>/dev/null | head -1)
if [ -n "$backup_file" ]; then
redis-cli -a "$REDIS_PASSWORD" SHUTDOWN 2>/dev/null || true
cp "$backup_file" /opt/homebrew/var/db/redis/dump.rdb
launchctl load /Library/LaunchDaemons/com.momentry.redis.plist 2>/dev/null ||
redis-server --daemonize yes --requirepass "$REDIS_PASSWORD"
log_success "Redis 恢復完成"
else
log_error "找不到 Redis 備份文件: $timestamp"
fi
}
restore_mariadb() {
local timestamp=$1
log "恢復 MariaDB (包含 WordPress)..."
local backup_file=$(ls "$BACKUP_ROOT/mariadb"/mariadb_db_wordpress_${timestamp}.sql.gz 2>/dev/null | head -1)
if [ -n "$backup_file" ]; then
gunzip -c "$backup_file" | mysql -u momentry_backup -pmomentry_backup_pwd_2026 wordpress
log_success "MariaDB/WordPress 恢復完成"
else
log_error "找不到 MariaDB 備份文件: $timestamp"
fi
}
restore_n8n() {
local timestamp=$1
log "恢復 n8n..."
# 恢復數據庫
local db_backup=$(ls "$BACKUP_ROOT/n8n"/n8n_db_${timestamp}.sql.gz 2>/dev/null | head -1)
if [ -n "$db_backup" ]; then
gunzip -c "$db_backup" | PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -d n8n
fi
# 恢復數據目錄
local data_backup=$(ls "$BACKUP_ROOT/n8n"/n8n_data_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$data_backup" ]; then
rm -rf /Users/accusys/momentry/var/n8n
tar -xzf "$data_backup" -C /Users/accusys/momentry/var/
fi
log_success "n8n 恢復完成"
}
restore_qdrant() {
local timestamp=$1
log "恢復 Qdrant..."
pkill qdrant 2>/dev/null || true
sleep 2
local data_backup=$(ls "$BACKUP_ROOT/qdrant"/qdrant_data_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$data_backup" ]; then
rm -rf /Users/accusys/momentry/var/qdrant
tar -xzf "$data_backup" -C /Users/accusys/momentry/var/
fi
launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist 2>/dev/null || true
log_success "Qdrant 恢復完成"
}
restore_gitea() {
local timestamp=$1
log "恢復 Gitea..."
# 停止 Gitea
pkill gitea 2>/dev/null || true
# 恢復數據
local data_backup=$(ls "$BACKUP_ROOT/gitea"/gitea_data_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$data_backup" ]; then
rm -rf /Users/accusys/momentry/var/gitea
tar -xzf "$data_backup" -C /Users/accusys/momentry/var/
fi
# 恢復配置
local cfg_backup=$(ls "$BACKUP_ROOT/gitea"/gitea_cfg_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$cfg_backup" ]; then
rm -rf /Users/accusys/momentry/etc/gitea
tar -xzf "$cfg_backup" -C /Users/accusys/momentry/etc/
fi
log_success "Gitea 恢復完成"
}
restore_ollama() {
local timestamp=$1
log "恢復 Ollama..."
# 恢復配置
local cfg_backup=$(ls "$BACKUP_ROOT/ollama"/ollama_cfg_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$cfg_backup" ]; then
rm -rf /Users/accusys/momentry/etc/ollama
tar -xzf "$cfg_backup" -C /Users/accusys/momentry/etc/
fi
log_success "Ollama 恢復完成"
}
restore_caddy() {
local timestamp=$1
log "恢復 Caddy..."
local cfg_backup=$(ls "$BACKUP_ROOT/caddy"/caddy_cfg_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$cfg_backup" ]; then
tar -xzf "$cfg_backup" -C /Users/accusys/momentry/etc/
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
fi
log_success "Caddy 恢復完成"
}
restore_sftpgo() {
local timestamp=$1
log "恢復 SftpGo..."
# 停止 SFTPGo
pkill -f sftpgo || true
sleep 2
# 恢復配置
local cfg_backup=$(ls "$BACKUP_ROOT/sftpgo"/sftpgo_cfg_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$cfg_backup" ]; then
rm -rf /Users/accusys/momentry/etc/sftpgo
tar -xzf "$cfg_backup" -C /Users/accusys/momentry/etc/
fi
# 恢復 PostgreSQL 數據庫
local db_backup=$(ls "$BACKUP_ROOT/sftpgo"/sftpgo_db_${timestamp}.sql.gz 2>/dev/null | head -1)
if [ -n "$db_backup" ]; then
# 確保數據庫存在
PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -h localhost -d postgres -c "DROP DATABASE IF EXISTS sftpgo;" 2>/dev/null
PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -h localhost -d postgres -c "CREATE DATABASE sftpgo OWNER $SFTPGO_USER;" 2>/dev/null
gunzip -c "$db_backup" | PGPASSWORD="$SFTPGO_PASSWORD" psql -U "$SFTPGO_USER" -h localhost -d sftpgo 2>/dev/null
fi
# 重啟 SFTPGo
cd /Users/accusys/momentry/var/sftpgo
/opt/homebrew/opt/sftpgo/bin/sftpgo serve --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json &
log_success "SftpGo 恢復完成"
}
restore_mongodb() {
local timestamp=$1
log "恢復 MongoDB..."
# 解壓縮到臨時目錄
local MONGO_RESTORE_DIR="/tmp/mongodb_restore_${timestamp}"
mkdir -p "$MONGO_RESTORE_DIR"
local data_backup=$(ls "$BACKUP_ROOT/mongodb"/mongodb_data_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$data_backup" ]; then
tar -xzf "$data_backup" -C "$MONGO_RESTORE_DIR/"
# 使用 mongorestore 恢復
if [ -n "$MONGODB_PASSWORD" ]; then
mongorestore --uri="mongodb://localhost:27017" \
--username="$MONGODB_USER" \
--password="$MONGODB_PASSWORD" \
--authenticationDatabase=admin \
--drop \
--dir="$MONGO_RESTORE_DIR" 2>/dev/null || true
else
mongorestore --uri="mongodb://localhost:27017" \
--drop \
--dir="$MONGO_RESTORE_DIR" 2>/dev/null || true
fi
rm -rf "$MONGO_RESTORE_DIR"
else
log_warn "MongoDB: 未找到備份文件"
fi
log_success "MongoDB 恢復完成"
}
restore_php() {
local timestamp=$1
log "恢復 PHP..."
local cfg_backup=$(ls "$BACKUP_ROOT/php"/php_cfg_${timestamp}.tar.gz 2>/dev/null | head -1)
if [ -n "$cfg_backup" ]; then
rm -rf /Users/accusys/momentry/etc/php/8.5
tar -xzf "$cfg_backup" -C /Users/accusys/momentry/etc/php/
fi
log_success "PHP 恢復完成"
}
restore_momentry_output() {
local timestamp=$1
log "恢復 Momentry Output..."
# v2: Output 目錄可能有多個版本,嘗試 v2 版本再回退到舊版本
local output_backup=""
# 嘗試 v2 版本
output_backup=$(ls "$BACKUP_ROOT/momentry"/momentry_output_v2_${timestamp}.tar.gz 2>/dev/null | head -1)
# 如果沒有 v2 版本,嘗試舊格式
if [ -z "$output_backup" ]; then
output_backup=$(ls "$BACKUP_ROOT/momentry"/momentry_output_${timestamp}.tar.gz 2>/dev/null | head -1)
fi
if [ -n "$output_backup" ]; then
rm -rf /Users/accusys/momentry/output
mkdir -p /Users/accusys/momentry
tar -xzf "$output_backup" -C /Users/accusys/momentry/
log "Momentry Output: 恢復 $(basename $output_backup)"
else
log_warn "Momentry Output: 未找到備份檔案"
fi
log_success "Momentry Output 恢復完成"
}
#===============================================================================
# 主程序
#===============================================================================
main() {
local command=${1:-all}
local service=${2:-}
local type=${3:-}
# 確保日誌目錄存在
mkdir -p "$LOG_DIR"
echo ""
log "=========================================="
log "Momentry 備份系統"
log "時間戳: $TIMESTAMP"
log "=========================================="
case $command in
restore | rollback)
if [ -z "$service" ]; then
log_error "請指定恢復時間戳 (YYYYMMDD_HHMMSS 或 v2_YYYYMMDD_HHMMSS)"
echo "示例: $0 restore v2_20260325_030000"
exit 1
fi
log "開始恢復到斷點: $service"
for svc in "${SERVICES[@]}"; do
case $svc in
postgresql) restore_postgresql "$service" ;;
redis) restore_redis "$service" ;;
mariadb) restore_mariadb "$service" ;;
n8n) restore_n8n "$service" ;;
qdrant) restore_qdrant "$service" ;;
gitea) restore_gitea "$service" ;;
ollama) restore_ollama "$service" ;;
caddy) restore_caddy "$service" ;;
sftpgo) restore_sftpgo "$service" ;;
mongodb) restore_mongodb "$service" ;;
php) restore_php "$service" ;;
momentry_output) restore_momentry_output "$service" ;;
esac
done
log "=========================================="
log_success "恢復完成!"
log "=========================================="
;;
list)
log "可用時間點:"
for dir in "$BACKUP_ROOT"/*/; do
local svc=$(basename "$dir")
echo " $svc:"
ls -1 "$dir"*.tar.gz "$dir"*.sql.gz "$dir"*.rdb 2>/dev/null |
sed 's/.*\([0-9]\{8\}\_[0-9]\{6\}\).*/\1/' | sort -u | sed 's/^/ /'
done
;;
status)
log "備份狀態:"
echo ""
for svc in "${SERVICES[@]}"; do
local date_part="${TIMESTAMP#*_}" # Remove v2_ prefix
date_part="${date_part:0:8}" # Extract YYYYMMDD
local latest=$(find "$BACKUP_ROOT/$svc" \( -name "*_${date_part}_*" -o -name "*_v2_${date_part}_*" \) -type f 2>/dev/null | head -1)
if [ -n "$latest" ]; then
local size=$(du -h "$latest" | cut -f1)
echo -e " $svc: ${GREEN}${NC} $size"
else
echo -e " $svc: ${RED}${NC}"
fi
done
;;
all)
# 備份所有服務
for svc in "${SERVICES[@]}"; do
case $svc in
postgresql) backup_postgresql "$type" ;;
redis) backup_redis "$type" ;;
mariadb) backup_mariadb "$type" ;;
wordpress) backup_wordpress_files ;;
n8n) backup_n8n "$type" ;;
qdrant) backup_qdrant "$type" ;;
gitea) backup_gitea "$type" ;;
ollama) backup_ollama "$type" ;;
caddy) backup_caddy "$type" ;;
sftpgo) backup_sftpgo "$type" ;;
mongodb) backup_mongodb "$type" ;;
php) backup_php "$type" ;;
momentry_output) backup_momentry_output "$type" ;;
esac
done
log "=========================================="
log_success "所有備份完成! 時間戳: $TIMESTAMP"
log "=========================================="
;;
*)
# 備份特定服務
if [ -n "$service" ]; then
case $service in
postgresql) backup_postgresql "$type" ;;
redis) backup_redis "$type" ;;
mariadb) backup_mariadb "$type" ;;
wordpress) backup_wordpress_files ;;
n8n) backup_n8n "$type" ;;
qdrant) backup_qdrant "$type" ;;
gitea) backup_gitea "$type" ;;
ollama) backup_ollama "$type" ;;
caddy) backup_caddy "$type" ;;
sftpgo) backup_sftpgo "$type" ;;
mongodb) backup_mongodb "$type" ;;
php) backup_php "$type" ;;
momentry_output) backup_momentry_output "$type" ;;
*)
log_error "未知服務: $service"
echo "可用服務: ${SERVICES[*]}"
exit 1
;;
esac
else
log_error "請指定命令或服務"
echo "用法: $0 [命令] [服務] [類型]"
echo ""
echo "命令:"
echo " all - 備份所有服務 (默認)"
echo " <service> - 備份特定服務"
echo " restore - 恢復到指定斷點"
echo " list - 列出可用時間點"
echo " status - 顯示備份狀態"
echo ""
echo "服務: ${SERVICES[*]}"
exit 1
fi
;;
esac
}
main "$@"