#!/usr/bin/env bash
#
#  ISP Boost — one-command installer for a fresh Ubuntu 22.04 / 24.04 server.
#
#  Customers run:
#    URL=https://get.ispboost.com/install.sh && \
#      if [ -f /usr/bin/curl ];then curl -ksSO $URL ;else wget --no-check-certificate -O install.sh $URL;fi; \
#      bash install.sh
#
#  Flags (optional):
#    --domain <d>     serve on this hostname (default: the server's public IP)
#    --email  <e>     admin email (default: admin@<domain>)
#    --license <KEY>  activate this license key (default: start a 7-day trial)
#    --ssl            obtain a Let's Encrypt certificate (needs a real --domain)
#    --no-radius      skip FreeRADIUS (install the management panel only)
#    --bundle-url <u> override the app bundle location
#    --local <dir>    install from a local unpacked bundle (offline / dev)
#
set -uo pipefail

# ─────────────────────────────  settings  ─────────────────────────────
H3R_DIR=/opt/h3radius
LOG=/var/log/h3radius-install.log
BUNDLE_URL_DEFAULT="https://get.ispboost.com/h3radius-app.tar.gz"
LICENSE_SERVER="https://license.ispboost.com"
PHP=8.4

usage() {
    cat <<'USAGE'
ISP Boost installer — for a fresh Ubuntu 22.04 / 24.04 server (run as root).

  Usage:  bash install.sh [options]

  Options:
    --domain <host>   Serve on this hostname (default: the server's public IP)
    --email  <addr>   Administrator email (default: admin@<domain>)
    --license <KEY>   Activate this license key (default: start a 7-day trial)
    --ssl             Obtain a free Let's Encrypt HTTPS certificate
                        (requires a real --domain whose DNS points here)
    --no-radius       Install the management panel only, skip FreeRADIUS
    --bundle-url <u>  Override the application bundle URL
    --local <dir>     Install from a local unpacked bundle (offline)
    -h, --help        Show this help and exit

  Example:
    bash install.sh --domain panel.myisp.com --email me@myisp.com --license ABCD-... --ssl

  Installs: PHP 8.4 · MySQL 8.4 · Redis · Nginx · FreeRADIUS 3.2 · ionCube ·
            the ISP Boost panel · queue + scheduler. Credentials are written to
            /root/h3radius-credentials.txt.
USAGE
}

DOMAIN="" ADMIN_EMAIL="" LICENSE_KEY="" DO_SSL=0 WITH_RADIUS=1 REINSTALL=0
BUNDLE_URL="$BUNDLE_URL_DEFAULT" LOCAL_BUNDLE=""
while [ $# -gt 0 ]; do
    case "$1" in
        --domain)      DOMAIN="$2"; shift 2;;
        --email)       ADMIN_EMAIL="$2"; shift 2;;
        --license)     LICENSE_KEY="$2"; shift 2;;
        --ssl)         DO_SSL=1; shift;;
        --no-radius)   WITH_RADIUS=0; shift;;
        --bundle-url)  BUNDLE_URL="$2"; shift 2;;
        --local)       LOCAL_BUNDLE="$2"; shift 2;;
        --reinstall|--force) REINSTALL=1; shift;;   # re-run over an existing install
        ipssl)         DO_SSL=1; shift;;      # aaPanel-style bare word
        -h|--help)     usage; exit 0;;
        *)             shift;;
    esac
done

# ─────────────────────────────  pretty UI  ────────────────────────────
if [ -t 1 ]; then
    cG=$'\033[1;32m'; cR=$'\033[1;31m'; cY=$'\033[1;33m'; cB=$'\033[1;36m'
    cD=$'\033[2m'; cW=$'\033[1m'; c0=$'\033[0m'
else
    cG= cR= cY= cB= cD= cW= c0=
fi
: > "$LOG"

