#!/usr/bin/env bash
# Idempotent bootstrap for a fresh MNW node (tier A/B/C deploy target).
#
# Run on the new node as root. After this finishes, sandod on the Sando host
# can rsync + deploy to <ssh_target>:/opt/mnw/.
#
# Required env:
#   SANDO_PUBKEY  — sando user's public key on the Sando host. Get it via:
#                   `ssh fw13 'sudo cat /srv/sando/.ssh/id_ed25519.pub'`
#
# Optional env:
#   DEPLOY_ROOT      — defaults to /opt/mnw
#   BIN_NAME         — primary binary name (matches sando-daemon.toml's
#                      bin_names[0]). Defaults to "makenotwork".
#   SERVICE_NAME     — systemd unit name. Defaults to "makenotwork.service".
#   SERVICE_USER     — runtime user for the binary. Defaults to "deploy".
#   ENABLE_FIREWALL  — "1" to set up UFW (22/80/443). Defaults to "1".
#   INSTALL_CADDY    — "1" to apt-install caddy (config is operator's job).
#                      Defaults to "1".
#   INSTALL_POSTGRES — "1" to apt-install postgresql. Defaults to "1".
#   INSTALL_TAILSCALE — "1" to apt-install tailscale (NOT authenticated;
#                       operator runs `tailscale up`). Defaults to "1".
#
# What this does NOT do (operator's job):
#   - tailscale up (auth)
#   - DNS records
#   - Caddyfile content + Cloudflare origin certs + private keys
#   - postgres role + db + .env / DATABASE_URL
#   - any secrets

set -euo pipefail

if [[ $EUID -ne 0 ]]; then
    echo "must run as root" >&2
    exit 1
fi
if [[ -z "${SANDO_PUBKEY:-}" ]]; then
    echo "SANDO_PUBKEY env var is required" >&2
    exit 1
fi

DEPLOY_ROOT="${DEPLOY_ROOT:-/opt/mnw}"
# FHS-style sidecar paths the systemd unit references. Bootstrap creates the
# dirs but does not populate `ENV_FILE` — operator drops secrets in after the
# bootstrap finishes, before starting the service.
ETC_DIR="${ETC_DIR:-/etc/mnw}"
ENV_FILE="${ENV_FILE:-$ETC_DIR/makenotwork.env}"
STATE_DIR="${STATE_DIR:-/var/lib/mnw}"
BIN_NAME="${BIN_NAME:-makenotwork}"
SERVICE_NAME="${SERVICE_NAME:-makenotwork.service}"
SERVICE_USER="${SERVICE_USER:-deploy}"
ENABLE_FIREWALL="${ENABLE_FIREWALL:-1}"
INSTALL_CADDY="${INSTALL_CADDY:-1}"
INSTALL_POSTGRES="${INSTALL_POSTGRES:-1}"
INSTALL_TAILSCALE="${INSTALL_TAILSCALE:-1}"

export DEBIAN_FRONTEND=noninteractive

log() { echo "[bootstrap] $*"; }

log "1/8 base packages"
apt-get update -qq
apt-get install -y -qq curl gnupg ca-certificates rsync ufw fail2ban > /dev/null

if [[ "$INSTALL_POSTGRES" == "1" ]]; then
    log "2/8 postgresql"
    apt-get install -y -qq postgresql > /dev/null
else
    log "2/8 skipping postgresql"
fi

if [[ "$INSTALL_TAILSCALE" == "1" ]]; then
    log "3/8 tailscale (not authenticating)"
    if ! command -v tailscale >/dev/null; then
        # Ubuntu codename. tailscale's repo is published per-codename;
        # noble (24.04) keys work on 24.04+ derivatives.
        codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
        curl -fsSL "https://pkgs.tailscale.com/stable/ubuntu/${codename}.noarmor.gpg" \
            > /usr/share/keyrings/tailscale-archive-keyring.gpg
        curl -fsSL "https://pkgs.tailscale.com/stable/ubuntu/${codename}.tailscale-keyring.list" \
            > /etc/apt/sources.list.d/tailscale.list
        apt-get update -qq
        apt-get install -y -qq tailscale > /dev/null
        systemctl enable --now tailscaled
    fi
else
    log "3/8 skipping tailscale"
fi

if [[ "$INSTALL_CADDY" == "1" ]]; then
    log "4/8 caddy (no Caddyfile — operator's job)"
    if ! command -v caddy >/dev/null; then
        curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key \
            | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
        curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \
            > /etc/apt/sources.list.d/caddy-stable.list
        apt-get update -qq
        apt-get install -y -qq caddy > /dev/null
    fi
else
    log "4/8 skipping caddy"
