#!/usr/bin/env bash
#
# dotnews-paas bootstrap — one-shot, idempotent setup of a fresh Debian 12 VM.
# Run as root from a checkout/rsync of this repo:
#   sudo PANEL_DOMAIN=deploy.younex.de PANEL_ADMIN_PASSWORD=... ./bootstrap.sh
#
# Installs: Node 22, PM2, Caddy, Varnish, sqlite3 + the paas CLI, the deploy
# panel, base configs, the deploy user, firewall. Safe to re-run.
#
# Optional runtimes:
#   WITH_LARAVEL=1   adds PHP-FPM (PHP_VERSIONS, default "8.3 8.4" from deb.sury.org),
#                    composer, MariaDB (loopback only) + the 'paas db' admin
#                    credentials, sudoers for fpm reloads, the Laravel scheduler
#                    and nightly DB-backup timers, and RUNTIMES=node,laravel in
#                    /srv/paas/paas.conf (what the panel/CLI gate on). Without the
#                    flag nothing of this is touched — a plain Node server.

set -euo pipefail

PANEL_DOMAIN="${PANEL_DOMAIN:-deploy.younex.de}"
ACME_EMAIL="${ACME_EMAIL:-info@logyou.de}"
PANEL_PORT="${PANEL_PORT:-3900}"
VARNISH_MALLOC="${VARNISH_MALLOC:-2g}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

log() { printf '\n[bootstrap] %s\n' "$*"; }
[[ "$(id -u)" -eq 0 ]] || { echo "run as root" >&2; exit 1; }
[[ -f "$SCRIPT_DIR/bin/paas" ]] || { echo "run from the dotnews-paas repo root" >&2; exit 1; }

# ------------------------------------------------------------- base system ----
log "apt base packages"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq curl ca-certificates gnupg git rsync jq sqlite3 ufw \
    unattended-upgrades build-essential python3 debian-keyring debian-archive-keyring \
    apt-transport-https chromium fonts-liberation >/dev/null   # chromium: panel homepage snapshots

log "unattended security upgrades"
printf 'APT::Periodic::Update-Package-Lists "1";\nAPT::Periodic::Unattended-Upgrade "1";\n' \
    >/etc/apt/apt.conf.d/20auto-upgrades

# ------------------------------------------------------------------ node 22 ----
if ! command -v node >/dev/null || [[ "$(node -v | cut -c2-3)" -lt 22 ]]; then
    log "installing Node 22 (NodeSource)"
    curl -fsSL https://deb.nodesource.com/setup_22.x | bash - >/dev/null
    apt-get install -y -qq nodejs >/dev/null
fi
command -v pm2 >/dev/null || { log "installing pm2"; npm install -g pm2 >/dev/null; }

# extra Node versions, selectable per app (system Node 22 is the default)
log "extra node versions (/opt/node)"
install -d /opt/node
for NV in 18 20 24; do
    if [[ ! -d "/opt/node/v$NV" ]]; then
        TARBALL=$(curl -fsSL "https://nodejs.org/dist/latest-v$NV.x/" | grep -oE "node-v$NV\.[0-9]+\.[0-9]+-linux-x64\.tar\.xz" | head -1)
        if [[ -n "$TARBALL" ]]; then
            curl -fsSL "https://nodejs.org/dist/latest-v$NV.x/$TARBALL" | tar -xJ -C /opt/node
            mv "/opt/node/${TARBALL%.tar.xz}" "/opt/node/v$NV"
            log "  installed node $("/opt/node/v$NV/bin/node" -v)"
        else
            log "  WARNING: could not resolve latest v$NV tarball — skipped"
        fi
    fi
done

# -------------------------------------------------------------------- caddy ----
if ! command -v caddy >/dev/null; then
    log "installing Caddy (official repo)"
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
        | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf '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

# ------------------------------------------------------------------ varnish ----
command -v varnishd >/dev/null || { log "installing Varnish"; apt-get install -y -qq varnish >/dev/null; }

# -------------------------------------------------------------- deploy user ----
if ! id deploy >/dev/null 2>&1; then
    log "creating deploy user"
    useradd -m -s /bin/bash deploy
fi
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
# authorize the same keys as root (so whoever bootstraps can ssh as deploy)
if [[ -f /root/.ssh/authorized_keys ]]; then
    touch /home/deploy/.ssh/authorized_keys
    while IFS= read -r k; do
        grep -qF "$k" /home/deploy/.ssh/authorized_keys || echo "$k" >>/home/deploy/.ssh/authorized_keys
    done </root/.ssh/authorized_keys
    chown deploy:deploy /home/deploy/.ssh/authorized_keys
    chmod 600 /home/deploy/.ssh/authorized_keys
