#!/usr/bin/env bash
# Fix the stock :80 Caddy site which can leave status.archforge.nl unreachable on HTTPS.
# Intentional: never touch the status app, its environment, SQLite, or admin credentials.
set -Eeuo pipefail
umask 077
log() { printf '[*] %s\n' "$*"; }
error() { printf '[ERROR] %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || error 'Run as root: sudo bash ./fix-https.sh'
[[ -r /etc/os-release ]] || error 'Cannot determine Linux distribution'
# shellcheck disable=SC1091
source /etc/os-release
[[ ${ID:-} == debian && ${VERSION_ID:-} == 13 ]] || error 'This repair is for Debian 13 only'
for binary in caddy systemctl ss curl python3; do
  command -v "$binary" >/dev/null 2>&1 || error "Missing required program: $binary"
done

ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
# shellcheck source=domain-lib.sh
source "$ROOT/scripts/domain-lib.sh"
CFG=/etc/archforge-status/status.env
CADDYFILE=/etc/caddy/Caddyfile
SITEFILE=/etc/caddy/conf.d/archforge-status.caddy
[[ -f "$CFG" && ! -L "$CFG" ]] || error 'Status configuration not found or symbolic link'
[[ -f "$CADDYFILE" && ! -L "$CADDYFILE" ]] || error 'Caddyfile not found or symbolic link'
[[ ! -L "$SITEFILE" ]] || error 'Refusing symlinked Caddy site file'
DOMAIN=$(af_read_public_domain "$CFG") || error 'Invalid STATUS_PUBLIC_URL in existing status configuration'
[[ "$DOMAIN" == status.archforge.nl ]] || error "Configured domain is '$DOMAIN', expected status.archforge.nl; no changes made"

# The user has only the Debian default :80 site and our status snippet. Never
# replace multi-site or otherwise customized Caddy instances on a shared VPS.
if [[ -d /etc/caddy/conf.d ]]; then
  OTHER=$(find /etc/caddy/conf.d -maxdepth 1 -type f -name '*.caddy' ! -name 'archforge-status.caddy' -print -quit)
  [[ -z "$OTHER" ]] || error "Other Caddy site exists: $OTHER. No changes made."
fi
CURRENT_JSON=$(mktemp /tmp/archforge-caddy-adapt.XXXXXXXX)
trap 'rm -f "$CURRENT_JSON"' EXIT
if ! caddy adapt --config "$CADDYFILE" > "$CURRENT_JSON" 2>/dev/null; then
  error 'Existing Caddy config does not parse. No changes made.'
fi
if ! python3 - "$CURRENT_JSON" "$DOMAIN" <<'PY'
import json, sys
conf=json.load(open(sys.argv[1]))
domain=sys.argv[2]
http=conf.get('apps',{}).get('http',{})
for server in http.get('servers',{}).values():
    for route in server.get('routes',[]):
        for matcher in route.get('match',[]):
            for host in matcher.get('host',[]):
                if host != domain:
                    print(f'Another HTTPS site exists: {host}',file=sys.stderr)
                    sys.exit(1)
PY
then
  error 'Caddy serves another named site. Refusing to overwrite shared config.'
fi

if ! curl --silent --show-error --fail --max-time 5 http://127.0.0.1:3000/healthz -o /dev/null; then
  error 'ArchForge Status is not running on local port 3000. No Caddy changes made.'
fi

log "Repairing HTTPS for $DOMAIN (Node.js backend is healthy)."
BACKUP="/root/archforge-caddy-before-https-fix-$(date -u +%Y%m%d-%H%M%S)"
mkdir -m 700 -- "$BACKUP"
cp -a -- "$CADDYFILE" "$BACKUP/Caddyfile"
if [[ -f "$SITEFILE" ]]; then cp -a -- "$SITEFILE" "$BACKUP/archforge-status.caddy"; fi
printf 'Backup directory: %s\n' "$BACKUP"

RESTORED=false
rollback() {
  printf '[!] Restoring previous Caddy configuration from %s\n' "$BACKUP" >&2
  cp -a -- "$BACKUP/Caddyfile" "$CADDYFILE"
  systemctl restart caddy >/dev/null 2>&1 || true
  RESTORED=true
}

# Use the existing configured site if it is a straightforward reverse proxy;
# otherwise generate the minimal known-safe Caddyfile without any extra directives.
TMP=$(mktemp /etc/caddy/.archforge-caddy.XXXXXXXX)
if [[ -f "$SITEFILE" ]] && \
   grep -Eq '^status\.archforge\.nl[[:space:]]*\{' "$SITEFILE" && \
   grep -Eq '^[[:space:]]*reverse_proxy[[:space:]]+127\.0\.0\.1:3000[[:space:]]*$' "$SITEFILE"; then
  cp -- "$SITEFILE" "$TMP"
else
  af_render_caddy "$DOMAIN" '' > "$TMP"
fi
chmod 644 "$TMP"
chown root:root "$TMP"
if ! caddy validate --config "$TMP" --adapter caddyfile >/dev/null; then
  rm -f "$TMP"
  error 'New direct HTTPS configuration did not validate. Old files untouched.'
fi

# Force the domain block directly into the main Caddyfile. This intentionally
# replaces only the stock HTTP :80 page on this dedicated status VPS.
if ! mv -f -- "$TMP" "$CADDYFILE"; then
  rm -f "$TMP" || true
  error 'Could not write Caddyfile. Backup remains available.'
fi
if ! systemctl restart caddy; then
  log 'Caddy restart failed; printing errors and rolling back.'
  journalctl -u caddy -n 30 --no-pager || true
  rollback
  error 'Caddy failed to restart with HTTPS config.'
fi
sleep 2
has_443() { ss -ltn | grep -Eq '(:|\])443[[:space:]]'; }
if ! has_443; then
  log 'Service did not bind 443. Trying explicit reload of the new Caddyfile.'
  caddy reload --config "$CADDYFILE" >/dev/null 2>&1 || true
  sleep 2
fi
if ! has_443; then
  printf '\n[!] Caddy STILL DOES NOT LISTEN on TCP 443. This is not a firewall issue.\n' >&2
  systemctl show caddy -p ExecStart -p FragmentPath --no-pager >&2 || true
  journalctl -u caddy -n 40 --no-pager >&2 || true
  rollback
  error 'HTTPS listener missing. Previous configuration restored. Above is the actual Caddy failure.'
fi

log 'SUCCESS: Caddy now listens on TCP 443.'
log 'Checking whether an ACME certificate is ready...'
TLS_OK=false
for _ in 1 2 3 4 5 6; do
  if curl --silent --show-error --fail --max-time 7 --resolve "$DOMAIN:443:127.0.0.1" "https://$DOMAIN/healthz" -o /dev/null 2>/dev/null; then
    TLS_OK=true
    break
  fi
  sleep 3
done
printf '\n===============================================\n'
printf ' Domain:       https://%s\n' "$DOMAIN"
printf ' Admin:        https://%s/admin/login\n' "$DOMAIN"
printf ' HTTPS port:   LISTENING (443/tcp)\n'
if [[ "$TLS_OK" == true ]]; then
  printf ' Certificate:  VERIFIED (valid TLS and status endpoint)\n'
else
  printf ' Certificate:  PENDING / NOT YET VERIFIED\n'
  printf ' Caddy is listening, but ACME or public DNS/connectivity must finish.\n'
  printf ' Recent Caddy logs:\n'
  journalctl -u caddy -n 25 --no-pager || true
fi
printf ' Backup:       %s\n' "$BACKUP"
printf '===============================================\n'
