#!/usr/bin/env bash set -Eeuo pipefail umask 077 SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" PROJECT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd -P)" BACKUP_ROOT="${DATA_BACKUP_ROOT:-}" PASSPHRASE_FILE="${DATA_BACKUP_PASSPHRASE_FILE:-}" DATA_DIR="${MYSQL_DATA_DIR:-}" STORAGE_ID="${MYSQL_STORAGE_ID:-}" RETENTION_DAYS="${DATA_BACKUP_RETENTION_DAYS:-30}" SERVICE_USER="${MYSQL_BACKUP_SERVICE_USER:-${SUDO_USER:-root}}" DOCKER_SOCKET="${DOCKER_HOST:-}" ENV_DIR="/etc/travel-kg" ENV_FILE="${ENV_DIR}/mysql-backup.env" SERVICE_FILE="/etc/systemd/system/nianxx-mysql-backup.service" TIMER_FILE="/etc/systemd/system/nianxx-mysql-backup.timer" fail() { echo "错误:$*" >&2 exit 2 } safe_path() { [[ "$1" =~ ^/[A-Za-z0-9._/-]+$ ]] } [[ "${EUID}" -eq 0 ]] || fail "请使用 sudo -E 执行" command -v systemctl >/dev/null 2>&1 || fail "服务器没有systemd,不能安装定时任务" command -v runuser >/dev/null 2>&1 || fail "缺少runuser命令" id "${SERVICE_USER}" >/dev/null 2>&1 || fail "备份服务用户不存在:${SERVICE_USER}" safe_path "${PROJECT_DIR}" || fail "代码目录只能包含字母、数字、点、下划线、横线和斜线" safe_path "${BACKUP_ROOT}" || fail "DATA_BACKUP_ROOT 必须是安全的绝对路径" safe_path "${PASSPHRASE_FILE}" || fail "DATA_BACKUP_PASSPHRASE_FILE 必须是安全的绝对路径" safe_path "${DATA_DIR}" || fail "MYSQL_DATA_DIR 必须是安全的绝对路径" [[ "${STORAGE_ID}" =~ ^[A-Za-z0-9._-]{16,128}$ ]] || fail "MYSQL_STORAGE_ID格式错误" [[ "${RETENTION_DAYS}" =~ ^[0-9]+$ ]] || fail "DATA_BACKUP_RETENTION_DAYS必须是整数" (( RETENTION_DAYS >= 7 && RETENTION_DAYS <= 3650 )) || fail "备份保留天数必须在7–3650天之间" [[ -x "${PROJECT_DIR}/scripts/backup_mysql_encrypted.sh" ]] || fail "备份脚本不存在或不可执行" [[ -d "${BACKUP_ROOT}" && ! -L "${BACKUP_ROOT}" ]] || fail "备份目录不存在或是符号链接" [[ -d "${DATA_DIR}" && ! -L "${DATA_DIR}" ]] || fail "MySQL数据目录不存在或是符号链接" [[ -f "${PASSPHRASE_FILE}" && ! -L "${PASSPHRASE_FILE}" ]] || fail "备份口令文件不存在或是符号链接" runuser -u "${SERVICE_USER}" -- test -w "${BACKUP_ROOT}" || fail "${SERVICE_USER}不能写入备份目录" runuser -u "${SERVICE_USER}" -- test -r "${PASSPHRASE_FILE}" || fail "${SERVICE_USER}不能读取备份口令文件" if [[ -n "${DOCKER_SOCKET}" && ! "${DOCKER_SOCKET}" =~ ^unix:///[A-Za-z0-9._/-]+$ ]]; then fail "DOCKER_HOST只允许安全的本机Unix socket" fi SERVICE_GROUP="$(id -gn "${SERVICE_USER}")" install -d -m 0750 -o root -g "${SERVICE_GROUP}" "${ENV_DIR}" install -m 0640 -o root -g "${SERVICE_GROUP}" /dev/null "${ENV_FILE}" { printf 'DATA_BACKUP_ROOT=%s\n' "${BACKUP_ROOT}" printf 'DATA_BACKUP_PASSPHRASE_FILE=%s\n' "${PASSPHRASE_FILE}" printf 'DATA_BACKUP_RETENTION_DAYS=%s\n' "${RETENTION_DAYS}" printf 'MYSQL_DATA_DIR=%s\n' "${DATA_DIR}" printf 'MYSQL_STORAGE_ID=%s\n' "${STORAGE_ID}" if [[ -n "${DOCKER_SOCKET}" ]]; then printf 'DOCKER_HOST=%s\n' "${DOCKER_SOCKET}" fi } > "${ENV_FILE}" install -m 0644 /dev/null "${SERVICE_FILE}" { printf '%s\n' '[Unit]' printf '%s\n' 'Description=Encrypted MySQL backup for Nianxx Data Center' printf '%s\n' 'Wants=network-online.target' printf '%s\n' 'After=network-online.target docker.service' printf '\n%s\n' '[Service]' printf '%s\n' 'Type=oneshot' printf 'User=%s\n' "${SERVICE_USER}" printf 'Group=%s\n' "${SERVICE_GROUP}" printf 'WorkingDirectory=%s\n' "${PROJECT_DIR}" printf 'EnvironmentFile=%s\n' "${ENV_FILE}" printf 'ExecStart=%s/scripts/backup_mysql_encrypted.sh\n' "${PROJECT_DIR}" printf '%s\n' 'UMask=0077' printf '%s\n' 'NoNewPrivileges=true' printf '%s\n' 'PrivateTmp=true' printf '%s\n' 'ProtectSystem=full' printf '%s\n' 'ProtectHome=false' printf '%s\n' 'LockPersonality=true' } > "${SERVICE_FILE}" install -m 0644 /dev/null "${TIMER_FILE}" { printf '%s\n' '[Unit]' printf '%s\n' 'Description=Daily encrypted MySQL backup for Nianxx Data Center' printf '\n%s\n' '[Timer]' printf '%s\n' 'OnCalendar=*-*-* 02:17:00' printf '%s\n' 'RandomizedDelaySec=15m' printf '%s\n' 'Persistent=true' printf '%s\n' 'Unit=nianxx-mysql-backup.service' printf '\n%s\n' '[Install]' printf '%s\n' 'WantedBy=timers.target' } > "${TIMER_FILE}" systemctl daemon-reload systemctl start nianxx-mysql-backup.service systemctl enable --now nianxx-mysql-backup.timer echo "首次加密备份已验证,systemd每日备份已启用。" echo "查看定时器:systemctl list-timers nianxx-mysql-backup.timer" echo "查看日志:journalctl -u nianxx-mysql-backup.service" echo "确认异地同步后,才可在.env中设置 DATA_BACKUP_ENABLED=true。"