banner() {
    printf '%s\n' "$cB"
    cat <<'ART'
   ██╗███████╗██████╗     ██████╗  ██████╗  ██████╗ ███████╗████████╗
   ██║██╔════╝██╔══██╗    ██╔══██╗██╔═══██╗██╔═══██╗██╔════╝╚══██╔══╝
   ██║███████╗██████╔╝    ██████╔╝██║   ██║██║   ██║███████╗   ██║
   ██║╚════██║██╔═══╝     ██╔══██╗██║   ██║██║   ██║╚════██║   ██║
   ██║███████║██║         ██████╔╝╚██████╔╝╚██████╔╝███████║   ██║
   ╚═╝╚══════╝╚═╝         ╚═════╝  ╚═════╝  ╚═════╝ ╚══════╝   ╚═╝
ART
    printf '%s   Supercharge your ISP · complete RADIUS + business platform%s\n' "$cD" "$c0"
    printf '%s   installer · fresh Ubuntu 22.04 / 24.04%s\n\n' "$cD" "$c0"
}
phase()  { printf '\n%s▎ %s%s\n' "$cB" "$*" "$c0"; }
info()   { printf '   %s%s%s\n'   "$cD" "$*" "$c0"; }
die()    { printf '\n%s✗ %s%s\n' "$cR" "$*" "$c0"; printf '%s  full log: %s%s\n' "$cD" "$LOG" "$c0"; exit 1; }

# run "label" cmd...  → animated spinner, ✓/✗, halts on failure
run() {
    local label="$1"; shift
    ( "$@" ) >>"$LOG" 2>&1 &
    local pid=$! i=0 fr='⣾⣽⣻⢿⡿⣟⣯⣷'
    if [ -t 1 ]; then
        while kill -0 "$pid" 2>/dev/null; do
            i=$(( (i+1) % 8 ))
            printf '\r   %s%s%s %s ' "$cY" "${fr:$i:1}" "$c0" "$label"
            sleep 0.12
        done
    fi
    if wait "$pid"; then
        printf '\r   %s✓%s %s\033[K\n' "$cG" "$c0" "$label"
    else
        printf '\r   %s✗%s %s\033[K\n' "$cR" "$c0" "$label"
        printf '%s' "$cD"; tail -14 "$LOG" | sed 's/^/       /'; printf '%s' "$c0"
        die "step failed: $label"
    fi
}

# check "label" "actual" "want-substring"  → ✓ shows value / ✗ halts
check() {
    local label="$1" got="$2" want="$3"
    if printf '%s' "$got" | grep -q -- "$want"; then
        printf '   %s✓%s %s %s%s%s\n' "$cG" "$c0" "$label" "$cD" "$got" "$c0"
    else
        printf '   %s✗%s %s %s(got:%s want:%s)%s\n' "$cR" "$c0" "$label" "$cD" "${got:-<empty>}" "$want" "$c0"
        die "verification failed: $label"
    fi
}

# check_http "label" url want-code  → retries (php-fpm/opcache may lag a cache warm)
check_http() {
    local label="$1" url="$2" want="$3" i code=000
    for i in 1 2 3 4 5 6 7 8 9 10; do
        code="$(curl -s -o /dev/null -w '%{http_code}' "$url" 2>/dev/null)"
        [ "$code" = "$want" ] && break
        sleep 1
    done
    check "$label" "$code" "$want"
}

genpw() { tr -dc 'A-HJKMNP-Za-hjkmnp-z2-9' </dev/urandom | head -c "${1:-24}"; }
export DEBIAN_FRONTEND=noninteractive

# ─────────────────────────────  0. preflight  ─────────────────────────
banner
phase "Preflight checks"
[ "$(id -u)" = 0 ] || die "please run as root (sudo)."
# A UTF-8 locale avoids the noisy perl/apt "Setting locale failed" warnings.
if ! locale -a 2>/dev/null | grep -qiE 'en_US\.utf-?8'; then
    apt-get install -y -qq locales >>"$LOG" 2>&1 || true
    locale-gen en_US.UTF-8 >>"$LOG" 2>&1 || true
    update-locale LANG=en_US.UTF-8 >>"$LOG" 2>&1 || true
fi
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
. /etc/os-release 2>/dev/null || true
case "${VERSION_ID:-}" in
    22.04|24.04) check "Ubuntu ${VERSION_ID}" "$PRETTY_NAME" "Ubuntu";;
    *) die "unsupported OS (${PRETTY_NAME:-unknown}); need Ubuntu 22.04 or 24.04.";;
esac
MEM_MB=$(awk '/MemTotal/{print int($2/1024)}' /proc/meminfo)
[ "$MEM_MB" -ge 1700 ] || info "warning: ${MEM_MB}MB RAM (2GB+ recommended)"
check "Memory" "${MEM_MB}MB" "MB"
for c in curl tar; do command -v $c >/dev/null || run "install $c" apt-get install -y -qq $c; done
[ -z "$DOMAIN" ] && DOMAIN="$(curl -fsS --max-time 8 https://api.ipify.org 2>/dev/null || hostname -I | awk '{print $1}')"
[ -z "$ADMIN_EMAIL" ] && ADMIN_EMAIL="admin@${DOMAIN}"
info "panel address : http://${DOMAIN}"
info "admin email   : ${ADMIN_EMAIL}"
info "FreeRADIUS    : $([ "$WITH_RADIUS" = 1 ] && echo yes || echo no)"

