109 lines
4.6 KiB
Bash
Executable File
109 lines
4.6 KiB
Bash
Executable File
#!/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。"
|