138 lines
4.3 KiB
Bash
138 lines
4.3 KiB
Bash
#!/bin/bash
|
||
|
||
# 固定资产系统备份脚本
|
||
set -euo pipefail
|
||
|
||
PROJECT_DIR="${PROJECT_DIR:-/opt/equipment-management}"
|
||
PROJECT_NAME="${PROJECT_NAME:-equipment-management}"
|
||
BACKUP_ROOT="${BACKUP_ROOT:-/opt/backups/equipment-management}"
|
||
KEEP_BACKUPS="${KEEP_BACKUPS:-3}"
|
||
DB_CONTAINER="${DB_CONTAINER:-equipment-mysql}"
|
||
BACKEND_CONTAINER="${BACKEND_CONTAINER:-equipment-backend}"
|
||
DB_NAME="${DB_NAME:-equipment_management}"
|
||
DB_USER="${DB_USER:-root}"
|
||
DB_PASSWORD="${DB_PASSWORD:-${MYSQL_ROOT_PASSWORD:-}}"
|
||
UPLOADS_PATH="${UPLOADS_PATH:-/app/uploads}"
|
||
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
||
BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}"
|
||
|
||
cleanup_on_error() {
|
||
echo "备份失败,清理不完整目录: ${BACKUP_DIR}"
|
||
rm -rf "${BACKUP_DIR}"
|
||
}
|
||
|
||
prune_old_backups() {
|
||
if ! [[ "${KEEP_BACKUPS}" =~ ^[0-9]+$ ]]; then
|
||
echo "KEEP_BACKUPS 不是有效数字: ${KEEP_BACKUPS}"
|
||
exit 1
|
||
fi
|
||
|
||
mapfile -t existing_backups < <(
|
||
find "${BACKUP_ROOT}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; \
|
||
| awk '/^[0-9]{8}_[0-9]{6}$/' \
|
||
| sort \
|
||
| while IFS= read -r dir_name; do
|
||
printf '%s/%s\n' "${BACKUP_ROOT}" "${dir_name}"
|
||
done
|
||
)
|
||
local total="${#existing_backups[@]}"
|
||
|
||
if [ "${total}" -le "${KEEP_BACKUPS}" ]; then
|
||
echo ">>> 当前标准备份数量 ${total},无需清理"
|
||
return
|
||
fi
|
||
|
||
local delete_count=$((total - KEEP_BACKUPS))
|
||
echo ">>> 仅保留最近 ${KEEP_BACKUPS} 份备份,清理 ${delete_count} 份旧备份"
|
||
for backup_dir in "${existing_backups[@]:0:${delete_count}}"; do
|
||
echo " 删除: ${backup_dir}"
|
||
rm -rf "${backup_dir}"
|
||
done
|
||
}
|
||
|
||
trap cleanup_on_error ERR
|
||
|
||
if ! docker inspect "${DB_CONTAINER}" >/dev/null 2>&1; then
|
||
echo "数据库容器不存在: ${DB_CONTAINER}"
|
||
exit 1
|
||
fi
|
||
|
||
if ! docker inspect "${BACKEND_CONTAINER}" >/dev/null 2>&1; then
|
||
echo "后端容器不存在: ${BACKEND_CONTAINER}"
|
||
exit 1
|
||
fi
|
||
|
||
if [ -z "${DB_PASSWORD}" ]; then
|
||
echo ">>> 未从 DB_PASSWORD / MYSQL_ROOT_PASSWORD 读取到数据库密码,尝试读取 ${DB_CONTAINER} 容器环境"
|
||
if ! container_env="$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "${DB_CONTAINER}")"; then
|
||
echo "无法读取数据库容器环境: ${DB_CONTAINER}" >&2
|
||
exit 1
|
||
fi
|
||
|
||
while IFS= read -r env_line; do
|
||
case "${env_line}" in
|
||
MYSQL_ROOT_PASSWORD=*)
|
||
DB_PASSWORD="${env_line#MYSQL_ROOT_PASSWORD=}"
|
||
break
|
||
;;
|
||
esac
|
||
done <<< "${container_env}"
|
||
unset container_env env_line
|
||
fi
|
||
|
||
if [ -z "${DB_PASSWORD}" ]; then
|
||
echo "数据库密码缺失:请设置 DB_PASSWORD 或 MYSQL_ROOT_PASSWORD;容器 ${DB_CONTAINER} 中也未找到 MYSQL_ROOT_PASSWORD。" >&2
|
||
exit 1
|
||
fi
|
||
|
||
mkdir -p "${BACKUP_DIR}/db" "${BACKUP_DIR}/uploads" "${BACKUP_DIR}/config"
|
||
|
||
echo "备份目录: ${BACKUP_DIR}"
|
||
echo "保留策略: 最近 ${KEEP_BACKUPS} 份"
|
||
|
||
echo ">>> 备份数据库 ${DB_NAME}"
|
||
docker exec -e "MYSQL_PWD=${DB_PASSWORD}" "${DB_CONTAINER}" \
|
||
mysqldump -u"${DB_USER}" --single-transaction --routines --triggers "${DB_NAME}" \
|
||
| gzip > "${BACKUP_DIR}/db/${DB_NAME}.sql.gz"
|
||
|
||
echo ">>> 备份上传目录 ${UPLOADS_PATH}"
|
||
docker exec "${BACKEND_CONTAINER}" \
|
||
sh -lc "if [ -d '${UPLOADS_PATH}' ]; then tar -C '${UPLOADS_PATH}' -czf - .; else printf '' | gzip; fi" \
|
||
> "${BACKUP_DIR}/uploads/uploads.tar.gz"
|
||
|
||
echo ">>> 备份部署配置"
|
||
cp "${PROJECT_DIR}/docker-compose.yml" "${BACKUP_DIR}/config/docker-compose.yml"
|
||
cp "${PROJECT_DIR}/deploy.sh" "${BACKUP_DIR}/config/deploy.sh"
|
||
cp "${PROJECT_DIR}/Dockerfile" "${BACKUP_DIR}/config/Dockerfile"
|
||
if [ -d "${PROJECT_DIR}/frontend/public/branding" ]; then
|
||
cp -R "${PROJECT_DIR}/frontend/public/branding" "${BACKUP_DIR}/config/branding"
|
||
fi
|
||
|
||
cat > "${BACKUP_DIR}/backup-info.txt" <<EOF
|
||
timestamp=${TIMESTAMP}
|
||
project_name=${PROJECT_NAME}
|
||
project_dir=${PROJECT_DIR}
|
||
db_container=${DB_CONTAINER}
|
||
backend_container=${BACKEND_CONTAINER}
|
||
db_name=${DB_NAME}
|
||
uploads_path=${UPLOADS_PATH}
|
||
hostname=$(hostname)
|
||
EOF
|
||
|
||
echo ">>> 生成校验信息"
|
||
(
|
||
cd "${BACKUP_DIR}"
|
||
if [ -d config/branding ]; then
|
||
find config/branding -type f -print0 | xargs -0 sha256sum > branding-sha256.tmp
|
||
else
|
||
: > branding-sha256.tmp
|
||
fi
|
||
cat <(sha256sum db/${DB_NAME}.sql.gz uploads/uploads.tar.gz config/docker-compose.yml config/deploy.sh config/Dockerfile) branding-sha256.tmp > SHA256SUMS
|
||
rm -f branding-sha256.tmp
|
||
)
|
||
|
||
prune_old_backups
|
||
trap - ERR
|
||
|
||
echo "备份完成: ${BACKUP_DIR}"
|