# Already installed? This is the FRESH installer — re-running it can regenerate
# the app key / DB password and reseed. Stop here (safe no-op) unless the caller
# explicitly asks to reinstall. Updates happen automatically or via upgrade.sh.
if [ -f "${H3R_DIR}/backend/artisan" ] && grep -q '^APP_KEY=base64' "${H3R_DIR}/backend/.env" 2>/dev/null; then
    INSTALLED_VER="$(cat "${H3R_DIR}/VERSION" 2>/dev/null || echo '?')"
    if [ "$REINSTALL" != 1 ]; then
        printf '\n%s✓ ISP Boost is already installed%s (v%s) at %s.\n' "$cG" "$c0" "$INSTALLED_VER" "$H3R_DIR"
        info "Nothing to do — this installer is for fresh servers."
        info "• It updates itself automatically (daily), or run:  ${H3R_DIR}/scripts/self-update.sh"
        info "• To repair/reinstall in place (keeps your DB, .env and data):  bash install.sh --reinstall"
        info "• Panel: http://${DOMAIN}"
        exit 0
    fi
    info "reinstall mode: existing DB, .env (app key + DB password) and data are preserved"
fi

# ─────────────────────────────  1. repositories  ──────────────────────
phase "Package repositories"
run "apt update"                 apt-get update -qq
run "prerequisites"              apt-get install -y -qq software-properties-common ca-certificates gnupg lsb-release apt-transport-https
run "PHP ${PHP} repo (ondrej)"   add-apt-repository -y ppa:ondrej/php
# MySQL 8.4 LTS repo (Ubuntu ships EOL 8.0). The published key expired in 2025,
# so we import the renewed key from the Ubuntu keyserver.
mysql_repo() {
    gpg --batch --no-default-keyring --keyring /tmp/mk.gpg \
        --keyserver keyserver.ubuntu.com --recv-keys B7B3B788A8D3785C
    gpg --no-default-keyring --keyring /tmp/mk.gpg --export B7B3B788A8D3785C > /usr/share/keyrings/mysql.gpg
    echo "deb [signed-by=/usr/share/keyrings/mysql.gpg] http://repo.mysql.com/apt/ubuntu ${VERSION_CODENAME} mysql-8.4-lts mysql-tools" \
        > /etc/apt/sources.list.d/mysql.list
}
run "MySQL 8.4 repo"             mysql_repo
# FreeRADIUS 3.2 from NetworkRADIUS (Ubuntu ships 3.0). If the repo/key can't be
# set up we drop it and fall back to Ubuntu's 3.0.x — both work identically here.
if [ "$WITH_RADIUS" = 1 ]; then
    fr_repo() {
        install -d -m 0755 /etc/apt/keyrings
        if curl -fsSL --max-time 20 "https://packages.networkradius.com/pgp/packages%40networkradius.com" -o /etc/apt/keyrings/networkradius.asc; then
            echo "deb [signed-by=/etc/apt/keyrings/networkradius.asc] https://packages.networkradius.com/freeradius-3.2/ubuntu/${VERSION_CODENAME} ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/networkradius.list
        else
            rm -f /etc/apt/sources.list.d/networkradius.list   # fall back to Ubuntu 3.0
        fi
    }
    run "FreeRADIUS 3.2 repo (NetworkRADIUS)" fr_repo
fi
run "apt update"                 apt-get update -qq

