#!/usr/bin/env bash
# harden-base.sh — idempotent Debian/Ubuntu baseline hardening.
# Runs ON THE TARGET VPS (not the local workstation). Ports generic
# patterns from ~/Projects/vortex/control/setup/setup.sh:13-53 — strips
# Vortex-specific Xray/sing-box/Wireguard steps.
#
# USAGE
#   curl -fsSL <your-raw-url>/harden-base.sh | sudo bash -s -- [options]
#     OR
#   scp harden-base.sh keiadmin@host:/tmp/
#   ssh keiadmin@host "sudo bash /tmp/harden-base.sh"
#
# OPTIONS
#   --admin-user <name>     default: keiadmin
#   --ssh-port <n>          default: 22 (opens in ufw + enforces in sshd drop-in)
#   --allow-port <n/proto>  repeatable. e.g. --allow-port 443/tcp --allow-port 80/tcp
#   --no-caddy              skip Caddy install (default: skip — install via its own block)
#   --no-reboot             default (never reboots; surfaces needrestart hints only)
#   --skip <step>           repeatable; known steps: apt, ssh, ufw, fail2ban, auditd, unattended
#
# IDEMPOTENCY
#   Every step is `test → configure → reload`. Re-run = diff-and-apply.
#   Detects the sshd_config.d/99-kei.conf + audit rules files; overwrites with
#   known-good content; never destroys /etc/ssh/sshd_config itself.
#
# ENV
#   No secrets read. SECRETS SINGLE SOURCE (RULE 0.8) → harden-base does not
#   touch tokens/keys.
#
# EXIT
#   0 ok
#   1 usage / platform not supported
#   2 hardening step failed (stderr)

set -euo pipefail

log()  { printf '[%s] [harden-base] %s\n' "$(date '+%H:%M:%S')" "$*" >&2; }
die()  { log "ERROR: $*"; exit "${2:-2}"; }

# ------------------------------------------------------------------ args
ADMIN_USER="keiadmin"
SSH_PORT="22"
ALLOW_PORTS=()
SKIPS=()
NO_CADDY=1           # default on; Caddy install is its own block
while [ $# -gt 0 ]; do
  case "$1" in
    --admin-user)   ADMIN_USER="$2"; shift 2 ;;
    --ssh-port)     SSH_PORT="$2";   shift 2 ;;
    --allow-port)   ALLOW_PORTS+=("$2"); shift 2 ;;
    --no-caddy)     NO_CADDY=1; shift ;;
    --no-reboot)    shift ;;                 # accepted for clarity; we never auto-reboot
    --skip)         SKIPS+=("$2"); shift 2 ;;
    -h|--help) cat <<EOF >&2
harden-base.sh — Debian/Ubuntu baseline hardening.
OPTIONS
  --admin-user <name>      default: keiadmin
  --ssh-port <n>           default: 22
  --allow-port <n/proto>   repeatable (e.g. 443/tcp, 80/tcp)
  --no-caddy               (default) skip Caddy install
  --skip <step>            apt|ssh|ufw|fail2ban|auditd|unattended
EOF
      exit 0 ;;
    *) die "unknown flag '$1'" 1 ;;
  esac
done

# ------------------------------------------------------------------ guards
[ "$(id -u)" -eq 0 ] || die "must run as root (sudo)." 1
. /etc/os-release 2>/dev/null || die "cannot read /etc/os-release" 1
case "${ID:-}" in
  debian|ubuntu) : ;;
  *) die "only Debian/Ubuntu supported (got ID=${ID:-unknown})" 1 ;;
esac

skipped() {
  local step="$1"
  for s in "${SKIPS[@]}"; do [ "$s" = "$step" ] && return 0; done
  return 1
}

# ------------------------------------------------------------------ step: apt
step_apt() {
  skipped apt && { log "skip apt"; return; }
  log "apt update + base packages…"
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq \
    ufw fail2ban unattended-upgrades needrestart auditd audispd-plugins \
    curl wget jq ca-certificates openssh-server
}

# ------------------------------------------------------------------ step: admin user
step_admin_user() {
  if id "$ADMIN_USER" >/dev/null 2>&1; then
    log "user '$ADMIN_USER' exists"
  else
    log "creating '$ADMIN_USER' (sudo, bash, NOPASSWD)"
    useradd -m -s /bin/bash -G sudo "$ADMIN_USER"
    install -d -m 0700 -o "$ADMIN_USER" -g "$ADMIN_USER" "/home/$ADMIN_USER/.ssh"
  fi
  install -d -m 0755 /etc/sudoers.d
  cat >/etc/sudoers.d/90-keiadmin <<EOF
$ADMIN_USER ALL=(ALL) NOPASSWD:ALL
EOF
  chmod 0440 /etc/sudoers.d/90-keiadmin
  visudo -cf /etc/sudoers.d/90-keiadmin >/dev/null
}