fi
# git deploy key for private repos (shown in the panel)
if [[ ! -f /home/deploy/.ssh/id_ed25519 ]]; then
    sudo -u deploy ssh-keygen -t ed25519 -N '' -C "deploy@dotnews-paas" -f /home/deploy/.ssh/id_ed25519 >/dev/null
fi

log "sudoers for deploy (service reloads + varnish validation only)"
cat >/etc/sudoers.d/paas <<'EOF'
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload caddy, /usr/bin/systemctl restart caddy, /usr/bin/systemctl reload varnish, /usr/bin/systemctl restart varnish, /usr/sbin/varnishd *, /usr/local/bin/paas-cert-prune *
EOF
chmod 440 /etc/sudoers.d/paas

# platform self-update over SSH (Jenkins job `paas-platform`, see
# ci/Jenkinsfile.platform + docs/ci-platform-deploy.md): root script + forced
# command wrapper + exact-command sudoers. Inert until infra/setup-ci-deploy.sh
# authorizes a CI key on the deploy user — no instance state here.
log "platform self-deploy (paas-self-deploy + sudoers, used by CI)"
install -m 0755 "$SCRIPT_DIR/infra/paas-self-deploy" /usr/local/sbin/paas-self-deploy
install -m 0755 "$SCRIPT_DIR/infra/paas-ci-ssh" /usr/local/bin/paas-ci-ssh
cat >/etc/sudoers.d/paas-ci <<'EOF'
deploy ALL=(root) NOPASSWD: /usr/local/sbin/paas-self-deploy *
EOF
chmod 440 /etc/sudoers.d/paas-ci

# ------------------------------------------------------------- directories ----
log "directory skeleton"
install -d -o deploy -g deploy /srv/paas /srv/paas/apps.d /srv/paas/caddy \
    /srv/paas/vcl /srv/paas/builds /srv/paas/logs /srv/paas/snapshots /srv/apps
chmod 755 /srv/paas /srv/paas/vcl /srv/paas/caddy   # varnish/caddy must read

# ----------------------------------------------------------------- database ----
log "sqlite schema"
install -d -o deploy -g deploy /srv/paas/sql
install -m 644 -o deploy -g deploy "$SCRIPT_DIR/sql/schema.sql" /srv/paas/sql/schema.sql
sudo -u deploy -H bash -c 'cd /srv/paas && sqlite3 /srv/paas/paas.db' <"$SCRIPT_DIR/sql/schema.sql"

# ----------------------------------------------------------------- paas CLI ----
log "installing paas CLI"
install -m 755 "$SCRIPT_DIR/bin/paas" /usr/local/bin/paas
install -m 755 "$SCRIPT_DIR/bin/paas-cert-prune" /usr/local/bin/paas-cert-prune
install -m 755 "$SCRIPT_DIR/bin/paas-init" /usr/local/bin/paas-init     # first-boot wizard (golden clones)
if [[ ! -f /srv/paas/paas.conf ]]; then
    cat >/srv/paas/paas.conf <<EOF
# platform-wide paas settings
# base domain for auto-generated system domains (*.base wildcard DNS -> this server)
SYSTEM_DOMAIN_BASE=${SYSTEM_DOMAIN_BASE:-younex.de}
EOF
    chown deploy:deploy /srv/paas/paas.conf
fi