# ─────────────────────────────  2. packages  ──────────────────────────
phase "System packages"
# Persist the root password so a re-run (or post-failure retry) can reconnect.
mysql_root="$(cat /root/.h3r_mysql_root 2>/dev/null || genpw 20)"
echo "$mysql_root" > /root/.h3r_mysql_root; chmod 600 /root/.h3r_mysql_root
echo "mysql-community-server mysql-community-server/root-pass password ${mysql_root}" | debconf-set-selections
echo "mysql-community-server mysql-community-server/re-root-pass password ${mysql_root}" | debconf-set-selections
# Defensive (no-op on a truly fresh box): if a prior half-removed mysql-common
# left dpkg thinking its conffile exists when the file is gone, its postinst
# fails ("my.cnf.fallback doesn't exist"). Recreate the minimal fallback so a
# retry always completes.
if dpkg -l mysql-common 2>/dev/null | grep -qE '^.[UFHiW]' && [ ! -f /etc/mysql/my.cnf.fallback ]; then
    mkdir -p /etc/mysql/conf.d /etc/mysql/mysql.conf.d
    printf '!includedir /etc/mysql/conf.d/\n!includedir /etc/mysql/mysql.conf.d/\n' > /etc/mysql/my.cnf.fallback
fi
run "MySQL 8.4 server"  apt-get install -y -qq mysql-community-server
run "PHP ${PHP} + extensions" apt-get install -y -qq \
    php${PHP}-cli php${PHP}-fpm php${PHP}-mysql php${PHP}-redis php${PHP}-mbstring \
    php${PHP}-xml php${PHP}-curl php${PHP}-bcmath php${PHP}-gd php${PHP}-intl php${PHP}-zip php${PHP}-opcache php${PHP}-sqlite3
run "Redis"  apt-get install -y -qq redis-server
run "Nginx"  apt-get install -y -qq nginx
run "utilities"  apt-get install -y -qq unzip git rsync cron
[ "$WITH_RADIUS" = 1 ] && run "FreeRADIUS + SQL" apt-get install -y -qq freeradius freeradius-mysql freeradius-utils
[ "$DO_SSL" = 1 ]      && run "certbot"          apt-get install -y -qq certbot python3-certbot-nginx

run "enable services" systemctl enable --now mysql redis-server php${PHP}-fpm nginx
check "PHP"    "$(php -v 2>/dev/null | head -1 | grep -o "PHP ${PHP}[0-9.]*")" "PHP ${PHP}"
check "MySQL"  "$(mysql --version 2>/dev/null | grep -oE 'Ver 8\.[0-9.]+' | head -1)" "Ver 8.4"
check "Redis"  "$(systemctl is-active redis-server)" "active"
check "Nginx"  "$(systemctl is-active nginx)" "active"

# ─────────────────────────────  3. ionCube loader  ────────────────────
phase "ionCube Loader (runs the protected application code)"
ioncube() {
    # Idempotent: if the loader is already active, don't re-download (this is
    # what used to hang a re-run — the download has no business running twice).
    if php -v 2>/dev/null | grep -q 'ionCube PHP Loader'; then
        return 0
    fi
    local ext; ext="$(php -r 'echo ini_get("extension_dir");')"
    # Reuse an already-extracted loader if present; otherwise download with hard
    # timeouts + retries so a slow/blocked mirror fails fast instead of hanging.
    if [ ! -f "/tmp/ioncube/ioncube_loader_lin_${PHP}.so" ]; then
        curl -fsSL --connect-timeout 20 --max-time 180 --retry 3 --retry-delay 3 --retry-all-errors \
            https://downloads.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64.tar.gz -o /tmp/ic.tgz \
            || { echo "ionCube loader download failed or timed out (network/firewall)."; return 1; }
        tar -xzf /tmp/ic.tgz -C /tmp || { echo "ionCube archive extract failed."; return 1; }
    fi
    cp "/tmp/ioncube/ioncube_loader_lin_${PHP}.so" "${ext}/ioncube_loader.so"
    local d; for d in cli fpm; do
        echo 'zend_extension=ioncube_loader.so' > "/etc/php/${PHP}/${d}/conf.d/00-ioncube.ini"
    done
    rm -rf /tmp/ic.tgz /tmp/ioncube
    systemctl restart php${PHP}-fpm 2>/dev/null || true
}
run "install ionCube ${PHP}" ioncube
check "ionCube" "$(php -v 2>/dev/null | grep -o 'ionCube PHP Loader v[0-9.]*')" "ionCube PHP Loader"

