建立每日自动异机备份基线(07-27-automatic-offhost-backup)
这个提交包含在:
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_USER="${BACKUP_USER:-hybbackup}"
|
||||
BACKUP_ROOT="${BACKUP_ROOT:-}"
|
||||
PUBLIC_KEY_FILE="${PUBLIC_KEY_FILE:-}"
|
||||
RECEIVER_SOURCE="${RECEIVER_SOURCE:-}"
|
||||
RECEIVER_TARGET="${RECEIVER_TARGET:-/usr/local/sbin/hyb-oa-offhost-receiver}"
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/hyb-oa-offhost-backup.conf}"
|
||||
DAILY_KEEP="${DAILY_KEEP:-7}"
|
||||
WEEKLY_KEEP="${WEEKLY_KEEP:-4}"
|
||||
MODE="${1:-install}"
|
||||
|
||||
fail() {
|
||||
echo "[offhost-receiver-installer][ERROR] $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
need_root() {
|
||||
[[ "$(id -u)" == "0" ]] || fail "root privileges are required"
|
||||
}
|
||||
|
||||
validate_absolute_path() {
|
||||
local name="$1"
|
||||
local value="$2"
|
||||
if [[ -z "$value" || "$value" != /* || ! "$value" =~ ^/[A-Za-z0-9._/-]+$ ]]; then
|
||||
fail "$name must be an explicit safe absolute path"
|
||||
fi
|
||||
if [[ "$value" == "/" || "$value" == */ || "$value" == *"//"* \
|
||||
|| "/$value/" == *"/../"* || "/$value/" == *"/./"* ]]; then
|
||||
fail "$name contains an unsafe path segment"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_backup_root() {
|
||||
validate_absolute_path BACKUP_ROOT "$BACKUP_ROOT"
|
||||
[[ "$BACKUP_ROOT" =~ ^/[^/]+/[^/]+ ]] \
|
||||
|| fail "backup root must be a dedicated path below a top-level directory"
|
||||
case "$BACKUP_ROOT" in
|
||||
/bin/*|/boot/*|/dev/*|/etc/*|/lib/*|/lib64/*|/proc/*|/run/*|/sbin/*|/sys/*|/usr/*)
|
||||
fail "backup root must not be inside a system-managed directory"
|
||||
;;
|
||||
esac
|
||||
[[ ! -L "$BACKUP_ROOT" ]] || fail "backup root must not be a symbolic link"
|
||||
if [[ -d "$BACKUP_ROOT" ]]; then
|
||||
local unmanaged_entry
|
||||
unmanaged_entry="$(find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 \
|
||||
! -name inbox ! -name archive ! -name weekly -print -quit)"
|
||||
[[ -z "$unmanaged_entry" ]] || fail "backup root contains an unmanaged top-level entry"
|
||||
fi
|
||||
}
|
||||
|
||||
validate_inputs() {
|
||||
[[ "$BACKUP_USER" =~ ^[a-z_][a-z0-9_-]{0,30}$ ]] || fail "backup user is invalid"
|
||||
validate_backup_root
|
||||
validate_absolute_path PUBLIC_KEY_FILE "$PUBLIC_KEY_FILE"
|
||||
validate_absolute_path RECEIVER_SOURCE "$RECEIVER_SOURCE"
|
||||
validate_absolute_path RECEIVER_TARGET "$RECEIVER_TARGET"
|
||||
validate_absolute_path CONFIG_FILE "$CONFIG_FILE"
|
||||
[[ -f "$PUBLIC_KEY_FILE" && ! -L "$PUBLIC_KEY_FILE" ]] || fail "public key file is missing"
|
||||
[[ -f "$RECEIVER_SOURCE" && ! -L "$RECEIVER_SOURCE" ]] || fail "receiver source is missing"
|
||||
[[ "$DAILY_KEEP" =~ ^[1-9][0-9]*$ && "$WEEKLY_KEEP" =~ ^[1-9][0-9]*$ ]] \
|
||||
|| fail "retention values must be positive"
|
||||
command -v rsync >/dev/null 2>&1 || fail "rsync must be installed before receiver setup"
|
||||
command -v rrsync >/dev/null 2>&1 || fail "rrsync must be installed before receiver setup"
|
||||
command -v python3 >/dev/null 2>&1 || fail "python3 is required"
|
||||
}
|
||||
|
||||
read_public_key() {
|
||||
local key_type key_body remainder extra_line_count
|
||||
extra_line_count="$(awk 'NF { count += 1 } END { print count + 0 }' "$PUBLIC_KEY_FILE")"
|
||||
[[ "$extra_line_count" == "1" ]] || fail "public key file must contain exactly one non-empty key"
|
||||
read -r key_type key_body remainder <"$PUBLIC_KEY_FILE"
|
||||
[[ "$key_type" == "ssh-ed25519" || "$key_type" == "[email protected]" ]] \
|
||||
|| fail "only Ed25519 public keys are accepted"
|
||||
[[ "$key_body" =~ ^[A-Za-z0-9+/=]+$ ]] || fail "public key body is invalid"
|
||||
printf '%s %s' "$key_type" "$key_body"
|
||||
}
|
||||
|
||||
install_receiver() {
|
||||
need_root
|
||||
validate_inputs
|
||||
|
||||
if ! id "$BACKUP_USER" >/dev/null 2>&1; then
|
||||
useradd --create-home --shell /bin/bash "$BACKUP_USER"
|
||||
fi
|
||||
passwd -l "$BACKUP_USER" >/dev/null 2>&1 || true
|
||||
|
||||
local home_dir inbox_dir archive_dir weekly_dir ssh_dir auth_file public_key rrsync_bin
|
||||
home_dir="$(getent passwd "$BACKUP_USER" | awk -F: '{print $6}')"
|
||||
validate_absolute_path BACKUP_USER_HOME "$home_dir"
|
||||
inbox_dir="$BACKUP_ROOT/inbox"
|
||||
archive_dir="$BACKUP_ROOT/archive"
|
||||
weekly_dir="$BACKUP_ROOT/weekly"
|
||||
mkdir -p "$inbox_dir" "$archive_dir" "$weekly_dir" "$home_dir/.ssh"
|
||||
[[ -d "$BACKUP_ROOT" && ! -L "$BACKUP_ROOT" \
|
||||
&& -d "$inbox_dir" && ! -L "$inbox_dir" \
|
||||
&& -d "$archive_dir" && ! -L "$archive_dir" \
|
||||
&& -d "$weekly_dir" && ! -L "$weekly_dir" ]] \
|
||||
|| fail "backup receiver directories must not be symbolic links"
|
||||
chown -R "$BACKUP_USER:$BACKUP_USER" "$BACKUP_ROOT" "$home_dir/.ssh"
|
||||
chmod 0700 "$BACKUP_ROOT" "$inbox_dir" "$archive_dir" "$weekly_dir" "$home_dir/.ssh"
|
||||
|
||||
install -o root -g root -m 0755 "$RECEIVER_SOURCE" "$RECEIVER_TARGET"
|
||||
rrsync_bin="$(command -v rrsync)"
|
||||
cat >"$CONFIG_FILE" <<CONFIG
|
||||
BACKUP_INBOX_DIR=${inbox_dir}
|
||||
BACKUP_ARCHIVE_DIR=${archive_dir}
|
||||
BACKUP_WEEKLY_DIR=${weekly_dir}
|
||||
RRSYNC_BIN=${rrsync_bin}
|
||||
PYTHON_BIN=python3
|
||||
DAILY_KEEP=${DAILY_KEEP}
|
||||
WEEKLY_KEEP=${WEEKLY_KEEP}
|
||||
CONFIG
|
||||
chown root:root "$CONFIG_FILE"
|
||||
chmod 0644 "$CONFIG_FILE"
|
||||
|
||||
ssh_dir="$home_dir/.ssh"
|
||||
auth_file="$ssh_dir/authorized_keys"
|
||||
if [[ -s "$auth_file" ]] && ! grep -Fq 'hyb-oa-managed-offhost-backup' "$auth_file"; then
|
||||
fail "dedicated backup account already has unmanaged authorized keys"
|
||||
fi
|
||||
public_key="$(read_public_key)"
|
||||
cat >"$auth_file" <<AUTH
|
||||
# hyb-oa-managed-offhost-backup
|
||||
command="${RECEIVER_TARGET}",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty,no-user-rc ${public_key}
|
||||
AUTH
|
||||
chown "$BACKUP_USER:$BACKUP_USER" "$auth_file"
|
||||
chmod 0600 "$auth_file"
|
||||
|
||||
runuser -u "$BACKUP_USER" -- env RECEIVER_CONFIG_FILE="$CONFIG_FILE" \
|
||||
"$RECEIVER_TARGET" validate-config >/dev/null
|
||||
echo "[offhost-receiver-installer][PASS] restricted receiver installed"
|
||||
}
|
||||
|
||||
uninstall_receiver() {
|
||||
need_root
|
||||
[[ "$BACKUP_USER" =~ ^[a-z_][a-z0-9_-]{0,30}$ ]] || fail "backup user is invalid"
|
||||
validate_absolute_path RECEIVER_TARGET "$RECEIVER_TARGET"
|
||||
validate_absolute_path CONFIG_FILE "$CONFIG_FILE"
|
||||
if id "$BACKUP_USER" >/dev/null 2>&1; then
|
||||
local home_dir auth_file
|
||||
home_dir="$(getent passwd "$BACKUP_USER" | awk -F: '{print $6}')"
|
||||
auth_file="$home_dir/.ssh/authorized_keys"
|
||||
if [[ -f "$auth_file" ]] && grep -Fq 'hyb-oa-managed-offhost-backup' "$auth_file"; then
|
||||
rm -f -- "$auth_file"
|
||||
fi
|
||||
fi
|
||||
rm -f -- "$RECEIVER_TARGET" "$CONFIG_FILE"
|
||||
echo "[offhost-receiver-installer][PASS] receiver authorization removed; archives retained"
|
||||
}
|
||||
|
||||
case "$MODE" in
|
||||
install)
|
||||
install_receiver
|
||||
;;
|
||||
uninstall|--uninstall)
|
||||
uninstall_receiver
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: BACKUP_ROOT=<absolute> PUBLIC_KEY_FILE=<absolute> RECEIVER_SOURCE=<absolute> bash install-offhost-backup-receiver.sh [install|uninstall]"
|
||||
;;
|
||||
*)
|
||||
fail "unsupported mode"
|
||||
;;
|
||||
esac
|
||||
在新工单中引用
屏蔽一个用户