# ----------------------------------------------------- laravel runtime (opt-in) ----
if [[ "${WITH_LARAVEL:-0}" == "1" ]]; then
    PHP_VERSIONS="${PHP_VERSIONS:-8.3 8.4}"
    PHP_DEFAULT="${PHP_DEFAULT:-${PHP_VERSIONS%% *}}"
    MARIADB_BUFFER_POOL="${MARIADB_BUFFER_POOL:-512M}"

    log "laravel runtime: PHP ($PHP_VERSIONS) from deb.sury.org"
    if [[ ! -f /etc/apt/sources.list.d/sury-php.list ]]; then
        # shellcheck disable=SC1091
        . /etc/os-release
        curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor --yes -o /usr/share/keyrings/deb.sury.org-php.gpg
        echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $VERSION_CODENAME main" \
            >/etc/apt/sources.list.d/sury-php.list
        apt-get update -qq
    fi
    install -d -o deploy -g deploy -m 755 /srv/paas/php /srv/paas/php/bin
    for V in $PHP_VERSIONS; do
        # explicit packages only — the "php$V" meta package would drag in Apache
        apt-get install -y -qq "php$V-fpm" "php$V-cli" "php$V-mysql" "php$V-mbstring" "php$V-xml" \
            "php$V-curl" "php$V-zip" "php$V-bcmath" "php$V-intl" "php$V-gd" "php$V-opcache" \
            "php$V-readline" "php$V-sqlite3" "php$V-redis" >/dev/null
        # per-app pools written by 'paas' (user deploy) live outside /etc
        install -d -o deploy -g deploy -m 755 "/srv/paas/php/$V" "/srv/paas/php/bin/$V"
        ln -sfn "/usr/bin/php$V" "/srv/paas/php/bin/$V/php"     # PATH shim for builds/artisan
        FPMCONF="/etc/php/$V/fpm/php-fpm.conf"
        grep -q "^include=/srv/paas/php/$V/\*.conf" "$FPMCONF" || echo "include=/srv/paas/php/$V/*.conf" >>"$FPMCONF"
        # let in-flight requests finish on the (rare) reload
        if grep -q '^;*process_control_timeout' "$FPMCONF"; then
            sed -i 's/^;*process_control_timeout.*/process_control_timeout = 10s/' "$FPMCONF"
        else
            sed -i '/^\[global\]/a process_control_timeout = 10s' "$FPMCONF"
        fi
        # opcache belongs to the fpm master (shared by all pools) — per-pool
        # values are ignored, so it is configured once per version here.
        # validate_timestamps stays on: 'paas restart' regenerates caches in place.
        cat >"/etc/php/$V/fpm/conf.d/99-paas.ini" <<'EOF'
; dotnews-paas — written by bootstrap.sh
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=50000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
expose_php=Off
EOF
        "/usr/sbin/php-fpm$V" -t >/dev/null
        systemctl enable --now "php$V-fpm" >/dev/null 2>&1
        systemctl reload "php$V-fpm"
        log "  php $V ready ($("/usr/bin/php$V" -r 'echo PHP_VERSION;'))"
    done

    log "composer"
    if ! command -v composer >/dev/null; then
        EXPECTED="$(curl -fsSL https://composer.github.io/installer.sig)"
        curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php
        ACTUAL="$("/usr/bin/php$PHP_DEFAULT" -r "echo hash_file('sha384', '/tmp/composer-setup.php');")"
        [[ "$EXPECTED" == "$ACTUAL" ]] || { rm -f /tmp/composer-setup.php; echo "composer installer checksum mismatch" >&2; exit 1; }
        "/usr/bin/php$PHP_DEFAULT" /tmp/composer-setup.php --quiet --install-dir=/usr/local/bin --filename=composer
        rm -f /tmp/composer-setup.php
    else
        sudo -u deploy -H composer self-update -q 2>/dev/null || composer self-update -q 2>/dev/null || true
    fi
    install -d -o deploy -g deploy /srv/paas/builds/.composer

    log "mariadb (loopback only)"
    command -v mariadbd >/dev/null || apt-get install -y -qq mariadb-server mariadb-client >/dev/null
    MARIADB_CNF=/etc/mysql/mariadb.conf.d/60-paas.cnf
    MARIADB_NEW="$(cat <<EOF
# dotnews-paas — written by bootstrap.sh (MARIADB_BUFFER_POOL=$MARIADB_BUFFER_POOL)
[mysqld]
bind-address            = 127.0.0.1
skip-name-resolve       = 1
innodb_buffer_pool_size = $MARIADB_BUFFER_POOL
max_allowed_packet      = 64M
character-set-server    = utf8mb4
collation-server        = utf8mb4_unicode_ci
performance_schema      = OFF
EOF
)"
    if [[ ! -f "$MARIADB_CNF" ]] || [[ "$(cat "$MARIADB_CNF")" != "$MARIADB_NEW" ]]; then
        printf '%s\n' "$MARIADB_NEW" >"$MARIADB_CNF"
        systemctl enable --now mariadb >/dev/null 2>&1
        systemctl restart mariadb
    else
        systemctl enable --now mariadb >/dev/null 2>&1
    fi
    # 'paas db' admin user: explicit privilege list (no FILE/SUPER), credentials
    # readable by deploy only. Re-running re-applies the password from the cnf.
    DB_ADMIN_CNF=/srv/paas/db-admin.cnf
    if [[ -f "$DB_ADMIN_CNF" ]]; then
        DB_ADMIN_PW="$(sed -n 's/^password=//p' "$DB_ADMIN_CNF")"
    else
        DB_ADMIN_PW="$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40)"
    fi
    mariadb <<EOF
