#!/usr/bin/env bash
#
# paas-self-deploy — update the PLATFORM itself (paas CLI + panel) on this
# server from a repo tarball, re-running bootstrap.sh with the settings the
# instance already has. This is what the Jenkins job `paas-platform`
# (ci/Jenkinsfile.platform) calls over SSH; it is also usable by hand.
#
#   tar czf - -C <repo> . | sudo -n paas-self-deploy deploy <commit> [<message>]
#   sudo paas-self-deploy verify              # health summary, exit 1 if unhealthy
#   sudo paas-self-deploy rollback            # swap back to the previous checkout + bootstrap
#   sudo paas-self-deploy status              # what is deployed, last log lines
#
# Runs as root (installed to /usr/local/sbin by bootstrap.sh; the deploy user
# may run it via the exact-command sudoers line in /etc/sudoers.d/paas-ci).
# Nothing here is instance-specific: the panel domain, flavour (Node-only or
# Node+Laravel), PHP versions, system-domain base and ACME e-mail are read
# back from the running instance, so a Laravel server stays Laravel and a
# server with its own panel domain keeps it. Apps, paas.db and the panel
# .env are never touched — bootstrap.sh is idempotent.
#
# Steps for `deploy`: extract to a temp dir → lint (bash -n, node --check) →
# copy the current checkout to <checkout>.prev → rsync the new tree in →
# bootstrap.sh → verify. Lint failures leave the server untouched; a failed
# bootstrap/verify leaves the new tree in place (so it can be inspected) and
# `rollback` restores the previous one.

set -euo pipefail
[[ "$(id -u)" -eq 0 ]] || { echo "run as root (sudo)" >&2; exit 1; }

LOG=/var/log/paas-self-deploy.log
LOCK=/run/lock/paas-self-deploy.lock
PANEL_ENV=/srv/paas/panel/.env
PAAS_CONF=/srv/paas/paas.conf

log() { printf '[self-deploy] %s\n' "$*" | tee -a "$LOG"; }
die() { log "ERROR: $*"; exit 1; }

# ---- locate the checkout the same way paas-init does -------------------
CHECKOUT="${PAAS_CHECKOUT:-}"
if [[ -z "$CHECKOUT" ]]; then
    for d in /home/younes/dotnews-paas /home/*/dotnews-paas /root/dotnews-paas /opt/dotnews-paas; do
        [[ -f "$d/bootstrap.sh" && -f "$d/bin/paas" ]] && { CHECKOUT="$d"; break; }
    done
fi
[[ -n "$CHECKOUT" ]] || CHECKOUT=/home/younes/dotnews-paas
PREV="$CHECKOUT.prev"
OWNER="$(stat -c %U "$CHECKOUT" 2>/dev/null || echo younes)"
id "$OWNER" >/dev/null 2>&1 || OWNER=root

# ---- instance settings bootstrap.sh must be re-run with -----------------
instance_env() {
    local panel base email runtimes php
    panel="$(sed -n 's#^PANEL_BASE_URL=https\?://##p' "$PANEL_ENV" 2>/dev/null | head -1)"
    base="$(sed -n 's/^SYSTEM_DOMAIN_BASE=//p' "$PAAS_CONF" 2>/dev/null | head -1)"
    email="$(sed -n 's/^[[:space:]]*email[[:space:]]\+\([^[:space:]]*\).*/\1/p' /etc/caddy/Caddyfile 2>/dev/null | head -1)"
    runtimes="$(sed -n 's/^RUNTIMES=//p' "$PAAS_CONF" 2>/dev/null | head -1)"
    php="$(sed -n 's/^PHP_VERSIONS=//p' "$PAAS_CONF" 2>/dev/null | head -1 | tr -d '"')"
    ENV=()
    [[ -n "$panel" ]] && ENV+=("PANEL_DOMAIN=$panel")
    [[ -n "$base" ]] && ENV+=("SYSTEM_DOMAIN_BASE=$base")
    [[ -n "$email" ]] && ENV+=("ACME_EMAIL=$email")
    if [[ "$runtimes" == *laravel* ]]; then
        ENV+=(WITH_LARAVEL=1)
        [[ -n "$php" ]] && ENV+=("PHP_VERSIONS=$php")
    fi
    # never prompt for an admin password on a re-run (the .env already exists;
    # bootstrap only asks when it is missing)
    ENV+=("PANEL_ADMIN_PASSWORD=${PANEL_ADMIN_PASSWORD:-unused}")
}

run_bootstrap() {
    instance_env
    log "bootstrap.sh (${ENV[*]/PANEL_ADMIN_PASSWORD=*/})"
    if ! ( cd "$CHECKOUT" && env "${ENV[@]}" ./bootstrap.sh ) >>"$LOG" 2>&1; then
        tail -20 "$LOG"
        die "bootstrap.sh failed — full log: $LOG"
    fi
    # keep this script itself current (bootstrap installs it too, belt and braces)
    [[ -f "$CHECKOUT/infra/paas-self-deploy" ]] && install -m 0755 "$CHECKOUT/infra/paas-self-deploy" /usr/local/sbin/paas-self-deploy
    [[ -f "$CHECKOUT/infra/paas-ci-ssh" ]] && install -m 0755 "$CHECKOUT/infra/paas-ci-ssh" /usr/local/bin/paas-ci-ssh
    return 0
}