# ─────────────────────────────  4. database  ──────────────────────────
phase "Database"
db_pass="$(cat /root/.h3r_db_pass 2>/dev/null || genpw 20)"
echo "$db_pass" > /root/.h3r_db_pass; chmod 600 /root/.h3r_db_pass
# Ensure we can authenticate as MySQL root with ${mysql_root}. On a fresh datadir
# the debconf password already applies. But if a previous install's /var/lib/mysql
# survived a package purge, the server keeps its OLD root password — so heal it:
# (a) already works → done; (b) reachable via socket/blank → set our password;
# (c) unknown password on a surviving datadir → reset it via an --init-file restart.
ensure_mysql_root() {
    mysql -uroot -p"${mysql_root}" -e 'SELECT 1' >/dev/null 2>&1 && return 0
    if mysql -uroot -e 'SELECT 1' >/dev/null 2>&1; then
        mysql -uroot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '${mysql_root}'; FLUSH PRIVILEGES;" >/dev/null 2>&1 \
            && mysql -uroot -p"${mysql_root}" -e 'SELECT 1' >/dev/null 2>&1 && return 0
    fi
    echo "MySQL root password mismatch (surviving data dir?) — resetting it…"
    local mysqld initf i
    mysqld="$(command -v mysqld || echo /usr/sbin/mysqld)"
    systemctl stop mysql 2>/dev/null || true
    initf="$(mktemp)"
    printf "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '%s';\nFLUSH PRIVILEGES;\n" "${mysql_root}" > "$initf"
    chmod 644 "$initf"
    "$mysqld" --user=mysql --init-file="$initf" --daemonize >/dev/null 2>&1
    i=0; until mysql -uroot -p"${mysql_root}" -e 'SELECT 1' >/dev/null 2>&1 || [ "$i" -ge 25 ]; do sleep 1; i=$((i+1)); done
    mysqladmin -uroot -p"${mysql_root}" shutdown >/dev/null 2>&1 || true
    rm -f "$initf"; sleep 2
    systemctl start mysql 2>/dev/null || true
    i=0; until systemctl is-active --quiet mysql || [ "$i" -ge 25 ]; do sleep 1; i=$((i+1)); done
    mysql -uroot -p"${mysql_root}" -e 'SELECT 1' >/dev/null 2>&1
}
db_setup() {
    ensure_mysql_root || { echo "Could not obtain MySQL root access — is /var/lib/mysql from another install? Wipe it and retry."; return 1; }
    mysql -uroot -p"${mysql_root}" <<SQL
CREATE DATABASE IF NOT EXISTS h3radius CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'h3radius'@'127.0.0.1' IDENTIFIED BY '${db_pass}';
CREATE USER IF NOT EXISTS 'h3radius'@'localhost' IDENTIFIED BY '${db_pass}';
ALTER USER 'h3radius'@'127.0.0.1' IDENTIFIED BY '${db_pass}';
ALTER USER 'h3radius'@'localhost' IDENTIFIED BY '${db_pass}';
GRANT ALL PRIVILEGES ON h3radius.* TO 'h3radius'@'127.0.0.1';
GRANT ALL PRIVILEGES ON h3radius.* TO 'h3radius'@'localhost';
SET GLOBAL log_bin_trust_function_creators = 1;
FLUSH PRIVILEGES;
SQL
    printf '[mysqld]\nlog_bin_trust_function_creators = 1\n' > /etc/mysql/mysql.conf.d/h3radius.cnf
}
run "create database + user" db_setup
check "app DB login" "$(mysql -uh3radius -p"${db_pass}" -h127.0.0.1 -N -e 'SELECT "ok"' 2>/dev/null)" "ok"

# ─────────────────────────────  5. application  ───────────────────────
phase "Application"
if [ -n "$LOCAL_BUNDLE" ]; then
    mkdir -p "$H3R_DIR"
    run "stage app (local)" rsync -a --delete "${LOCAL_BUNDLE}/" "${H3R_DIR}/"
else
    run "download app bundle" bash -c "curl -fsSL --connect-timeout 20 --max-time 600 --retry 3 --retry-delay 3 --retry-all-errors '${BUNDLE_URL}' -o /tmp/h3r-app.tgz && (curl -fsSL --connect-timeout 20 --max-time 60 '${BUNDLE_URL}.sha256' -o /tmp/h3r-app.sha256 || true)"
    if [ -s /tmp/h3r-app.sha256 ]; then
        run "verify checksum" bash -c "cd /tmp && awk '{print \$1\"  /tmp/h3r-app.tgz\"}' h3r-app.sha256 | sha256sum -c -"
    fi
    mkdir -p "$H3R_DIR"
    run "extract app" tar -xzf /tmp/h3r-app.tgz -C "$H3R_DIR" --strip-components=1
fi

BE="${H3R_DIR}/backend"
[ -f "${BE}/artisan" ] || die "app bundle missing backend/artisan"