CREATE USER IF NOT EXISTS 'paas_admin'@'localhost' IDENTIFIED BY '$DB_ADMIN_PW';
ALTER USER 'paas_admin'@'localhost' IDENTIFIED BY '$DB_ADMIN_PW';
GRANT CREATE, DROP, ALTER, INDEX, SELECT, INSERT, UPDATE, DELETE, REFERENCES, LOCK TABLES,
      CREATE VIEW, SHOW VIEW, TRIGGER, EVENT, CREATE ROUTINE, ALTER ROUTINE, EXECUTE,
      CREATE TEMPORARY TABLES, RELOAD, PROCESS, CREATE USER
      ON *.* TO 'paas_admin'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EOF
    cat >"$DB_ADMIN_CNF" <<EOF
[client]
user=paas_admin
password=$DB_ADMIN_PW
socket=/run/mysqld/mysqld.sock
EOF
    chown deploy:deploy "$DB_ADMIN_CNF"; chmod 600 "$DB_ADMIN_CNF"
    install -d -o deploy -g deploy -m 700 /srv/paas/backups /srv/paas/backups/db

    log "sudoers for deploy (php-fpm reload + config test, exact commands)"
    SUDO_LINE="deploy ALL=(root) NOPASSWD:"
    SEP=" "
    for V in $PHP_VERSIONS; do
        SUDO_LINE+="${SEP}/usr/bin/systemctl reload php$V-fpm, /usr/sbin/php-fpm$V -t"
        SEP=", "
    done
    echo "$SUDO_LINE" >/etc/sudoers.d/paas-laravel
    chmod 440 /etc/sudoers.d/paas-laravel
    visudo -cf /etc/sudoers.d/paas-laravel >/dev/null

    log "laravel scheduler (1/min) + database backups (nightly) timers"
    install -m 755 "$SCRIPT_DIR/bin/paas-laravel-scheduler" /usr/local/bin/paas-laravel-scheduler
    install -m 644 "$SCRIPT_DIR/infra/paas-laravel-scheduler.service" /etc/systemd/system/
    install -m 644 "$SCRIPT_DIR/infra/paas-laravel-scheduler.timer" /etc/systemd/system/
    install -m 644 "$SCRIPT_DIR/infra/paas-db-backup.service" /etc/systemd/system/
    install -m 644 "$SCRIPT_DIR/infra/paas-db-backup.timer" /etc/systemd/system/
    systemctl daemon-reload
    systemctl enable --now paas-laravel-scheduler.timer >/dev/null 2>&1
    systemctl enable --now paas-db-backup.timer >/dev/null 2>&1

    # capability flags the CLI + panel gate on
    set_paas_conf() { # $1 key, $2 value
        if grep -q "^$1=" /srv/paas/paas.conf; then sed -i "s|^$1=.*|$1=$2|" /srv/paas/paas.conf; else echo "$1=$2" >>/srv/paas/paas.conf; fi
    }
    set_paas_conf RUNTIMES "node,laravel"
    set_paas_conf PHP_VERSIONS "\"$PHP_VERSIONS\""
    set_paas_conf PHP_DEFAULT "$PHP_DEFAULT"
    log "laravel runtime enabled (RUNTIMES=node,laravel in /srv/paas/paas.conf)"
fi

# --------------------------------------------------------- checkmk monitoring ----
log "checkmk local checks (agent deb itself: install manually from the monitoring site)"
install -d /usr/lib/check_mk_agent/local
install -m 755 "$SCRIPT_DIR/agent/local-checks-paas" /usr/lib/check_mk_agent/local/paas
cat >/etc/sudoers.d/paas-cmk <<'EOF'
deploy ALL=(root) NOPASSWD: /usr/bin/check_mk_agent
EOF
chmod 440 /etc/sudoers.d/paas-cmk

