#!/usr/bin/env bash set -euo pipefail log_info(){ echo "[INFO] $(date -Iseconds) $*"; } log_warn(){ echo "[WARN] $(date -Iseconds) $*" >&2; } log_error(){ echo "[ERROR] $(date -Iseconds) $*" >&2; } die(){ log_error "$*"; exit 1; } need(){ command -v "$1" >/dev/null 2>&1 || die "Missing required tool: $1"; } confirm_gate() { : "${DRY_RUN:=1}" : "${REQUIRE_CONFIRM:=1}" : "${CONFIRM_PHRASE:=I UNDERSTAND THIS CAN AFFECT REMOTE ACCESS}" [[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0)." if [[ "$REQUIRE_CONFIRM" == "1" ]]; then echo "Type to confirm:" echo " $CONFIRM_PHRASE" echo read -r -p "> " typed [[ "$typed" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase did not match." fi } require_root() { [[ "$(id -u)" == "0" ]] || die "Run as root." } backup_file() { local src="$1" backup_dir="$2" mkdir -p "$backup_dir" if [[ -f "$src" ]]; then local ts; ts="$(date -Iseconds | tr ':' '-')" cp -p "$src" "$backup_dir/$(basename "$src").${ts}.bak" fi } os_codename() { # Prefer /etc/os-release (present on Debian/Ubuntu). local code="" if [[ -r /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release code="${VERSION_CODENAME:-}" fi if [[ -z "$code" ]] && command -v lsb_release >/dev/null 2>&1; then code="$(lsb_release -cs 2>/dev/null || true)" fi echo "$code" } ssh_service_name() { # Debian/Ubuntu commonly use "ssh.service". Some distros use "sshd.service". if systemctl status ssh >/dev/null 2>&1; then echo "ssh" return 0 fi if systemctl status sshd >/dev/null 2>&1; then echo "sshd" return 0 fi # Fallback echo "ssh" } reload_ssh_service() { local svc; svc="$(ssh_service_name)" systemctl reload "$svc" >/dev/null 2>&1 || systemctl restart "$svc" >/dev/null 2>&1 || true } restart_ssh_service() { local svc; svc="$(ssh_service_name)" systemctl restart "$svc" >/dev/null 2>&1 || true } is_ssh_active() { local svc; svc="$(ssh_service_name)" systemctl is-active "$svc" >/dev/null 2>&1 }