← All four scripts

lib.sh

Shared helpers: dry-run handling, idempotent writes, and the rollback timer the two risky scripts depend on. Sourced, never run directly.

Read this, do not pipe it. These scripts run as root. Copy the source into a file on your own machine, read it, and run it yourself. Nobody should curl a root shell script straight off a website, including this one.

#!/usr/bin/env bash # Shared helpers for the harden-*.sh scripts. # Sourced, never executed directly. set -Eeuo pipefail RED=$'\033[31m'; GRN=$'\033[32m'; YLW=$'\033[33m'; BLU=$'\033[34m'; DIM=$'\033[2m'; RST=$'\033[0m' DRY_RUN="${DRY_RUN:-0}" CHANGED=0 SKIPPED=0 FAILED=0 # Optional site-specific overrides (APP_USER, SANDBOX_SERVICES, AGENT_USERS, …). # Put local values in harden.local.conf rather than editing the scripts, so the # scripts stay generic. Untracked. An explicit if-block, not `[ -f ] && source`: # under `set -e` a false test as the last statement makes `source lib.sh` fail. if [ -f ./harden.local.conf ]; then # shellcheck source=/dev/null . ./harden.local.conf fi log() { printf '%s\n' "$*"; } info() { printf '%s→%s %s\n' "$BLU" "$RST" "$*"; } ok() { printf '%s✓%s %s\n' "$GRN" "$RST" "$*"; SKIPPED=$((SKIPPED+1)); } did() { printf '%s✚%s %s\n' "$GRN" "$RST" "$*"; CHANGED=$((CHANGED+1)); } warn() { printf '%s!%s %s\n' "$YLW" "$RST" "$*"; } err() { printf '%s✗%s %s\n' "$RED" "$RST" "$*" >&2; FAILED=$((FAILED+1)); } head1(){ printf '\n%s══ %s ══%s\n' "$BLU" "$*" "$RST"; } # Fail loudly with the failing line, rather than silently continuing. trap 'err "line $LINENO: command failed (exit $?)"' ERR need_root() { [ "$(id -u)" -eq 0 ] || { err "must run as root (use sudo)"; exit 1; } } # run <description> <command...> — honours DRY_RUN run() { local desc="$1"; shift if [ "$DRY_RUN" = "1" ]; then printf '%s[dry-run]%s %s %s(%s)%s\n' "$YLW" "$RST" "$desc" "$DIM" "$*" "$RST" return 0 fi if "$@"; then did "$desc"; else err "$desc"; return 1; fi } # Idempotently ensure "key value" exists in a sysctl-style file. # ensure_kv <file> <key> <value> [separator] ensure_kv() { local file="$1" key="$2" val="$3" sep="${4:-=}" local line="${key}${sep}${val}" if [ -f "$file" ] && grep -qE "^[[:space:]]*${key}[[:space:]]*${sep}[[:space:]]*${val}[[:space:]]*$" "$file"; then ok "$key already $val in $(basename "$file")" return 0 fi if [ "$DRY_RUN" = "1" ]; then printf '%s[dry-run]%s would set %s in %s\n' "$YLW" "$RST" "$line" "$file" return 0 fi mkdir -p "$(dirname "$file")" touch "$file" # Replace an existing key line if present, else append. if grep -qE "^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*${sep}" "$file"; then sed -i -E "s|^[[:space:]]*#?[[:space:]]*${key}[[:space:]]*${sep}.*|${line}|" "$file" else printf '%s\n' "$line" >> "$file" fi did "set $line in $(basename "$file")" } # Write a file only if the content differs. Keeps a .bak on first change. # write_file <path> <mode> <<<"content" write_file() { local path="$1" mode="${2:-0644}" content content="$(cat)" if [ -f "$path" ] && [ "$(cat "$path")" = "$content" ]; then ok "$(basename "$path") already correct" return 0 fi if [ "$DRY_RUN" = "1" ]; then printf '%s[dry-run]%s would write %s (%s bytes)\n' "$YLW" "$RST" "$path" "${#content}" return 0 fi mkdir -p "$(dirname "$path")" [ -f "$path" ] && [ ! -f "${path}.bak" ] && cp -a "$path" "${path}.bak" printf '%s\n' "$content" > "$path" chmod "$mode" "$path" did "wrote $path" } pkg_installed() { dpkg -s "$1" >/dev/null 2>&1; } ensure_pkg() { local p for p in "$@"; do if pkg_installed "$p"; then ok "$p installed" else run "install $p" apt-get install -y -q "$p" >/dev/null; fi done } summary() { printf '\n%s──────────────────────────────%s\n' "$DIM" "$RST" printf 'changed: %s%d%s already-ok: %s%d%s failed: %s%d%s\n' \ "$GRN" "$CHANGED" "$RST" "$DIM" "$SKIPPED" "$RST" \ "$([ "$FAILED" -gt 0 ] && echo "$RED" || echo "$DIM")" "$FAILED" "$RST" [ "$DRY_RUN" = "1" ] && printf '%sDRY RUN — nothing was changed%s\n' "$YLW" "$RST" return 0 } # ── Dead man's switch ──────────────────────────────────────────────────────── # Schedules an automatic undo. If you lock yourself out, the box heals itself. # rollback_arm <unit-name> <minutes> <command...> rollback_arm() { local unit="$1" mins="$2"; shift 2 systemctl stop "${unit}.timer" 2>/dev/null || true systemd-run --collect --on-active="${mins}min" --unit="$unit" "$@" >/dev/null warn "ROLLBACK ARMED: '$*' runs in ${mins} min unless cancelled" } rollback_disarm() { local unit="$1" systemctl stop "${unit}.timer" 2>/dev/null || true systemctl reset-failed "$unit" 2>/dev/null || true did "rollback cancelled ($unit)" } # Blocks until the operator confirms from a SEPARATE session. confirm_still_connected() { local what="$1" cat <<EOF ${YLW}┌─────────────────────────────────────────────────────────────┐ │ VERIFY NOW, FROM A SECOND TERMINAL │ │ │ │ ${what} │ │ │ Do NOT use this session. Open a NEW one. │ │ If it fails, do nothing — the rollback fires automatically. │ └─────────────────────────────────────────────────────────────┘${RST} EOF local ans="" read -r -p "Confirmed working from a NEW session? type 'yes': " ans [ "$ans" = "yes" ] }