# vendor: shipped in the bundle; install only if absent
if [ ! -d "${BE}/vendor" ]; then
    command -v composer >/dev/null || run "install Composer" bash -c \
        'curl -fsSL https://getcomposer.org/installer -o /tmp/ci.php && php /tmp/ci.php --quiet --install-dir=/usr/local/bin --filename=composer && rm -f /tmp/ci.php'
    run "composer install" bash -c "cd '${BE}' && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader --no-interaction"
fi

# Laravel needs these writable dirs to exist before any artisan command; the
# encoded bundle ships app code only, so (re)create them here.
run "prepare writable dirs" bash -c "mkdir -p '${BE}/bootstrap/cache' '${BE}/storage/framework/cache/data' '${BE}/storage/framework/sessions' '${BE}/storage/framework/views' '${BE}/storage/app/public' '${BE}/storage/logs'"

app_env() {
    cd "$BE"
    [ -f .env ] || cp .env.example .env
    sed -i \
        -e 's|^APP_ENV=.*|APP_ENV=production|' \
        -e 's|^APP_DEBUG=.*|APP_DEBUG=false|' \
        -e "s|^APP_URL=.*|APP_URL=http://${DOMAIN}|" \
        -e 's|^APP_NAME=.*|APP_NAME="ISP Boost"|' \
        -e "s|^DB_PASSWORD=.*|DB_PASSWORD=${db_pass}|" .env
    # Only generate an app key if one isn't set. Regenerating it on a re-run
    # would make all existing encrypted data (sessions, 2FA secrets, gateway
    # credentials) undecryptable — so preserve an existing key.
    if ! grep -q '^APP_KEY=base64' .env; then
        php artisan key:generate --force
    fi
}
run "configure environment" app_env
run "database migrations"   bash -c "cd '${BE}' && php artisan migrate --force"

# Persist the admin password so a retry/re-run prints the SAME (correct) one —
# and never force-resets a password the customer may have already changed
# (the seeder's firstOrCreate leaves an existing admin untouched).
admin_pass="$(cat /root/.h3r_admin_pass 2>/dev/null || genpw 14)"
echo "$admin_pass" > /root/.h3r_admin_pass; chmod 600 /root/.h3r_admin_pass
run "seed + create admin" bash -c "cd '${BE}' && ADMIN_PASSWORD='${admin_pass}' ADMIN_NAME='System Administrator' ADMIN_EMAIL='${ADMIN_EMAIL}' php artisan db:seed --force"
run "point at license server" bash -c "cd '${BE}' && php artisan tinker --execute='try{app(\App\Services\SettingsService::class)->set(\"license_server_url\",\"${LICENSE_SERVER}\");}catch(\Throwable \$e){}'"
run "permissions" bash -c "chown -R www-data:www-data '${BE}/storage' '${BE}/bootstrap/cache' '${H3R_DIR}/frontend'"
run "cache config + routes" bash -c "cd '${BE}' && php artisan config:cache && php artisan route:cache"
check "app boots" "$(cd "$BE" && php artisan --version 2>/dev/null | grep -o 'Laravel Framework [0-9.]*')" "Laravel Framework"

# ─────────────────────────────  6. web server  ────────────────────────
phase "Web server"
web_vhost() {
    cat > /etc/nginx/sites-available/h3radius <<NGINX
server {
    listen 80 default_server;
    server_name ${DOMAIN} _;
    root ${H3R_DIR}/frontend/browser;
    index index.html;
    client_max_body_size 2G;

    # Defense-in-depth: never serve dotfiles (.env, .git, …) — 404, not the SPA
    # fallback. Keep .well-known reachable for Let's Encrypt ACME challenges.
    location ~ /\.(?!well-known) { return 404; access_log off; }

    location ^~ /storage/ { root ${BE}/public; try_files \$uri =404; access_log off; expires 30d; }
    location /api/       { try_files /dev/null @laravel; }
    location /sanctum/   { try_files /dev/null @laravel; }
    location ~ ^/(pay|webhooks)/ { try_files \$uri @laravel; }

    location @laravel {
        fastcgi_pass unix:/run/php/php${PHP}-fpm.sock;
        fastcgi_param SCRIPT_FILENAME ${BE}/public/index.php;
        fastcgi_param DOCUMENT_ROOT ${BE}/public;
        include fastcgi_params;
    }
    location / { try_files \$uri \$uri/ /index.html; }
}
NGINX
    ln -sf /etc/nginx/sites-available/h3radius /etc/nginx/sites-enabled/h3radius
    rm -f /etc/nginx/sites-enabled/default
    nginx -t && systemctl reload nginx
}
run "configure nginx vhost" web_vhost
run "reload PHP-FPM (warm caches)" systemctl restart php${PHP}-fpm
check_http "homepage" "http://127.0.0.1/" "200"
check_http "API"      "http://127.0.0.1/api/branding/public" "200"
if [ "$DO_SSL" = 1 ] && printf '%s' "$DOMAIN" | grep -q '\.'; then
    run "Let's Encrypt certificate" certbot --nginx -n --agree-tos -m "$ADMIN_EMAIL" -d "$DOMAIN" --redirect