# ---------------------------------------------------------- traffic metrics ----
log "traffic rollup (caddy access logs -> sqlite, systemd timer)"
# caddy (service) and deploy (validate/rollup) both need rw on the log files:
# shared setgid dir + 0664 file mode + caddy in the deploy group.
usermod -aG deploy caddy 2>/dev/null || true
install -d -o caddy -g deploy -m 2770 /srv/paas/logs/caddy
install -m 755 "$SCRIPT_DIR/bin/paas-traffic-rollup" /usr/local/bin/paas-traffic-rollup
install -m 644 "$SCRIPT_DIR/infra/paas-traffic-rollup.service" /etc/systemd/system/
install -m 644 "$SCRIPT_DIR/infra/paas-traffic-rollup.timer" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now paas-traffic-rollup.timer >/dev/null 2>&1

# same rollup pass also aggregates article_clicks_daily (opt-in per app via
# route-manifest.json); this second timer pushes the totals to each app
log "click-count writeback (article_clicks_daily -> each app's Strapi, hourly)"
install -m 755 "$SCRIPT_DIR/bin/paas-clicks-writeback" /usr/local/bin/paas-clicks-writeback
install -m 644 "$SCRIPT_DIR/infra/paas-clicks-writeback.service" /etc/systemd/system/
install -m 644 "$SCRIPT_DIR/infra/paas-clicks-writeback.timer" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now paas-clicks-writeback.timer >/dev/null 2>&1

# ------------------------------------------------------------ geo-blocking ----
log "geoip refresh (DB-IP country mmdb, monthly)"
install -m 755 "$SCRIPT_DIR/bin/paas-geoip-refresh" /usr/local/bin/paas-geoip-refresh
install -m 644 "$SCRIPT_DIR/infra/paas-geoip-refresh.service" /etc/systemd/system/
install -m 644 "$SCRIPT_DIR/infra/paas-geoip-refresh.timer" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now paas-geoip-refresh.timer >/dev/null 2>&1
[[ -f /srv/paas/geoip/country.mmdb ]] || /usr/local/bin/paas-geoip-refresh || true
if [[ ! -x /usr/local/bin/caddy ]]; then
    log "NOTE: geo-blocking needs the xcaddy-built caddy (maxmind module):"
    log "  apt-get install -y golang-go && go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest"
    log "  xcaddy build \$(caddy version | cut -d' ' -f1) --with github.com/porech/caddy-maxmind-geolocation --output /usr/local/bin/caddy"
    log "  + systemd override ExecStart/ExecReload -> /usr/local/bin/caddy (see docs/README.md)"
fi

log "google crawler IP refresh (AdsBot/Googlebot exemption from geo-blocking, daily)"
install -m 755 "$SCRIPT_DIR/bin/paas-google-crawlers-refresh" /usr/local/bin/paas-google-crawlers-refresh
install -m 644 "$SCRIPT_DIR/infra/paas-google-crawlers-refresh.service" /etc/systemd/system/
install -m 644 "$SCRIPT_DIR/infra/paas-google-crawlers-refresh.timer" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now paas-google-crawlers-refresh.timer >/dev/null 2>&1
[[ -f /srv/paas/geoip/google-crawlers.cidr ]] || /usr/local/bin/paas-google-crawlers-refresh || true

# ------------------------------------------------------------ varnish config ----
log "varnish base config"
install -m 644 "$SCRIPT_DIR/templates/base.vcl" /etc/varnish/default.vcl
if [[ ! -f /srv/paas/vcl/paas-backends.vcl ]]; then
    echo "backend b_paas_none none;" >/srv/paas/vcl/paas-backends.vcl
    echo 'return (synth(421, "Misdirected Request"));' >/srv/paas/vcl/paas-routing.vcl
    chown deploy:deploy /srv/paas/vcl/paas-*.vcl
fi
install -d /etc/systemd/system/varnish.service.d
cat >/etc/systemd/system/varnish.service.d/override.conf <<EOF
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a 127.0.0.1:6081 -T localhost:6082 \\
    -f /etc/varnish/default.vcl -S /etc/varnish/secret \\
    -s malloc,${VARNISH_MALLOC} -p default_ttl=120 -p default_grace=3600
EOF
systemctl daemon-reload
systemctl enable --now varnish >/dev/null 2>&1
systemctl restart varnish

# -------------------------------------------------------------- caddy config ----
log "caddy base config"
sed "s/{\$ACME_EMAIL:info@logyou.de}/$ACME_EMAIL/" "$SCRIPT_DIR/templates/Caddyfile" >/etc/caddy/Caddyfile
if [[ ! -f /srv/paas/caddy/panel.caddy ]]; then
    cat >/srv/paas/caddy/panel.caddy <<EOF
