#!/usr/bin/env bash
# ArchForge External Status — Debian 13 standalone installer.
# Run from your ArchForge checkout: sudo ./install.sh
set -Eeuo pipefail
umask 027
abort(){ echo "[ERROR] $*" >&2;exit 1; }
[[ $EUID -eq 0 ]] || abort 'Run sudo ./install.sh'
[[ -f /etc/os-release ]] || abort 'Cannot determine operating system'
source /etc/os-release
[[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || abort 'Only Debian 13 (trixie) is supported by the automatic installer'
[[ -t 0 ]] || abort 'Use an interactive terminal to run the setup wizard'
SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
[[ -f "$SRC/package.json" && -f "$SRC/src/server.js" ]] || abort 'Run installer from the project root'
[[ "$SRC" != /opt/archforge-status/app ]] || abort 'Run from the ArchForge checkout, not installed app directory'
if [[ -f /etc/systemd/system/archforge-status.service ]] && systemctl is-active --quiet archforge-status; then
  echo '[*] Existing service detected. Checking/recovering admin setup only; no reinstall.'
  exec bash "$SRC/recover-admin.sh"
fi
printf '\n══════════════════════════════════════════════\n ArchForge External Status 1.0.3 — Debian 13\n Independent ArchForge-installed status monitoring\n══════════════════════════════════════════════\n\n'
echo 'This installer installs Node.js 24, Caddy, SQLite, msmtp, and a systemd service.'
echo 'The existing archforge.nl website and MySQL database will NOT be changed.'
read -rp 'Continue? Type INSTALL: ' OK
[[ "$OK" == INSTALL ]] || abort 'Cancelled'
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y ca-certificates curl gnupg git rsync sqlite3 caddy msmtp python3
NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)
if (( NODE_MAJOR < 24 )); then
  echo '[*] Installing Node.js 24 from the signed NodeSource apt repository'
  install -d -m 755 /etc/apt/keyrings
  curl --fail --silent --show-error --location https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
  chmod 644 /etc/apt/keyrings/nodesource.gpg
  ARCH=$(dpkg --print-architecture)
  printf 'deb [arch=%s signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main\n' "$ARCH" > /etc/apt/sources.list.d/nodesource.list
  apt-get update
  apt-get install -y nodejs
fi
NODE_MAJOR=$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || echo 0)
(( NODE_MAJOR >= 24 )) || abort 'Node.js 24+ is required'
command -v caddy >/dev/null || abort 'Caddy installation failed'
command -v sqlite3 >/dev/null || abort 'SQLite installation failed'
command -v msmtp >/dev/null || abort 'msmtp installation failed'
if ! id -u archforge-status >/dev/null 2>&1; then
  useradd --system --user-group --home-dir /var/lib/archforge-status --shell /usr/sbin/nologin archforge-status