# ------------------------------------------------------------------ step: ssh
step_ssh() {
  skipped ssh && { log "skip ssh"; return; }
  log "ssh: writing /etc/ssh/sshd_config.d/99-kei.conf (port=$SSH_PORT, user=$ADMIN_USER)…"
  install -d -m 0755 /etc/ssh/sshd_config.d
  cat >/etc/ssh/sshd_config.d/99-kei.conf <<EOF
# GENERATED by harden-base.sh — idempotent. Edit intent, re-run the script.
Port $SSH_PORT
Protocol 2
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
PermitEmptyPasswords no
UsePAM yes
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 20
AllowUsers $ADMIN_USER
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
ClientAliveInterval 120
ClientAliveCountMax 2
LogLevel VERBOSE
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,sntrup761x25519-sha512@openssh.com
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
EOF
  sshd -t
  systemctl reload ssh 2>/dev/null || systemctl reload sshd
}

# ------------------------------------------------------------------ step: ufw
step_ufw() {
  skipped ufw && { log "skip ufw"; return; }
  log "ufw: default-deny-in + allow-out + ssh rate-limit…"
  ufw --force reset >/dev/null
  ufw default deny  incoming
  ufw default allow outgoing
  ufw default deny  routed
  ufw limit  "$SSH_PORT/tcp" comment "ssh (rate-limited)"
  for p in "${ALLOW_PORTS[@]}"; do
    log "  allow $p"
    ufw allow "$p"
  done
  ufw logging medium
  ufw --force enable
}

# ------------------------------------------------------------------ step: fail2ban
step_fail2ban() {
  skipped fail2ban && { log "skip fail2ban"; return; }
  log "fail2ban: writing /etc/fail2ban/jail.local (sshd jail)…"
  cat >/etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime  = 3600
findtime = 600
maxretry = 5
backend  = systemd

[sshd]
enabled = true
port    = $SSH_PORT
EOF
  systemctl enable --now fail2ban
  systemctl restart fail2ban
}

# ------------------------------------------------------------------ step: auditd
step_auditd() {
  skipped auditd && { log "skip auditd"; return; }
  log "auditd: writing /etc/audit/rules.d/99-kei.rules…"
  install -d -m 0750 /etc/audit/rules.d
  cat >/etc/audit/rules.d/99-kei.rules <<'EOF'
# GENERATED by harden-base.sh — idempotent baseline.
-w /etc/ssh/sshd_config        -p wa -k sshd_config
-w /etc/ssh/sshd_config.d/     -p wa -k sshd_config
-w /root/.ssh/                 -p wa -k ssh_keys_root
-w /etc/sudoers                -p wa -k sudoers
-w /etc/sudoers.d/             -p wa -k sudoers
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k sudo_root
-w /etc/passwd                 -p wa -k identity
-w /etc/group                  -p wa -k identity
-w /etc/shadow                 -p wa -k identity
-w /etc/gshadow                -p wa -k identity
-a always,exit -F arch=b64 -S init_module -S finit_module -S delete_module -k module
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time
-w /etc/localtime              -p wa -k time
-e 2
EOF
  augenrules --load >/dev/null
  systemctl enable --now auditd
}

# ------------------------------------------------------------------ step: unattended-upgrades + needrestart
step_unattended() {
  skipped unattended && { log "skip unattended"; return; }
  log "unattended-upgrades + needrestart (list-only)…"
  cat >/etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade  "1";
APT::Periodic::AutocleanInterval   "7";
EOF
  cat >/etc/apt/apt.conf.d/50unattended-upgrades.kei <<'EOF'
Unattended-Upgrade::Origins-Pattern {
    "origin=Debian,codename=${distro_codename}-security";
    "origin=Debian,codename=${distro_codename}-updates";
    "origin=Ubuntu,archive=${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::MailReport "on-change";
EOF
  # needrestart: list services, suppress TTY prompts (non-TTY cron safe).
  if [ -f /etc/needrestart/needrestart.conf ]; then
    sed -i "s/^#\?\$nrconf{restart}.*/\$nrconf{restart} = 'l';/" /etc/needrestart/needrestart.conf
    sed -i "s/^#\?\$nrconf{kernelhints}.*/\$nrconf{kernelhints} = -1;/" /etc/needrestart/needrestart.conf
  fi
}

# ------------------------------------------------------------------ main
log "start: admin=$ADMIN_USER ssh=$SSH_PORT extra-ports=${ALLOW_PORTS[*]:-none}"
step_apt
step_admin_user
step_ssh
step_ufw
step_fail2ban
step_auditd
step_unattended
log "done. Next: verify via _primitives/_rust/ssh-check + firewall-diff."
