109 lines
3.4 KiB
Bash
109 lines
3.4 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:-jsjj8888}"
|
|
UPLOADS_PATH="${UPLOADS_PATH:-/app/uploads}"
|
|
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
|
BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}"
|
|
|
|
mkdir -p "${BACKUP_DIR}"
|
|
mkdir -p "${BACKUP_DIR}/db" "${BACKUP_DIR}/uploads" "${BACKUP_DIR}/config"
|
|
|
|
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 | sort)
|
|
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
|
|
|
|
echo "备份目录: ${BACKUP_DIR}"
|
|
echo "保留策略: 最近 ${KEEP_BACKUPS} 份"
|
|
|
|
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
|
|
|
|
echo ">>> 备份数据库 ${DB_NAME}"
|
|
docker exec "${DB_CONTAINER}" \
|
|
sh -lc "exec mysqldump -u${DB_USER} -p'${DB_PASSWORD}' --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}"
|