#!/usr/bin/env bash
# ╔══════════════════════════════════════════════════════════════════╗
# ║  Gateway — One-Shot Bootstrap                                    ║
# ║                                                                  ║
# ║    curl -fsSL https://thegateway.pro/install.sh | bash           ║
# ║                                                                  ║
# ║  Detects arch, verifies the binary, fetches the installer,       ║
# ║  then                                                          ║
# ║  hands off. Defaults to the `vps` profile because that's what    ║
# ║  curl-pipe-bash is overwhelmingly used for (a fresh Linux VPS    ║
# ║  with no peripherals). RPi operators should run the full         ║
# ║  installer directly so they can pick the rpi profile.            ║
# ╚══════════════════════════════════════════════════════════════════╝
#
# What this script does:
#   1. sudo-elevates if not root
#   2. Picks the right arch (amd64 / arm64)
#   3. Downloads the original gatewayd .gz archive (legacy: raw binary)
#   4. Verifies archive + uncompressed SHA256 before handing off
#   5. Downloads the full installer (install-gateway.sh) into /tmp
#   6. Runs install-gateway.sh --profile vps --binary /tmp/gatewayd ...
#
# Flags (passed through pipe by setting env vars before piping, or as
# args when run directly):
#   GATEWAY_PROFILE=vps|rpi|node|full|custom   default: vps
#   GATEWAY_VERSION=latest|vX.Y.Z              default: latest
#   GATEWAY_BASE_URL=https://thegateway.pro    where install-gateway.sh + binary live
#   GATEWAY_FROM_SOURCE=1                      clone repo and `go build` instead of binary download
#   GATEWAY_NONINTERACTIVE=1                   skip every prompt
#   GATEWAY_UPDATE=1                           update binary + install missing runtime packages;
#                                              keep config / db / certs / systemd unit in place.
#                                              Uses --no-upgrade for existing packages; repairs
#                                              dependencies even when the binary is current.
#
# Example (build from source on a VPS):
#   curl -fsSL https://thegateway.pro/install.sh | \
#     GATEWAY_FROM_SOURCE=1 GATEWAY_PROFILE=vps GATEWAY_NONINTERACTIVE=1 bash
#
# Example (update an existing install to the latest binary):
#   curl -fsSL https://thegateway.pro/install.sh | GATEWAY_UPDATE=1 bash
#
# Example (update + pin to a specific release):
#   curl -fsSL https://thegateway.pro/install.sh | \
#     GATEWAY_UPDATE=1 GATEWAY_VERSION=v2.16.0 bash

set -euo pipefail

# ── Colours so curl-pipe-bash shows progress visibly ──
GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
log()  { echo -e "${GREEN}[+]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
err()  { echo -e "${RED}[✗]${NC} $*" >&2; exit 1; }
info() { echo -e "${CYAN}[i]${NC} $*"; }

# ── Sudo escalation when needed ──
# Curl-pipe-bash invocations run as the operator's user; the deeper
# installer needs root for apt, systemd, /etc edits. Re-exec under
# sudo so the operator doesn't have to remember `sudo` in the curl
# line.
if [ "$(id -u)" -ne 0 ]; then
    # `curl … | bash` has no script FILE: $0 is "bash", so `sudo … bash "$0"`
    # would become `sudo … bash bash` and fail. We cannot re-exec a consumed
    # stdin pipe, so give a clear instruction instead of the cryptic failure.
    # The file case (./install.sh) still auto-re-execs under sudo below.
    if [ ! -f "$0" ]; then
        err "This installer needs root. Re-run with sudo, e.g.: curl -fsSL https://thegateway.pro/install.sh | sudo bash"
    fi
    if command -v sudo >/dev/null 2>&1; then
        warn "Re-running under sudo..."
        # `bash -c` keeps env vars (GATEWAY_*) propagated; -E on sudo
        # preserves them through the privilege escalation.
        exec sudo -E env "BASH_ENV=${BASH_ENV:-}" \
            GATEWAY_PROFILE="${GATEWAY_PROFILE:-}" \
            GATEWAY_VERSION="${GATEWAY_VERSION:-}" \
            GATEWAY_BASE_URL="${GATEWAY_BASE_URL:-}" \
            GATEWAY_FROM_SOURCE="${GATEWAY_FROM_SOURCE:-}" \
            GATEWAY_NONINTERACTIVE="${GATEWAY_NONINTERACTIVE:-}" \
            GATEWAY_UPDATE="${GATEWAY_UPDATE:-}" \
            GATEWAY_REPO_URL="${GATEWAY_REPO_URL:-}" \
            bash "$0" "$@"
    else
        err "sudo not available and not running as root"
    fi
fi

# ── Defaults ──
PROFILE="${GATEWAY_PROFILE:-vps}"
VERSION="${GATEWAY_VERSION:-latest}"
BASE_URL="${GATEWAY_BASE_URL:-https://thegateway.pro}"
FROM_SOURCE="${GATEWAY_FROM_SOURCE:-0}"
NONINTERACTIVE="${GATEWAY_NONINTERACTIVE:-0}"
UPDATE_MODE="${GATEWAY_UPDATE:-0}"

# Whitelist of supported profiles so a typo doesn't silently fall back
# to "vps" and surprise the operator. Empty profile means interactive;
# we map that to vps for non-interactive mode below.
case "$PROFILE" in
    ""|vps|rpi|node|full|custom) ;;
    *) err "GATEWAY_PROFILE must be one of: vps rpi node full custom (got: $PROFILE)" ;;