fi
install -d -m 755 /opt/archforge-status /opt/archforge-status/app
install -d -o archforge-status -g archforge-status -m 750 /var/lib/archforge-status
install -d -m 750 /etc/archforge-status
chown root:archforge-status /etc/archforge-status
install -d -m 700 /var/backups/archforge-status
rsync -a --delete --exclude='.git' --exclude='.env' --exclude='data' --exclude='node_modules' --exclude='backups' --chmod=D755,F644 "$SRC/" /opt/archforge-status/app/
chown -R root:root /opt/archforge-status/app
echo '[*] Running package validation before service installation'
(cd /opt/archforge-status/app && /usr/bin/node --check src/server.js && /usr/bin/node --test tests/*.test.js) || abort 'Application self-tests failed; installation halted'
chmod 755 /opt/archforge-status /opt/archforge-status/app
printf '%s\n' "$SRC" > /etc/archforge-status/source-path
chmod 600 /etc/archforge-status/source-path
CFG=/etc/archforge-status/status.env
if [[ -f "$CFG" ]]; then
  echo '[*] Existing configuration preserved (no secret rotation)'
else
  echo
  echo '--- Domain and monitored targets ---'
  read -rp 'Public status domain [status.archforge.nl]: ' DOMAIN; DOMAIN=${DOMAIN:-status.archforge.nl}
  [[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]{0,251}[a-zA-Z0-9])?$ && "$DOMAIN" == *.* ]] || abort 'Invalid domain'
  read -rp 'Main website HTTPS URL [https://archforge.nl]: ' WEBSITE; WEBSITE=${WEBSITE:-https://archforge.nl}
  [[ "$WEBSITE" =~ ^https://[a-zA-Z0-9.-]+/?$ ]] || abort 'Use https://example.com for website'
  read -rp 'Optional public API URL to monitor (Enter to disable): ' API
  read -rp 'Public ArchForge HTTPS URL ending /info/refs (leave empty to disable): ' GIT_HTTP
  read -rp 'ArchForge SSH host [archforge.nl]: ' SSH_HOST; SSH_HOST=${SSH_HOST:-archforge.nl}
  [[ "$SSH_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid ArchForge SSH host'
  read -rp 'ArchForge SSH port [22]: ' SSH_PORT; SSH_PORT=${SSH_PORT:-22}
  [[ "$SSH_PORT" =~ ^[0-9]{1,5}$ && "$SSH_PORT" -ge 1 && "$SSH_PORT" -le 65535 ]] || abort 'Invalid SSH port'
  echo
  echo '--- Notifications (optional, configurable later) ---'
  read -rp 'Discord webhook URL (Enter to skip): ' DISCORD
  [[ -z "$DISCORD" || "$DISCORD" =~ ^https://(discord.com|discordapp.com)/api/webhooks/ ]] || abort 'Discord URL must be an official webhook URL'
  read -rp 'SMTP server hostname (Enter to disable email): ' SMTP_HOST
  SMTP_PORT=587 SMTP_USER='' SMTP_PASS='' SMTP_FROM=''
  if [[ -n "$SMTP_HOST" ]]; then
    [[ "$SMTP_HOST" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid SMTP host'
    read -rp 'SMTP port [587]: ' SMTP_PORT; SMTP_PORT=${SMTP_PORT:-587}
    [[ "$SMTP_PORT" =~ ^[0-9]{1,5}$ && "$SMTP_PORT" -ge 1 && "$SMTP_PORT" -le 65535 ]] || abort 'Invalid SMTP port'
    read -rp 'SMTP username: ' SMTP_USER
    read -rsp 'SMTP password (hidden): ' SMTP_PASS;printf '\n'
    read -rp 'From email (e.g. status@archforge.nl): ' SMTP_FROM
    [[ "$SMTP_FROM" == *@* && "$SMTP_FROM" != *$'\n'* ]] || abort 'Invalid sender address'
  fi
  KEY=$(od -An -tx1 -N32 /dev/urandom | tr -d ' \n')
  # Never source this file as shell. Values are read as literal NAME=VALUE pairs by Node.
  (umask 077; cat > "$CFG" <<ENVFILE
NODE_ENV=production
PORT=3000
STATUS_LISTEN_HOST=127.0.0.1
STATUS_PUBLIC_URL=https://${DOMAIN}
TARGET_WEBSITE=${WEBSITE}
TARGET_API_URL=${API}
TARGET_GIT_HTTP_URL=${GIT_HTTP}
TARGET_GIT_SSH_HOST=${SSH_HOST}
TARGET_GIT_SSH_PORT=${SSH_PORT}
MONITOR_INTERVAL_SECONDS=60
MONITOR_FAILURE_THRESHOLD=3
MONITOR_RECOVERY_THRESHOLD=2
MONITOR_TIMEOUT_MS=8000
STATUS_DISCORD_WEBHOOK_URL=${DISCORD}
SMTP_HOST=${SMTP_HOST}
SMTP_PORT=${SMTP_PORT}
SMTP_USER=${SMTP_USER}
SMTP_PASSWORD=${SMTP_PASS}
SMTP_FROM=${SMTP_FROM}
SMTP_TLS=on
STATUS_ENCRYPTION_KEY=${KEY}
STATUS_DB_PATH=/var/lib/archforge-status/status.db
ENVFILE
  )
  chown root:archforge-status "$CFG"
  chmod 640 "$CFG"
  unset KEY SMTP_PASS
fi
# Generate msmtp config from status.env only if enabled. Secrets never go into ArchForge.
# Read settings without sourcing the dotenv file.
get_conf(){ grep -m1 "^${1}=" "$CFG" | cut -d= -f2- || true; }
SMTP_HOST_VALUE=$(get_conf SMTP_HOST)
if [[ -n "$SMTP_HOST_VALUE" ]]; then
  smtp_quote(){ local x=$1;x=${x//\\/\\\\};x=${x//\"/\\\"};printf '"%s"' "$x"; }
  SMTP_P=$(get_conf SMTP_PORT);SMTP_U=$(get_conf SMTP_USER);SMTP_W=$(get_conf SMTP_PASSWORD);SMTP_F=$(get_conf SMTP_FROM)
  [[ "$SMTP_P" =~ ^[0-9]+$ ]] || abort 'Invalid SMTP port'
  {
    echo 'defaults';echo 'auth on';echo 'tls on';echo 'tls_starttls on';echo 'tls_trust_file /etc/ssl/certs/ca-certificates.crt';echo 'account status'
    printf 'host %s\n' "$(smtp_quote "$SMTP_HOST_VALUE")"
    printf 'port %s\n' "$SMTP_P"
    printf 'from %s\n' "$(smtp_quote "$SMTP_F")"
    printf 'user %s\n' "$(smtp_quote "$SMTP_U")"
    printf 'password %s\n' "$(smtp_quote "$SMTP_W")"
    echo 'account default : status'
  } >/etc/archforge-status/msmtprc
  chown archforge-status:archforge-status /etc/archforge-status/msmtprc
  chmod 600 /etc/archforge-status/msmtprc
  unset SMTP_W
else
  echo '[*] SMTP not configured. Email subscriptions will be disabled until configured.'
fi
install -m 644 "$SRC/scripts/archforge-status.service" /etc/systemd/system/archforge-status.service
install -m 644 "$SRC/scripts/archforge-status-backup.service" /etc/systemd/system/archforge-status-backup.service
install -m 644 "$SRC/scripts/archforge-status-backup.timer" /etc/systemd/system/archforge-status-backup.timer
install -m 755 "$SRC/scripts/backup.sh" /usr/local/sbin/archforge-status-backup
DOMAIN=$(get_conf STATUS_PUBLIC_URL);DOMAIN=${DOMAIN#https://};DOMAIN=${DOMAIN%%/*}
[[ "$DOMAIN" =~ ^[a-zA-Z0-9.-]+$ ]] || abort 'Invalid domain in saved config'
mkdir -p /etc/caddy/conf.d
cat > /etc/caddy/conf.d/archforge-status.caddy <<CADDYFILE
${DOMAIN} {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000
}
CADDYFILE
if ! grep -Fq 'import /etc/caddy/conf.d/*.caddy' /etc/caddy/Caddyfile; then
  cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.backup.$(date +%s)"
  printf '\nimport /etc/caddy/conf.d/*.caddy\n' >> /etc/caddy/Caddyfile
fi
caddy validate --config /etc/caddy/Caddyfile || abort 'Caddy configuration is invalid; inspect /etc/caddy/Caddyfile and its backup'
systemctl daemon-reload
# Create database and initial 2FA admin before starting the daemon.
echo
printf '\n--- Admin account + 2FA ---\n'
cd /opt/archforge-status/app
runuser -u archforge-status -- env STATUS_ENV_FILE="$CFG" NODE_ENV=production /usr/bin/node src/cli.js create-admin
systemctl enable --now archforge-status.service
systemctl enable --now archforge-status-backup.timer
systemctl enable --now caddy
systemctl reload caddy || systemctl restart caddy
sleep 1
systemctl is-active --quiet archforge-status || abort 'Service did not start: sudo journalctl -u archforge-status -n 50'
echo
printf '══════════════════════════════════════════════\n Installation finished.\n Public status: https://%s\n Admin login: https://%s/admin/login\n Local health: http://127.0.0.1:3000/healthz\n Logs: sudo journalctl -u archforge-status -f\n══════════════════════════════════════════════\n' "$DOMAIN" "$DOMAIN"
echo 'For a guided domain and SSL check, run: sudo bash ./configure-domain.sh from your ArchForge checkout.'
