feat: harden data access and simplify user management
This commit is contained in:
1 parent
3dd5731751
commit
adb780bc82
51 files changed
+3239
-2121
No files matched your search
Executable
+108
@@ -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。"
|
||||
Reference in new issue
Block a user