# deploy panel — written by bootstrap.sh
$PANEL_DOMAIN {
    reverse_proxy 127.0.0.1:$PANEL_PORT
    header -Server
    encode zstd gzip
    log {
        output file /srv/paas/logs/caddy/panel.access.log {
            roll_size 100MiB
            roll_keep 3
            mode 0664
        }
        format json
    }
}
EOF
    chown deploy:deploy /srv/paas/caddy/panel.caddy
fi
systemctl enable --now caddy >/dev/null 2>&1
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null
systemctl reload caddy

# -------------------------------------------------------------------- panel ----
log "installing deploy panel"
rsync -a --delete --exclude node_modules --exclude .env "$SCRIPT_DIR/panel/" /srv/paas/panel/
chown -R deploy:deploy /srv/paas/panel
sudo -u deploy bash -c 'cd /srv/paas/panel && (npm ci --omit=dev >/dev/null 2>&1 || npm install --omit=dev >/dev/null)'

if [[ ! -f /srv/paas/panel/.env ]]; then
    if [[ -z "${PANEL_ADMIN_PASSWORD:-}" ]]; then
        read -rsp "[bootstrap] choose a panel admin password: " PANEL_ADMIN_PASSWORD; echo
    fi
    HASH="$(sudo -u deploy node /srv/paas/panel/bin/hash-password.js "$PANEL_ADMIN_PASSWORD")"
    cat >/srv/paas/panel/.env <<EOF
PANEL_PORT=$PANEL_PORT
PANEL_BASE_URL=https://$PANEL_DOMAIN
PAAS_DB=/srv/paas/paas.db
PAAS_BIN=/usr/local/bin/paas
BUILD_DIR=/srv/paas/builds
LOG_DIR=/srv/paas/logs
DEPLOY_KEY_PUB=/home/deploy/.ssh/id_ed25519.pub
SESSION_SECRET=$(openssl rand -hex 32)
ADMIN_PASSWORD_HASH=$HASH
EOF
    chown deploy:deploy /srv/paas/panel/.env
    chmod 600 /srv/paas/panel/.env
fi

cat >/srv/paas/panel/run.sh <<'EOF'
#!/usr/bin/env bash
set -a; . /srv/paas/panel/.env; set +a
cd /srv/paas/panel
exec node server.js
EOF
chmod +x /srv/paas/panel/run.sh
chown deploy:deploy /srv/paas/panel/run.sh

log "registering panel with pm2 (user deploy)"
# -H + cd: pm2 must not inherit a cwd the deploy user cannot access
sudo -u deploy -H bash -c 'cd /srv/paas/panel && { pm2 describe paas-panel >/dev/null 2>&1 && pm2 restart paas-panel || pm2 start /srv/paas/panel/run.sh --name paas-panel --interpreter bash --time; }' >/dev/null
sudo -u deploy -H bash -c 'cd /srv/paas/panel && pm2 save' >/dev/null
env PATH="$PATH" pm2 startup systemd -u deploy --hp /home/deploy >/dev/null

# ----------------------------------------------------------------- firewall ----
log "firewall (22, 80/tcp, 443/tcp+udp)"
ufw allow 22/tcp >/dev/null
ufw allow 80/tcp >/dev/null
ufw allow 443/tcp >/dev/null
# 443/udp = HTTP/3 (QUIC). Caddy listens on UDP 443 by default and advertises
# h3 via Alt-Svc — with only the tcp rule, QUIC died silently at this host
# firewall even though OPNsense forwarded it (found 2026-08-20).
ufw allow 443/udp >/dev/null
ufw --force enable >/dev/null

# converge generated configs (fpm pools need the sudoers + paas.conf from above)
if [[ "${WITH_LARAVEL:-0}" == "1" ]]; then
    sudo -u deploy -H bash -c 'cd /srv/paas && /usr/local/bin/paas regen' >/dev/null
fi

log "DONE.
  panel:      https://$PANEL_DOMAIN  (after DNS points here)
  deploy key: $(cat /home/deploy/.ssh/id_ed25519.pub)
  runtimes:   $(grep -s '^RUNTIMES=' /srv/paas/paas.conf | cut -d= -f2 || echo node)
  next:       ssh deploy@<this-host>  ->  paas create <app> --domains <domain> [--cache] [--runtime laravel]"
