Files
hms/docker/backup.sh
iven bc571c7749 feat(docker): 生产环境 DevOps 基础设施 — TLS + 备份加密 + Prometheus + Redis 持久化
新增:
- nginx/nginx.conf: TLS 1.2/1.3 终端 + HSTS/CSP 安全头 + SSE 长连接 + 50M 上传限制
- prometheus/prometheus.yml: HMS/PostgreSQL/Redis/Nginx 四指标源
- prometheus/alerts.yml: 4 组告警规则(系统/应用/数据库/Redis),含 5xx 错误率 + 内存 + 连接数
- restore.sh: 备份恢复脚本(支持加密备份解密恢复)

改进:
- backup.sh: 新增 BACKUP_PASSPHRASE 加密(AES-256-CBC)+ 完整性校验 + 恢复指引
- docker-compose.production.yml: 添加 Nginx/Prometheus/Grafana/uploads-backup 容器
- docker-compose.yml: Redis 添加 --appendonly yes 持久化
- .env.production.example: 添加 DevOps 相关环境变量模板
2026-05-21 18:21:51 +08:00

96 lines
3.5 KiB
Bash
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.
#!/usr/bin/env bash
# PostgreSQL 自动备份脚本(含加密)
# 用法:
# 手动: ./docker/backup.sh
# 自动: 由 docker compose backup 服务每日 02:00 执行
#
# 加密方式(二选一):
# BACKUP_PASSPHRASE — 使用 openssl AES-256-CBC 对称加密(无额外依赖)
# GPG_RECIPIENT — 使用 GPG 非对称加密(需预置公钥)
set -euo pipefail
BACKUP_DIR="${BACKUP_DIR:-/backups}"
PG_HOST="${PGHOST:-postgres}"
PG_PORT="${PGPORT:-5432}"
PG_USER="${PGUSER:-erp}"
PG_DB="${PGDATABSE:-erp}"
KEEP_DAYS="${KEEP_DAYS:-7}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
FILENAME="${PG_DB}_${TIMESTAMP}.sql.gz"
ENCRYPTED_FILENAME="${FILENAME}.enc"
FILEPATH="${BACKUP_DIR}/${FILENAME}"
ENCRYPTED_FILEPATH="${BACKUP_DIR}/${ENCRYPTED_FILENAME}"
mkdir -p "${BACKUP_DIR}"
echo "[$(date -Iseconds)] 开始备份 ${PG_DB}${FILEPATH}"
if pg_dump \
-h "${PG_HOST}" \
-p "${PG_PORT}" \
-U "${PG_USER}" \
-d "${PG_DB}" \
--format=plain \
--no-owner \
--no-privileges \
| gzip > "${FILEPATH}"; then
SIZE=$(du -h "${FILEPATH}" | cut -f1)
echo "[$(date -Iseconds)] 备份完成: ${FILENAME} (${SIZE})"
else
echo "[$(date -Iseconds)] 备份失败!" >&2
rm -f "${FILEPATH}"
exit 1
fi
# ── 加密备份 ──
if [ -n "${BACKUP_PASSPHRASE:-}" ]; then
echo "[$(date -Iseconds)] 使用 AES-256-CBC 加密备份..."
if openssl enc -aes-256-cbc -salt -pbkdf2 -pass "pass:${BACKUP_PASSPHRASE}" \
-in "${FILEPATH}" -out "${ENCRYPTED_FILEPATH}"; then
rm -f "${FILEPATH}"
ENC_SIZE=$(du -h "${ENCRYPTED_FILEPATH}" | cut -f1)
echo "[$(date -Iseconds)] 加密完成: ${ENCRYPTED_FILENAME} (${ENC_SIZE})"
else
echo "[$(date -Iseconds)] 加密失败!保留未加密备份" >&2
rm -f "${ENCRYPTED_FILEPATH}"
fi
elif [ -n "${GPG_RECIPIENT:-}" ]; then
echo "[$(date -Iseconds)] 使用 GPG 加密备份..."
if gpg --batch --yes --encrypt --recipient "${GPG_RECIPIENT}" "${FILEPATH}"; then
rm -f "${FILEPATH}"
ENC_SIZE=$(du -h "${ENCRYPTED_FILEPATH}" | cut -f1)
echo "[$(date -Iseconds)] 加密完成: ${ENCRYPTED_FILENAME} (${ENC_SIZE})"
else
echo "[$(date -Iseconds)] GPG 加密失败!保留未加密备份" >&2
rm -f "${FILEPATH}.gpg"
fi
else
echo "[$(date -Iseconds)] 警告: 未设置 BACKUP_PASSPHRASE 或 GPG_RECIPIENT备份未加密" >&2
fi
# ── 备份完整性校验 ──
LATEST_FILE=$(ls -t "${BACKUP_DIR}/${PG_DB}"_*.sql.gz* 2>/dev/null | head -1)
if [ -n "${LATEST_FILE}" ] && [ -f "${LATEST_FILE}" ]; then
if [[ "${LATEST_FILE}" == *.enc ]]; then
echo "[$(date -Iseconds)] 加密备份文件存在: $(basename "${LATEST_FILE}")"
elif gzip -t "${LATEST_FILE}" 2>/dev/null; then
echo "[$(date -Iseconds)] 备份完整性校验通过"
else
echo "[$(date -Iseconds)] 警告: 备份文件可能损坏: ${LATEST_FILE}" >&2
fi
fi
# ── 清理过期备份 ──
DELETED=$(find "${BACKUP_DIR}" -name "${PG_DB}_*.sql.gz*" -mtime +${KEEP_DAYS} -delete -print | wc -l)
if [ "${DELETED}" -gt 0 ]; then
echo "[$(date -Iseconds)] 已清理 ${DELETED} 个过期备份(>${KEEP_DAYS}天)"
fi
# ── 恢复指引 ──
echo ""
echo "恢复方法:"
echo " # 解密(如加密):"
echo " openssl enc -d -aes-256-cbc -pbkdf2 -pass pass:\$BACKUP_PASSPHRASE -in ${ENCRYPTED_FILEPATH} -out ${FILEPATH}"
echo " # 恢复:"
echo " gunzip -c ${FILEPATH} | psql -h \$PGHOST -U \$PGUSER -d \$PGDB"