esac

# ── Architecture detection ──
HOST_ARCH=$(uname -m)
case "$HOST_ARCH" in
    x86_64|amd64)   ARCH=amd64 ;;
    aarch64|arm64)  ARCH=arm64 ;;
    *) err "Unsupported architecture: $HOST_ARCH (supported: x86_64, aarch64)" ;;
esac

# ── Sanity: we're on a Linux that the installer supports ──
# Source os-release inside a subshell so its VERSION=<distro-version>
# export doesn't stomp on OUR VERSION (the gateway release ref). The
# subshell prints back only the fields we care about for the warning
# heuristic. Without the subshell, the parent VERSION ended up as
# "24.04.3 LTS (Noble Numbat)" and the binary fetch URL became a
# malformed-input curl rejection.
if [ ! -f /etc/os-release ]; then
    err "Unsupported OS — /etc/os-release missing. The installer targets Debian / Ubuntu."
fi
read OS_ID OS_ID_LIKE OS_PRETTY < <(. /etc/os-release; printf '%s %s %q\n' "${ID:-}" "${ID_LIKE:-}" "${PRETTY_NAME:-unknown}")
case "${OS_ID}${OS_ID_LIKE}" in
    *debian*|*ubuntu*) ;;
    *) warn "Detected OS: ${OS_PRETTY}. Installer is tested on Debian 12+ / Ubuntu 22.04+; YMMV." ;;
esac

log "Gateway bootstrap"
info "  profile:        $PROFILE"
info "  arch:           $ARCH"
info "  from_source:    $([ "$FROM_SOURCE" = "1" ] && echo "yes — will clone repo + go build" || echo "no — download prebuilt binary")"
info "  version:        $VERSION"
info "  base_url:       $BASE_URL"

# ── Workspace ──
WORK=$(mktemp -d -t gateway-bootstrap.XXXXXX)
trap 'rm -rf "$WORK"' EXIT
cd "$WORK"

# ── Required local tooling for the bootstrap itself ──
need_pkg=""
command -v curl >/dev/null 2>&1 || need_pkg="$need_pkg curl"
command -v tar  >/dev/null 2>&1 || need_pkg="$need_pkg tar"
if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then
    need_pkg="$need_pkg coreutils"
fi
if [ -n "$need_pkg" ]; then
    log "Installing bootstrap tools:$need_pkg"
    apt-get update -qq
    apt-get install -y -qq $need_pkg
fi

# ── Fetch the deeper installer ──
INSTALLER="$WORK/install-gateway.sh"
log "Fetching installer from $BASE_URL/install-gateway.sh"
if ! curl -fsSL "$BASE_URL/install-gateway.sh" -o "$INSTALLER"; then
    err "Failed to download install-gateway.sh from $BASE_URL/install-gateway.sh — check connectivity and GATEWAY_BASE_URL"
fi
chmod +x "$INSTALLER"

