#!/usr/bin/env bash # # Dezky production host bootstrap — OS hardening for the AX41 k3s node. # # Run ONCE on a fresh Debian 12 (bookworm) install, as root, e.g.: # scp -r infrastructure/production/host root@:/opt/dezky-host # ssh root@ 'cd /opt/dezky-host && cp config.env.example config.env && nano config.env' # ssh root@ 'cd /opt/dezky-host && ./bootstrap.sh' # # Order matters: we create your admin user + install your SSH key BEFORE # disabling root/password login, so you can't lock yourself out. The script # is idempotent — safe to re-run. # # What it does NOT do: install k3s, Stalwart, or backups. Those are separate # steps in this host/ layer (added next). This is OS baseline + firewall only. set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="$SCRIPT_DIR/config.env" echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Dezky Production Host Bootstrap (Debian 12) ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" # ── Preflight ────────────────────────────────────────────────────────────── if [[ $EUID -ne 0 ]]; then error "Run as root (you'll create the unprivileged admin user from here)." exit 1 fi if [[ ! -f "$CONFIG_FILE" ]]; then error "Missing $CONFIG_FILE — copy config.env.example and fill it in." exit 1 fi # shellcheck disable=SC1090 source "$CONFIG_FILE" : "${ADMIN_USER:?ADMIN_USER required}" : "${ADMIN_SSH_PUBKEY:?ADMIN_SSH_PUBKEY required — without it you would lock yourself out}" : "${MGMT_ALLOW_V4:?MGMT_ALLOW_V4 required}" : "${SERVER_HOSTNAME:?SERVER_HOSTNAME required}" : "${SSH_PORT:=22}" if [[ "$ADMIN_SSH_PUBKEY" != ssh-* ]]; then error "ADMIN_SSH_PUBKEY doesn't look like a public key (should start with 'ssh-')." exit 1 fi # ── Step 1: base packages + system upgrade ───────────────────────────────── info "Step 1: Updating system and installing base packages..." export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get upgrade -y -qq apt-get install -y -qq \ nftables fail2ban unattended-upgrades apt-listchanges \ curl ca-certificates gnupg htop tmux vim chrony \ >/dev/null ok "Base packages installed." # ── Step 2: hostname + timezone + time sync ──────────────────────────────── info "Step 2: Hostname, timezone (UTC), time sync..." hostnamectl set-hostname "$SERVER_HOSTNAME" timedatectl set-timezone UTC systemctl enable --now chrony >/dev/null 2>&1 || true # Ensure the FQDN resolves locally if ! grep -q "$SERVER_HOSTNAME" /etc/hosts; then echo "127.0.1.1 ${SERVER_HOSTNAME} ${SERVER_HOSTNAME%%.*}" >> /etc/hosts fi ok "Hostname set to $SERVER_HOSTNAME (UTC)." # ── Step 3: admin user + SSH key (BEFORE locking SSH) ────────────────────── info "Step 3: Admin user '$ADMIN_USER' + SSH key..." if ! id -u "$ADMIN_USER" >/dev/null 2>&1; then adduser --disabled-password --gecos "" "$ADMIN_USER" fi usermod -aG sudo "$ADMIN_USER" install -d -m 0700 -o "$ADMIN_USER" -g "$ADMIN_USER" "/home/$ADMIN_USER/.ssh" AUTH_KEYS="/home/$ADMIN_USER/.ssh/authorized_keys" touch "$AUTH_KEYS" grep -qxF "$ADMIN_SSH_PUBKEY" "$AUTH_KEYS" || echo "$ADMIN_SSH_PUBKEY" >> "$AUTH_KEYS" chmod 0600 "$AUTH_KEYS" chown "$ADMIN_USER:$ADMIN_USER" "$AUTH_KEYS" # Passworded sudo (member of sudo group). Set a password manually later if you # want interactive sudo: `passwd $ADMIN_USER`. Key-only login still works. ok "Admin user ready with your SSH key." # ── Step 4: SSH hardening (drop-in) ──────────────────────────────────────── info "Step 4: Hardening SSH..." SSHD_DROPIN="/etc/ssh/sshd_config.d/99-dezky.conf" cat > "$SSHD_DROPIN" </dev/null || systemctl reload sshd 2>/dev/null || true ok "SSH hardened: key-only, no root, AllowUsers=${ADMIN_USER}, port ${SSH_PORT}." else error "sshd config test FAILED — removing drop-in, leaving SSH as-is." rm -f "$SSHD_DROPIN" exit 1 fi # ── Step 5: kernel sysctl for k3s + sane limits ──────────────────────────── info "Step 5: sysctl + kernel modules for k3s..." modprobe br_netfilter 2>/dev/null || true modprobe overlay 2>/dev/null || true cat > /etc/modules-load.d/dezky-k3s.conf < /etc/sysctl.d/99-dezky-k3s.conf < raise inotify + file limits fs.inotify.max_user_instances = 8192 fs.inotify.max_user_watches = 524288 fs.file-max = 2097152 EOF sysctl --system >/dev/null ok "sysctl applied." # ── Step 6: disable swap (kubelet best practice) ─────────────────────────── info "Step 6: Disabling swap (recommended for k3s nodes)..." swapoff -a || true # Comment any swap entries so it stays off across reboots sed -i.bak -E 's@^([^#].*\sswap\s.*)$@# \1 # disabled by dezky bootstrap@' /etc/fstab || true ok "Swap disabled." # ── Step 7: fail2ban (ssh) ───────────────────────────────────────────────── info "Step 7: fail2ban for SSH..." cat > /etc/fail2ban/jail.d/dezky-sshd.local </dev/null 2>&1 || true systemctl restart fail2ban >/dev/null 2>&1 || true ok "fail2ban active on SSH." # ── Step 8: unattended security upgrades ─────────────────────────────────── info "Step 8: Enabling unattended security upgrades..." cat > /etc/apt/apt.conf.d/20auto-upgrades </dev/null 2>&1 || true CONFIG_FILE="$CONFIG_FILE" "$SCRIPT_DIR/firewall/firewall.sh" echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Host bootstrap complete ║" echo "╚══════════════════════════════════════════════════════════════╝" warn "BEFORE you close this root session:" warn " 1. Open a new terminal and run: ssh -p ${SSH_PORT} ${ADMIN_USER}@${SERVER_PUBLIC_IPV4:-}" warn " 2. Confirm you get in with your key." warn " 3. Only then close this session. KVM/LARA is your fallback if not." echo "" info "Next host-layer steps (separate scripts, added next): k3s registration," info "Stalwart mail, Restic backups."