fi

# ─────────────────────────────  7. FreeRADIUS  ────────────────────────
if [ "$WITH_RADIUS" = 1 ]; then
    phase "FreeRADIUS (AAA)"
    # Config dir differs by version: NetworkRADIUS 3.2 = /etc/freeradius (flat),
    # Ubuntu 3.0 = /etc/freeradius/3.0. Detect by where radiusd.conf lives.
    RAD=/etc/freeradius/3.0
    for d in /etc/freeradius /etc/freeradius/3.0; do [ -f "$d/radiusd.conf" ] && { RAD="$d"; break; }; done
    radius_setup() {
        # SQL module against our MySQL (stock schema: radcheck/radacct/nas_clients…)
        if [ -f "${H3R_DIR}/freeradius/mods-enabled/sql" ]; then
            sed -e 's/server = "mysql"/server = "127.0.0.1"/' \
                -e "s/password = \"h3radius\"/password = \"${db_pass}\"/" \
                "${H3R_DIR}/freeradius/mods-enabled/sql" > "${RAD}/mods-available/sql"
        fi
        ln -sf "${RAD}/mods-available/sql" "${RAD}/mods-enabled/sql"
        [ -f "${H3R_DIR}/freeradius/sites-enabled/default" ] && \
            cp "${H3R_DIR}/freeradius/sites-enabled/default" "${RAD}/sites-available/h3radius" && \
            ln -sf "${RAD}/sites-available/h3radius" "${RAD}/sites-enabled/default"
        grep -q 'client localhost' "${RAD}/clients.conf" || printf '\nclient localhost {\n ipaddr = 127.0.0.1\n secret = %s\n nas_type = other\n}\n' "$(genpw 16)" >> "${RAD}/clients.conf"
        chown -R freerad:freerad "$RAD"; chmod 640 "${RAD}/mods-available/sql"
    }
    run "wire SQL module + site" radius_setup
    run "config check"           freeradius -CX
    run "start FreeRADIUS"        systemctl restart freeradius
    run "enable at boot"          systemctl enable freeradius
    check "FreeRADIUS $(freeradius -v 2>&1 | grep -oE '3\.[0-9]+\.[0-9]+' | head -1)" "$(systemctl is-active freeradius)" "active"
fi

