#!/usr/bin/env bash # tools/cmd/railiance-backup-s1 — S1 OS & Provisioning backup # Backs up: key OS config files applied by Ansible (sshd, ufw, fail2ban, hosts) # Encryption: age (reuses SOPS key pair from .sops.yaml) # Output: /opt/backup/railiance/infra/ # No network required. Requires root to read /etc/. set -euo pipefail # ── Configuration ────────────────────────────────────────────────────────────── AGE_PUBLIC_KEY="age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4" BACKUP_DIR="/opt/backup/railiance/infra" KEEP=7 TS="$(date -u +%Y%m%dT%H%M%SZ)" # Colour helpers (no external dependency) ok() { printf " ✅ %-12s %s\n" "$1" "$2"; } warn() { printf " ⚠️ %-12s %s\n" "$1" "$2"; } bad() { printf " ❌ %-12s %s\n" "$1" "$2"; } mkdir -p "${BACKUP_DIR}" printf "\nrailiance-infra (S1) backup — %s\n" "${TS}" printf "%0.s-" $(seq 1 44); echo # ── Root check ───────────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then bad "root" "this script requires root — run via: sudo make backup" exit 1 fi # ── OS config snapshot ───────────────────────────────────────────────────────── # Captures the live state of files that Ansible manages. # These may drift from the playbooks if manual changes were made. ok "os-config" "snapshotting /etc config…" OS_FILES=( /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ /etc/ufw/ufw.conf /etc/ufw/user.rules /etc/ufw/user6.rules /etc/fail2ban/jail.local /etc/fail2ban/jail.d/ /etc/hosts /etc/hostname /etc/apt/sources.list.d/ ) TMP_OS="$(mktemp -d)" for item in "${OS_FILES[@]}"; do [[ -e "${item}" ]] || continue dest="${TMP_OS}$(dirname "${item}")" mkdir -p "${dest}" cp -a "${item}" "${dest}/" 2>/dev/null || true done tar -czf - -C "${TMP_OS}" . \ | age -r "${AGE_PUBLIC_KEY}" -o "${BACKUP_DIR}/os-config-${TS}.tar.gz.age" rm -rf "${TMP_OS}" ok "os-config" "encrypted → os-config-${TS}.tar.gz.age" # ── Installed packages list ──────────────────────────────────────────────────── if command -v dpkg &>/dev/null; then dpkg --get-selections \ | age -r "${AGE_PUBLIC_KEY}" -o "${BACKUP_DIR}/packages-${TS}.txt.age" ok "packages" "encrypted → packages-${TS}.txt.age" fi # ── Prune local cache ────────────────────────────────────────────────────────── for pattern in "os-config-*.tar.gz.age" "packages-*.txt.age"; do find "${BACKUP_DIR}" -name "${pattern}" | sort -r | tail -n +$((KEEP + 1)) | xargs -r rm -f done ok "prune" "kept last ${KEEP} of each type" # ── Stamp ────────────────────────────────────────────────────────────────────── echo "${TS}" > "${BACKUP_DIR}/.last-backup" echo ok "done" "backup complete — ${TS}" echo " Location: ${BACKUP_DIR}" echo " Decrypt with: age -d -i ~/.config/sops/age/keys.txt "