# ── Acquire the gatewayd binary ──
BINARY_PATH=""
if [ "$FROM_SOURCE" = "1" ]; then
    # Source-build path. Go is installed by install-gateway.sh's
    # install_go() — we just shell that out here directly so the
    # operator's first feedback isn't "what is Go and why am I
    # waiting 6 minutes for it to install".
    log "Source-build mode: installing Go + cloning repo"
    if ! command -v go >/dev/null 2>&1; then
        GO_VER=1.24.2
        curl -fsSL "https://go.dev/dl/go${GO_VER}.linux-${ARCH}.tar.gz" -o /tmp/go.tgz
        rm -rf /usr/local/go
        tar -C /usr/local -xzf /tmp/go.tgz
        rm /tmp/go.tgz
        export PATH=$PATH:/usr/local/go/bin
    fi
    if ! command -v git >/dev/null 2>&1; then
        apt-get install -y -qq git
    fi
    log "Cloning gateway source"
    git clone --depth 1 "${GATEWAY_REPO_URL:-https://github.com/thegateway-pro/gateway.git}" "$WORK/src" \
        || err "git clone failed — set GATEWAY_REPO_URL to a reachable clone URL if the default is wrong"
    cd "$WORK/src"
    log "Building gatewayd (this takes ~2 min on a modest VPS)"
    GIT_COMMIT=$(git rev-parse --short HEAD)
    GIT_VERSION=$(git describe --tags --always 2>/dev/null || echo dev)
    BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
    LDFLAGS="-X main.version=$GIT_VERSION -X main.commit=$GIT_COMMIT -X main.buildDate=$BUILD_DATE"
    GOOS=linux GOARCH="$ARCH" CGO_ENABLED=0 \
        go build -ldflags "$LDFLAGS" -o "$WORK/gatewayd" ./cmd/gatewayd
    BINARY_PATH="$WORK/gatewayd"
    cd "$WORK"
else
    # Prefer the original compressed release artifact when its checksum is
    # present. Older pinned releases keep the existing raw-binary path.
    RELEASE_BASE="$BASE_URL/releases/$VERSION"
    MANIFEST_URL="$RELEASE_BASE/SHA256SUMS"
    if ! curl -fsSL "$MANIFEST_URL" -o "$WORK/SHA256SUMS"; then
        err "Failed to download $MANIFEST_URL — refusing to run an unverified binary"
    fi
    verify_release_file() {
        local path="$1" name="$2" manifest="$3" expected_hash actual_hash count
        count=$(awk -v file="$name" '$2 == file {n++} END {print n+0}' "$manifest")
        [ "$count" = 1 ] || err "Manifest must contain exactly one checksum for $name"
        expected_hash=$(awk -v file="$name" '$2 == file {print $1}' "$manifest")
        [[ "$expected_hash" =~ ^[0-9a-fA-F]{64}$ ]] || err "Invalid SHA256 for $name"
        if command -v sha256sum >/dev/null 2>&1; then
            actual_hash=$(sha256sum "$path" | awk '{print $1}')
        else
            actual_hash=$(shasum -a 256 "$path" | awk '{print $1}')
        fi
        [ "$actual_hash" = "$expected_hash" ] || err "Checksum mismatch for $name"
        info "  $name sha256: $actual_hash"
    }
    ARTIFACT="gatewayd-linux-$ARCH"
    if awk -v file="$ARTIFACT.gz" '$2 == file {found=1} END {exit !found}' "$WORK/SHA256SUMS"; then
        command -v gzip >/dev/null 2>&1 || err "gzip is required to unpack this release"
        log "Downloading original release archive from $RELEASE_BASE/$ARTIFACT.gz"
        curl -fSL "$RELEASE_BASE/$ARTIFACT.gz" -o "$WORK/gatewayd.gz" || err "Release archive download failed"
        verify_release_file "$WORK/gatewayd.gz" "$ARTIFACT.gz" "$WORK/SHA256SUMS"
        gzip -dc "$WORK/gatewayd.gz" > "$WORK/gatewayd" || err "Release archive could not be decompressed"
        # Also check the uncompressed upstream checksum before handing off.
        curl -fsSL "$RELEASE_BASE/UPSTREAM_SHA256SUMS" -o "$WORK/UPSTREAM_SHA256SUMS" || err "Upstream checksum manifest download failed"
        verify_release_file "$WORK/gatewayd" "$ARTIFACT" "$WORK/UPSTREAM_SHA256SUMS"
    else
        log "Downloading binary from $RELEASE_BASE/$ARTIFACT"
        curl -fSL "$RELEASE_BASE/$ARTIFACT" -o "$WORK/gatewayd" || err "Binary download failed"
        verify_release_file "$WORK/gatewayd" "$ARTIFACT" "$WORK/SHA256SUMS"
    fi
    chmod +x "$WORK/gatewayd"
    BINARY_PATH="$WORK/gatewayd"
fi