lint_tree() { # $1 dir
    local d="$1" f ok=1
    [[ -f "$d/bootstrap.sh" && -f "$d/bin/paas" && -f "$d/panel/server.js" ]] \
        || die "tarball does not look like a dotnews-paas checkout (bootstrap.sh, bin/paas, panel/server.js)"
    for f in "$d"/bootstrap.sh "$d"/bin/* "$d"/infra/*.sh "$d"/infra/paas-self-deploy "$d"/infra/paas-ci-ssh; do
        [[ -f "$f" ]] || continue
        head -1 "$f" | grep -q 'bash' || continue
        bash -n "$f" || { log "bash -n failed: ${f#"$d"/}"; ok=0; }
    done
    if command -v node >/dev/null; then
        for f in "$d"/panel/*.js "$d"/panel/lib/*.js "$d"/panel/bin/*.js; do
            [[ -f "$f" ]] || continue
            node --check "$f" 2>>"$LOG" || { log "node --check failed: ${f#"$d"/}"; ok=0; }
        done
    fi
    [[ "$ok" -eq 1 ]] || die "lint failed — nothing was changed on this server"
    log "lint ok"
}

verify() {
    local port fails=0 code
    port="$(sed -n 's/^PANEL_PORT=//p' "$PANEL_ENV" 2>/dev/null | head -1)"; port="${port:-3900}"
    for s in caddy varnish; do
        if systemctl is-enabled "$s" >/dev/null 2>&1; then
            systemctl is-active --quiet "$s" && log "$s: active" || { log "$s: NOT active"; fails=$((fails+1)); }
        fi
    done
    local pm2
    pm2="$(sudo -u deploy -H pm2 jlist 2>/dev/null | node -e '
        let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const j=JSON.parse(s);const p=j.find(x=>x.name==="paas-panel");console.log(p?p.pm2_env.status:"absent")}catch{console.log("unknown")}})' 2>/dev/null || echo unknown)"
    [[ "$pm2" == "online" ]] && log "pm2 paas-panel: online" || { log "pm2 paas-panel: $pm2"; fails=$((fails+1)); }
    code=000
    for _ in $(seq 1 15); do
        code="$(curl -s -o /dev/null -m 5 -w '%{http_code}' "http://127.0.0.1:$port/login" || echo 000)"
        [[ "$code" == "200" ]] && break
        sleep 2
    done
    [[ "$code" == "200" ]] && log "panel /login: 200" || { log "panel /login: $code"; fails=$((fails+1)); }
    if [[ -f /srv/paas/panel/server.js && -f "$CHECKOUT/panel/server.js" ]]; then
        cmp -s /srv/paas/panel/server.js "$CHECKOUT/panel/server.js" \
            && log "installed panel matches checkout" || { log "installed panel DIFFERS from checkout"; fails=$((fails+1)); }
    fi
    [[ -f "$CHECKOUT/.deployed" ]] && log "deployed: $(cat "$CHECKOUT/.deployed")"
    [[ "$fails" -eq 0 ]] || die "$fails check(s) failed — 'paas-self-deploy rollback' restores the previous checkout"
    log "verify ok"
}

cmd="${1:-}"; shift || true
case "$cmd" in
deploy)
    commit="${1:-unknown}"; msg="${2:-}"
    [[ "$commit" =~ ^[A-Za-z0-9._/-]{1,64}$ ]] || die "bad commit id"
    exec 9>"$LOCK"; flock -n 9 || die "another self-deploy is running"
    [[ ! -t 0 ]] || die "expected a .tar.gz of the repo on stdin"
    tmp="$(mktemp -d /tmp/paas-self-deploy.XXXXXX)"
    trap 'rm -rf "$tmp"' EXIT
    log "===== deploy $commit ${msg:+— $msg }(by ${SUDO_USER:-root}) ====="
    tar xzf - -C "$tmp" --warning=no-unknown-keyword || die "could not extract tarball"
    lint_tree "$tmp"
    if [[ -d "$CHECKOUT" ]]; then
        rsync -a --delete "$CHECKOUT/" "$PREV/"
        log "previous checkout saved to $PREV"
    fi
    install -d -o "$OWNER" -g "$OWNER" "$CHECKOUT"
    rsync -a --delete --exclude .git --exclude .idea --exclude .claude --exclude node_modules \
        --exclude .env --exclude '*.db' "$tmp/" "$CHECKOUT/"
    printf '%s %s %s\n' "$commit" "$(date -Is)" "$msg" >"$CHECKOUT/.deployed"
    chown -R "$OWNER:$OWNER" "$CHECKOUT"
    log "checkout updated ($CHECKOUT @ $commit)"
    run_bootstrap
    verify
    log "===== deploy $commit done ====="
    ;;
rollback)
    exec 9>"$LOCK"; flock -n 9 || die "another self-deploy is running"
    [[ -d "$PREV" && -f "$PREV/bootstrap.sh" ]] || die "no previous checkout at $PREV"
    log "===== rollback to $(cat "$PREV/.deployed" 2>/dev/null || echo 'previous checkout') ====="
    tmp="$CHECKOUT.swap.$$"
    mv "$CHECKOUT" "$tmp" && mv "$PREV" "$CHECKOUT" && mv "$tmp" "$PREV"
    run_bootstrap
    verify
    log "===== rollback done ====="
    ;;
verify)
    verify
    ;;
status)
    echo "checkout: $CHECKOUT"
    echo "deployed: $(cat "$CHECKOUT/.deployed" 2>/dev/null || echo '(unknown — not deployed via self-deploy yet)')"
    [[ -d "$PREV" ]] && echo "previous: $(cat "$PREV/.deployed" 2>/dev/null || echo '(present)')"
    echo "--- last log lines ($LOG)"
    tail -15 "$LOG" 2>/dev/null || true
    ;;
*)
    sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'
    exit 2
    ;;
esac