# ─────────────────────────────  8. background workers  ────────────────
phase "Background workers"
workers() {
    cat > /etc/systemd/system/h3radius-queue.service <<U
[Unit]
Description=ISP Boost queue worker
After=network.target mysql.service redis-server.service
[Service]
User=www-data
WorkingDirectory=${BE}
ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=1 --timeout=3600 --max-time=3600
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
U
    cat > /etc/systemd/system/h3radius-scheduler.service <<U
[Unit]
Description=ISP Boost task scheduler
After=network.target mysql.service redis-server.service
[Service]
User=www-data
WorkingDirectory=${BE}
ExecStart=/usr/bin/php artisan schedule:work
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
U
    # Remote auto-update: a daily oneshot that asks the license server for a newer
    # build and, if published, safely self-installs it (scripts/self-update.sh →
    # installer/upgrade.sh: backup, staged swap, health-gate, auto-rollback).
    chmod +x "${H3R_DIR}/scripts/self-update.sh" "${H3R_DIR}/installer/upgrade.sh" 2>/dev/null || true
    # Let the panel (www-data) trigger the updater on demand — the "Update now"
    # button. Scoped to exactly this one systemctl invocation, nothing else.
    cat > /etc/sudoers.d/h3radius-autoupdate <<'SUDO'
www-data ALL=(root) NOPASSWD: /usr/bin/systemctl --no-block start h3radius-autoupdate.service
SUDO
    chmod 440 /etc/sudoers.d/h3radius-autoupdate
    visudo -cf /etc/sudoers.d/h3radius-autoupdate >/dev/null 2>&1 || rm -f /etc/sudoers.d/h3radius-autoupdate
    cat > /etc/systemd/system/h3radius-autoupdate.service <<U
[Unit]
Description=ISP Boost remote auto-update
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=${H3R_DIR}/scripts/self-update.sh
U
    cat > /etc/systemd/system/h3radius-autoupdate.timer <<U
[Unit]
Description=ISP Boost daily auto-update check
[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
U
    systemctl daemon-reload
    systemctl enable --now h3radius-queue h3radius-scheduler
    systemctl enable --now h3radius-autoupdate.timer
}
run "install queue + scheduler" workers
check "queue worker" "$(systemctl is-active h3radius-queue)" "active"
check "scheduler"    "$(systemctl is-active h3radius-scheduler)" "active"
check "auto-update"  "$(systemctl is-active h3radius-autoupdate.timer)" "active"

# ─────────────────────────────  9. license & first-run  ───────────────
phase "License & first-run setup"
if [ -n "$LICENSE_KEY" ]; then
    # Non-interactive: activate the key now and skip the browser wizard, so the
    # customer can log straight in with the admin credentials below.
    run "activate license"    bash -c "cd '${BE}' && php artisan license:activate '${LICENSE_KEY}'"
    run "mark setup complete" bash -c "cd '${BE}' && php artisan tinker --execute='app(\App\Services\SettingsService::class)->set(\"setup_completed\", true, \"general\", \"boolean\");'"
    info "$(cd "$BE" && php artisan license:status 2>/dev/null | grep -E 'state|plan' | tr '\n' ' ')"
    SETUP=done
else
    # No key given: leave the polished first-run wizard for the customer, where
    # they set their company, theme and license (key or Free plan) in-browser.
    info "first-run setup will be completed in the browser (company · theme · license)"
    SETUP=wizard
fi

# ─────────────────────────────  done  ─────────────────────────────────
printf '%s' "$cG"
cat <<'DONE'

   ✓ ISP Boost is installed and running.

DONE
printf '%s' "$c0"
# Show both LAN (local) and WAN (public) addresses — like aaPanel — so the panel
# is reachable whether the operator is on the same network or over the internet.
LOCAL_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
PUBLIC_IP="$(curl -fsS --max-time 8 https://api.ipify.org 2>/dev/null || true)"
show_urls() {
    if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "$LOCAL_IP" ] && [ "$DOMAIN" != "$PUBLIC_IP" ]; then
        printf '     %sURL    :%s  %shttp://%s%s\n' "$cW" "$c0" "$cY" "$DOMAIN" "$c0"
    fi
    [ -n "$LOCAL_IP" ]  && printf '     %sLocal  :%s  %shttp://%s%s\n' "$cW" "$c0" "$cY" "$LOCAL_IP" "$c0"
    [ -n "$PUBLIC_IP" ] && printf '     %sPublic :%s  %shttp://%s%s\n' "$cW" "$c0" "$cY" "$PUBLIC_IP" "$c0"
}
if [ "$SETUP" = wizard ]; then
    printf '   %sFinish setup in your browser — open any address below:%s\n' "$cW" "$c0"
    show_urls
    printf '   %sYou will set your company, theme and license there.%s\n\n' "$cD" "$c0"
else
    printf '   %sPanel access — open any address below:%s\n' "$cW" "$c0"
    show_urls
    printf '   %sLogin :%s admin  /  %s%s%s\n' "$cW" "$c0" "$cY" "$admin_pass" "$c0"
    printf '   %sEmail :%s %s\n' "$cW" "$c0" "$ADMIN_EMAIL"
    printf '   %s(you will be asked to change the password on first login)%s\n\n' "$cD" "$c0"
fi
{ echo "panel=http://${DOMAIN}"; echo "local_url=http://${LOCAL_IP}"; [ -n "$PUBLIC_IP" ] && echo "public_url=http://${PUBLIC_IP}"; echo "admin_user=admin"; echo "admin_pass=${admin_pass}"; echo "db_pass=${db_pass}"; echo "mysql_root=${mysql_root}"; } > /root/h3radius-credentials.txt
chmod 600 /root/h3radius-credentials.txt
info "credentials saved to /root/h3radius-credentials.txt"