fi

log "5/8 deploy user + dirs"
if ! id "$SERVICE_USER" &>/dev/null; then
    useradd -m -d "/home/$SERVICE_USER" -s /bin/bash "$SERVICE_USER"
fi
install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0700 "/home/$SERVICE_USER/.ssh"
if ! grep -qF "$SANDO_PUBKEY" "/home/$SERVICE_USER/.ssh/authorized_keys" 2>/dev/null; then
    echo "$SANDO_PUBKEY" >> "/home/$SERVICE_USER/.ssh/authorized_keys"
fi
chown "$SERVICE_USER:$SERVICE_USER" "/home/$SERVICE_USER/.ssh/authorized_keys"
chmod 0600 "/home/$SERVICE_USER/.ssh/authorized_keys"
install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0755 "$DEPLOY_ROOT" "$DEPLOY_ROOT/releases"
# FHS sidecars: /etc/mnw owned root:service (so the service can read the env
# file but not edit it); /var/lib/mnw owned service:service for runtime
# state (backups, scan-spool, anything else the binary writes).
install -d -o root -g "$SERVICE_USER" -m 0750 "$ETC_DIR"
install -d -o "$SERVICE_USER" -g "$SERVICE_USER" -m 0750 "$STATE_DIR"

# If the git user exists (i.e. this host runs git SSH), grant it read access
# to the env file via ACL so mnw-admin git-auth can load DATABASE_URL. The git
# user is neither owner nor in the SERVICE_USER group, so without this the
# /etc/mnw/makenotwork.env is unreadable and every `git push` panics with
# "DATABASE_URL must be set". Conditional + idempotent.
if getent passwd git >/dev/null; then
    setfacl -m u:git:x "$ETC_DIR"
    if [ -f "$ENV_FILE" ]; then
        setfacl -m u:git:r "$ENV_FILE"
    fi
fi

log "6/8 sudoers (systemctl on $SERVICE_NAME for $SERVICE_USER)"
cat > "/etc/sudoers.d/${SERVICE_USER}-mnw" <<EOF
$SERVICE_USER ALL=(ALL) NOPASSWD: /bin/systemctl reload-or-restart $SERVICE_NAME, /bin/systemctl restart $SERVICE_NAME, /bin/systemctl status $SERVICE_NAME
EOF
chmod 0440 "/etc/sudoers.d/${SERVICE_USER}-mnw"
visudo -c -f "/etc/sudoers.d/${SERVICE_USER}-mnw" >/dev/null

log "7/8 systemd unit ($SERVICE_NAME) — points at $DEPLOY_ROOT/current/$BIN_NAME"
cat > "/etc/systemd/system/$SERVICE_NAME" <<EOF
[Unit]
Description=Makenotwork
After=network.target

[Service]
Type=simple
User=$SERVICE_USER
Group=$SERVICE_USER
WorkingDirectory=$DEPLOY_ROOT/current
ExecStart=$DEPLOY_ROOT/current/$BIN_NAME
# Secrets live outside the release dir so they survive deploys + rollbacks.
# Bootstrap creates ETC_DIR but not ENV_FILE — operator populates that.
EnvironmentFile=$ENV_FILE
# Runtime state (backups, spool, etc.) on FHS path; never inside the release
# dir or the deploy will erase it.
ReadWritePaths=$STATE_DIR
Restart=on-failure
RestartSec=30
# Exit 2 = migration failure (MNW server convention). Don't restart;
# operator must intervene before the next deploy.
RestartPreventExitStatus=2
StandardOutput=journal
StandardError=journal
SyslogIdentifier=$BIN_NAME

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$SERVICE_NAME" >/dev/null 2>&1 || true

if [[ "$ENABLE_FIREWALL" == "1" ]]; then
    log "8/8 firewall (UFW: 22/80/443 in, all else deny)"
    ufw --force reset > /dev/null
    ufw default deny incoming > /dev/null
    ufw default allow outgoing > /dev/null
    ufw allow 22/tcp > /dev/null
    ufw allow 80/tcp > /dev/null
    ufw allow 443/tcp > /dev/null
    ufw --force enable > /dev/null
else
    log "8/8 skipping firewall"
fi

echo
log "Done. Next steps for the operator:"
echo "   - tailscale up    (auth this node to the tailnet)"
echo "   - DNS A/AAAA records for the domain you'll serve"
echo "   - Install /etc/caddy/Caddyfile + Cloudflare Origin CA cert + key"
echo "   - postgres: create role+db, drop secrets into $ENV_FILE (chmod 0640, chown root:$SERVICE_USER)"
echo "   - Run a sando deploy from the Sando host: POST /promote/<tier>"
