- upgrade.sh/rollback.sh: backup→pull→rolling restart→healthcheck→auto-rollback - backup.sh/restore.sh: mysqldump+redis BGSAVE+config tar, SHA256 manifest, restore with checksum verification - healthcheck.sh: Docker/container/MySQL/Redis/HTTP/disk checks, JSON output to .deploy-state/ - doctor.sh: sanitized diagnostics archive, vendor API TCP connectivity, cert expiry - export-offline-bundle.sh: docker pull+save for all profile images, load-images.sh, SHA256 - configure.sh: interactive/non-interactive mode, MySQL/Redis mode selection, domain prompts - enable-service.sh: domain validation, docker pull + compose up, healthcheck - disable-service.sh: compose stop+rm, profile removal, render-config - renew-cert.sh: acme.sh/certbot, --dry-run, backup old cert, nginx reload on success - alert-webhook.sh: WeCom/DingTalk/Feishu webhook, message sanitization - bench.sh: ab/wrk/curl benchmark, JSON report with docker stats - rotate-secrets.sh: JWT and internal token rotation - vendor credential templates: push.env and store-submit.env with full credential comments - render-config.sh: auto-sync SDK URL env vars (SDK_FILE_SERVICE_URL, SDK_IM_API_URL, SDK_IM_WS_URL) - All scripts pass bash -n syntax check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 行
3.4 KiB
Bash
可执行文件
86 行
3.4 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
. "$ROOT_DIR/scripts/lib.sh"
|
|
|
|
BACKUP_NAME="${1:-}"
|
|
[ -n "$BACKUP_NAME" ] || fail_json "XUQM_PRIVATE_4010" "usage: restore.sh <backup-name>" "restore"
|
|
|
|
BACKUP_DIR="$ROOT_DIR/data/backups"
|
|
BACKUP_PATH="$BACKUP_DIR/$BACKUP_NAME"
|
|
[ -d "$BACKUP_PATH" ] || fail_json "XUQM_PRIVATE_4011" "backup not found: $BACKUP_PATH" "restore"
|
|
|
|
audit "restore" "STARTED" "backup=$BACKUP_NAME"
|
|
progress "restore" "STARTED" "backup=$BACKUP_NAME"
|
|
|
|
# Verify checksums
|
|
if [ -f "$BACKUP_PATH/sha256sums.txt" ]; then
|
|
(cd "$BACKUP_DIR" && sha256sum -c "$BACKUP_NAME/sha256sums.txt" --ignore-missing) || \
|
|
fail_json "XUQM_PRIVATE_4012" "checksum verification failed for $BACKUP_NAME" "restore"
|
|
audit "restore" "CHECKSUM_OK" "$BACKUP_NAME"
|
|
fi
|
|
|
|
# Stop running services
|
|
compose down 2>/dev/null || true
|
|
audit "restore" "SERVICES_STOPPED" ""
|
|
|
|
# Restore config (skip secrets.env if already exists)
|
|
if [ -f "$BACKUP_PATH/config.tar.gz" ]; then
|
|
tar -xzf "$BACKUP_PATH/config.tar.gz" -C "$ROOT_DIR" \
|
|
--exclude='config/secrets.env'
|
|
audit "restore" "CONFIG_DONE" "config restored (secrets.env preserved)"
|
|
fi
|
|
|
|
load_env
|
|
|
|
# Restore MySQL
|
|
if [ -f "$BACKUP_PATH/mysql.sql" ] && grep -qv '^#' "$BACKUP_PATH/mysql.sql" 2>/dev/null; then
|
|
if [ "${MYSQL_MODE:-external}" = "managed" ]; then
|
|
COMPOSE_PROFILES=infra-mysql compose up -d mysql
|
|
sleep 5
|
|
MYSQL_CTR="$(compose ps -q mysql | head -1)"
|
|
docker exec -i "$MYSQL_CTR" \
|
|
mysql -u root -p"${MYSQL_ROOT_PASSWORD:-}" "${MYSQL_DATABASE:-xuqm_private}" \
|
|
< "$BACKUP_PATH/mysql.sql"
|
|
audit "restore" "MYSQL_DONE" "restored to managed mysql"
|
|
elif command -v mysql >/dev/null 2>&1; then
|
|
mysql -h "${MYSQL_HOST:-127.0.0.1}" -P "${MYSQL_PORT:-3306}" \
|
|
-u "${MYSQL_USERNAME:-}" -p"${MYSQL_PASSWORD:-}" \
|
|
"${MYSQL_DATABASE:-xuqm_private}" < "$BACKUP_PATH/mysql.sql"
|
|
audit "restore" "MYSQL_DONE" "restored to external mysql"
|
|
else
|
|
audit "restore" "MYSQL_SKIPPED" "mysql client not available; restore manually"
|
|
printf 'WARNING: MySQL restore skipped. Restore manually with:\n'
|
|
printf ' mysql -h %s -P %s -u %s -p <password> %s < %s/mysql.sql\n' \
|
|
"${MYSQL_HOST:-}" "${MYSQL_PORT:-3306}" "${MYSQL_USERNAME:-}" \
|
|
"${MYSQL_DATABASE:-}" "$BACKUP_PATH"
|
|
fi
|
|
fi
|
|
|
|
# Restore Redis
|
|
if [ -f "$BACKUP_PATH/redis-dump.rdb" ]; then
|
|
if [ "${REDIS_MODE:-external}" = "managed" ]; then
|
|
COMPOSE_PROFILES=infra-redis compose up -d redis
|
|
sleep 3
|
|
REDIS_CTR="$(compose ps -q redis | head -1)"
|
|
compose pause redis 2>/dev/null || docker pause "$REDIS_CTR" 2>/dev/null || true
|
|
docker cp "$BACKUP_PATH/redis-dump.rdb" "$REDIS_CTR:/data/dump.rdb"
|
|
compose unpause redis 2>/dev/null || docker unpause "$REDIS_CTR" 2>/dev/null || true
|
|
docker restart "$REDIS_CTR"
|
|
audit "restore" "REDIS_DONE" "redis-dump.rdb restored to managed redis"
|
|
else
|
|
audit "restore" "REDIS_SKIPPED" "external Redis — restore dump.rdb manually"
|
|
printf 'WARNING: Redis restore skipped for external mode. Copy %s/redis-dump.rdb to Redis data dir.\n' "$BACKUP_PATH"
|
|
fi
|
|
fi
|
|
|
|
# Restart services
|
|
"$ROOT_DIR/scripts/render-config.sh"
|
|
COMPOSE_PROFILES="${COMPOSE_PROFILES:-base}" compose up -d
|
|
"$ROOT_DIR/scripts/healthcheck.sh"
|
|
|
|
audit "restore" "DONE" "backup=$BACKUP_NAME restored successfully"
|
|
progress "restore" "DONE" "backup=$BACKUP_NAME"
|
|
printf 'Restore complete from: %s\n' "$BACKUP_PATH"
|