# ── Update mode: swap the binary, leave everything else alone ──
#
# The full installer is overkill (and slightly risky — apt updates,
# systemd-unit regeneration, TLS-cert paths) for a routine binary
# bump. Update mode skips all of that and just stops the daemon,
# replaces /usr/local/bin/gatewayd, and starts it again. Bails
# early if there's nothing to update.
if [ "$UPDATE_MODE" = "1" ]; then
    if [ ! -x /usr/local/bin/gatewayd ]; then
        err "GATEWAY_UPDATE=1 set, but /usr/local/bin/gatewayd doesn't exist — run a fresh install first"
    fi
    log "Update mode: swapping gatewayd binary in place"
    # Runtime packages a newer gatewayd may exec that an older install never
    # got (openvpn was missing from every install before 2026-10-07). Only
    # ABSENT packages are installed, --no-upgrade: nothing already on the box
    # moves. Runs before the version check so re-running an update repairs
    # the dependencies even when the binary is current. Keep in sync with
    # install_base_packages in install-gateway.sh.
    missing=""
    for p in nftables iptables iproute2 conntrack wireguard-tools openvpn curl wget jq \
             ca-certificates openssl openssh-client sqlite3 tcpdump ethtool cryptsetup \
             procps iputils-ping kmod dbus python3 python3-yaml; do
        dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q "ok installed" || missing="$missing $p"
    done
    if ! command -v dhcpcd >/dev/null 2>&1; then
        for p in dhcpcd-base dhcpcd5 dhcpcd; do
            apt-cache show "$p" >/dev/null 2>&1 && { missing="$missing $p"; break; }
        done
    fi
    if [ -n "$missing" ]; then
        log "Installing missing runtime packages:$missing"
        if apt-get update -qq && apt-get install -y -qq --no-upgrade $missing; then
            case " $missing " in *" openvpn "*) systemctl disable --now openvpn 2>/dev/null || true ;; esac
        else
            warn "could not install:$missing — continuing with the binary update; features needing them stay off"
        fi
    fi
    old_ver=$(/usr/local/bin/gatewayd --version 2>&1 | head -1 || echo "unknown")
    new_ver=$("$BINARY_PATH" --version 2>&1 | head -1 || echo "unknown")
    info "  current: $old_ver"
    info "  new:     $new_ver"
    if [ "$old_ver" = "$new_ver" ] && [ "$old_ver" != "unknown" ]; then
        info "Versions match — nothing to do (re-run with GATEWAY_VERSION=<ref> to force)"
        exit 0
    fi
    # Snapshot the running binary in case the new one wedges and
    # the operator needs to roll back fast. Same shape as the
    # deploy-pi.sh / deploy-vps.sh inline backup that we've been
    # using by hand all day.
    backup="/usr/local/bin/gatewayd.bak.$(date -u +%Y%m%dT%H%M%SZ)"
    cp /usr/local/bin/gatewayd "$backup"
    info "  backup:  $backup"

    # NEVER SIGKILL — corrupts the SQLite WAL on a write-in-progress
    # and we've burned an hour of recovery time on that before
    # (per project memory feedback_deploy_no_sigkill.md). Graceful
    # stop only.
    systemctl stop gatewayd
    cp "$BINARY_PATH" /usr/local/bin/gatewayd
    chmod +x /usr/local/bin/gatewayd
    systemctl start gatewayd
    sleep 4
    if systemctl is-active gatewayd >/dev/null 2>&1; then
        log "gatewayd updated: $(/usr/local/bin/gatewayd --version 2>&1 | head -1)"
        info "Rollback: sudo systemctl stop gatewayd && sudo cp $backup /usr/local/bin/gatewayd && sudo systemctl start gatewayd"
    else
        warn "gatewayd failed to start with new binary — rolling back to $backup"
        systemctl stop gatewayd
        cp "$backup" /usr/local/bin/gatewayd
        systemctl start gatewayd
        err "Rolled back. Check: journalctl -u gatewayd -n 50"
    fi
    exit 0
fi

# ── Hand off to the full installer ──
INSTALLER_ARGS=( --profile "$PROFILE" --binary "$BINARY_PATH" )
if [ "$NONINTERACTIVE" = "1" ]; then
    INSTALLER_ARGS+=( --non-interactive )
fi
log "Handing off to install-gateway.sh ${INSTALLER_ARGS[*]}"
exec bash "$INSTALLER" "${INSTALLER_ARGS[@]}"
