feat: harden data access and simplify user management

This commit is contained in:
xuelong committed 2026-08-25 21:02:30 -07:00
1 parent 3dd5731751
commit adb780bc82
51 files changed
+3239 -2121

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
#!/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。"