#!/usr/bin/env bash
#
# vantapanel installer — fresh Ubuntu/Debian server.
# Run as root from inside the extracted bundle:  sudo bash bin/install.sh
#
set -euo pipefail

# ---- settings (override with env vars) -------------------------------------
# DOMAIN is OPTIONAL. If unset, the panel is reached by this server's public IP
# on a self-signed certificate — exactly like WHM on a fresh VPS. Add a domain
# and a free Let's Encrypt cert any time afterwards.
DOMAIN="${DOMAIN:-}"
APP_DIR="/opt/vantapanel"
WORKER="/usr/local/sbin/vantapanel-worker.php"
PANEL_USER="paneluser"
CERT_DIR="/etc/ssl/vantapanel"

say(){ printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
die(){ printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }

apt_lock_wait(){
  local n=0
  while fuser /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock >/dev/null 2>&1; do
    [ "$n" -eq 0 ] && say "Waiting for the system package manager to finish (background updates)..."
    sleep 3; n=$((n+3)); [ "$n" -ge 600 ] && die "package manager still busy after 10 min; run 'ps aux | grep apt', let it finish, then re-run."
  done
}

# Generate an 18-char password with NO lookalike characters (no l I 1 O 0),
# so the printed credentials can't be mistyped. Reads a fixed block of random
# bytes (finite — no SIGPIPE/pipefail concerns), keeps only the safe alphabet,
# and takes the first 18.
gen_pass(){
  local s
  s="$(LC_ALL=C head -c 512 /dev/urandom | tr -dc 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789')"
  printf '%s' "${s:0:18}"
}

# Strip the optional /vwhm HTTP Basic Auth "browser gate" from EVERY Apache
# vhost — including certbot-generated -le-ssl copies the installer never wrote —
# and drop its htpasswd. The panel has its own login (+ optional 2FA), so this
# extra prompt only confuses first-time admins. Runs by default on every
# install/upgrade; enable the gate deliberately with VANTAPANEL_WHM_GATE=1.
remove_whm_gate(){
  local changed=0 f
  command -v perl >/dev/null 2>&1 || return 0
  for f in /etc/apache2/sites-available/*.conf /etc/apache2/sites-enabled/*.conf; do
    [ -f "$f" ] || continue
    [ -L "$f" ] && continue   # skip symlinks (sites-enabled -> sites-available)
    grep -q '\.vantapanel-admin' "$f" 2>/dev/null || continue
    perl -0777 -i -pe 's{[^\S\n]*<Location[^>]*vwhm[^>]*>(?:(?!</Location>)[\s\S])*?\.vantapanel-admin(?:(?!</Location>)[\s\S])*?</Location>[^\S\n]*\n?}{}gi' "$f"
    changed=1
  done
  rm -f /etc/apache2/.vantapanel-admin 2>/dev/null || true
  [ "$changed" = 1 ] && say "Removed the legacy /vwhm browser gate (the panel's own login is enough)."
  return 0
}

# Authoritative DNS (PowerDNS, MySQL-backed) so operators can run true private
# nameservers (ns1/ns2.their-domain) that answer from this server. Installed
# but idle by default; the operator turns on per-account zone creation from the
# panel's Server Setup page. Idempotent; safe to re-run on upgrade.
setup_local_dns(){
  say "Setting up authoritative DNS (PowerDNS)"
  export DEBIAN_FRONTEND=noninteractive
  # Install; its postinst may fail to start (bind backend vs resolved) - that's
  # fine, we reconfigure and restart below. Never let it abort the installer.
  apt-get -o DPkg::Lock::Timeout=300 install -y pdns-server pdns-backend-mysql dnsutils >/dev/null 2>&1 || true
  systemctl stop pdns 2>/dev/null || true
  command -v pdnsutil >/dev/null 2>&1 || { say "  PowerDNS unavailable - skipping (DNS can be added later)"; return 0; }

  local PDNS_PASS=""
  [ -f /etc/powerdns/pdns.d/pdns.local.gmysql.conf ] \
    && PDNS_PASS="$(sed -n 's/^gmysql-password=//p' /etc/powerdns/pdns.d/pdns.local.gmysql.conf | head -1)"
  [ -z "$PDNS_PASS" ] && PDNS_PASS="$(openssl rand -hex 16)"
  mysql <<SQL || true
CREATE DATABASE IF NOT EXISTS powerdns CHARACTER SET utf8mb4;
CREATE USER IF NOT EXISTS 'pdns'@'127.0.0.1' IDENTIFIED BY '${PDNS_PASS}';
ALTER USER 'pdns'@'127.0.0.1' IDENTIFIED BY '${PDNS_PASS}';
GRANT ALL ON powerdns.* TO 'pdns'@'127.0.0.1';
FLUSH PRIVILEGES;
SQL
  if [ "$(mysql -N powerdns -e 'SHOW TABLES' 2>/dev/null | wc -l)" -eq 0 ]; then
    local SCHEMA
    SCHEMA="$(ls /usr/share/pdns-backend-mysql/schema/schema.mysql.sql \
                 /usr/share/doc/pdns-backend-mysql/schema.mysql.sql 2>/dev/null | head -1)"
    [ -n "$SCHEMA" ] && { mysql powerdns < "$SCHEMA" || true; }   # never abort the installer
  fi
  # gmysql backend (must be group-readable by 'pdns' - the service runs as pdns)
  cat > /etc/powerdns/pdns.d/pdns.local.gmysql.conf <<CONF
# Vanta Panel - authoritative DNS backend (MySQL)
launch=gmysql
gmysql-host=127.0.0.1
gmysql-dbname=powerdns
gmysql-user=pdns
gmysql-password=${PDNS_PASS}
CONF
  chown root:pdns /etc/powerdns/pdns.d/pdns.local.gmysql.conf 2>/dev/null || true
  chmod 640 /etc/powerdns/pdns.d/pdns.local.gmysql.conf
  # Bind the public IP + loopback (systemd-resolved keeps 127.0.0.53); serve new
  # zones immediately (no stale zone cache).
  # PowerDNS must bind an address that exists on a local interface. On NAT'd /
  # port-forwarded VPSes the public IP (SERVER_IP) is NOT on any interface, so
  # bind the primary interface IP instead; the public IP is only used for the
  # panel URL and DNS records, never for binding.
  local BIND_IP
  BIND_IP="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')"
  [ -z "$BIND_IP" ] && BIND_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
  [ -z "$BIND_IP" ] && BIND_IP="$SERVER_IP"
  local LOCAL_ADDR="127.0.0.1"
  [ -n "$BIND_IP" ] && LOCAL_ADDR="${BIND_IP},127.0.0.1"
  [ -n "$BIND_IP" ] || say "  WARNING: no local IP detected - PowerDNS will listen on 127.0.0.1 only until re-run"
  cat > /etc/powerdns/pdns.d/vanta-listen.conf <<CONF
local-address=${LOCAL_ADDR}
zone-cache-refresh-interval=0
CONF
  [ -f /etc/powerdns/pdns.d/bind.conf ] && mv /etc/powerdns/pdns.d/bind.conf /etc/powerdns/pdns.d/bind.conf.disabled
  systemctl enable pdns >/dev/null 2>&1 || true
  systemctl restart pdns 2>/dev/null || true
  if systemctl is-active --quiet pdns; then
    say "  PowerDNS active on ${SERVER_IP}:53 (enable per-account zones in the panel's Server Setup)"
  else
    say "  WARNING: PowerDNS did not start - check 'journalctl -u pdns'. Install continues."
  fi
}

[ "$(id -u)" -eq 0 ] || die "run as root (sudo bash bin/install.sh)"

# locate the bundle root (parent of this script's dir)
SRC="$(cd "$(dirname "$0")/.." && pwd)"
[ -f "$SRC/bin/panel-worker.php" ] || die "run from the extracted vantapanel bundle"

# ---- pre-flight checks: fail fast with clear guidance ----------------------
# Catch the common "install succeeds but the panel is broken" cases before we
# touch the system: wrong OS, PHP too old, 32-bit, no disk, busy web ports.
preflight(){
  command -v apt-get >/dev/null 2>&1 \
    || die "Vanta Panel requires Debian or Ubuntu (apt-get was not found on this system)."

  if [ -r /etc/os-release ]; then
    . /etc/os-release
    case "${ID:-}" in
      ubuntu)
        { [ "${VERSION_ID%%.*}" -ge 22 ] 2>/dev/null; } \
          || die "Ubuntu ${VERSION_ID:-?} ships PHP 7.x, but Vanta Panel needs PHP 8.1+. Please reinstall on Ubuntu 22.04 LTS or newer." ;;
      debian)
        { [ "${VERSION_ID%%.*}" -ge 12 ] 2>/dev/null; } \
          || die "Debian ${VERSION_ID:-?} ships PHP 7.x, but Vanta Panel needs PHP 8.1+. Please reinstall on Debian 12 (Bookworm) or newer." ;;
      *)
        case "${ID_LIKE:-}" in
          *debian*|*ubuntu*) say "WARNING: '${PRETTY_NAME:-${ID:-unknown}}' is Debian-like but untested — continuing. PHP must be 8.1+." ;;
          *) die "Unsupported OS '${PRETTY_NAME:-${ID:-unknown}}'. Vanta Panel supports Ubuntu 22.04+ and Debian 12+." ;;
        esac ;;
    esac
  fi

  case "$(uname -m)" in
    x86_64|amd64|aarch64|arm64) : ;;
    *) die "Unsupported CPU architecture '$(uname -m)'. A 64-bit system is required." ;;
  esac

  local avail_kb; avail_kb="$(df -Pk / 2>/dev/null | awk 'NR==2{print $4}')"
  if [ -n "${avail_kb:-}" ] && [ "$avail_kb" -lt 3000000 ]; then
    die "Low disk space: only $(( avail_kb / 1024 )) MB free on /. At least ~3 GB is required."
  fi

  if command -v ss >/dev/null 2>&1 && ! systemctl is-active --quiet apache2 2>/dev/null; then
    local p
    for p in 80 443; do
      if ss -ltnH 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]${p}\$"; then
        say "WARNING: port ${p} is already in use. Vanta Panel serves the panel through Apache on 80/443 — if nginx or another web server is running, stop/remove it first or the panel will be unreachable."
      fi
    done
  fi

  # Network + DNS reachability: the installer must download OS packages. A box
  # with broken DNS or no outbound internet otherwise fails deep inside apt with
  # a confusing wall of errors. Catch it here with one clear, actionable message.
  net_ok=""
  # Try the configured apt mirror first (what apt will actually use), then a
  # couple of well-known hosts, so we don't false-fail on a single slow mirror.
  for host in \
      "$(. /etc/os-release 2>/dev/null; echo "${ID:-ubuntu}").archive.ubuntu.com" \
      archive.ubuntu.com security.ubuntu.com deb.debian.org cloudflare.com; do
    if getent hosts "$host" >/dev/null 2>&1; then net_ok="dns"; break; fi
  done
  if [ -z "$net_ok" ]; then
    # DNS failed for every host. Distinguish "no DNS" from "no internet at all".
    if ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 || ping -c1 -W2 8.8.8.8 >/dev/null 2>&1; then
      die "This server can reach the internet but cannot resolve DNS, so it can't download the packages Vanta Panel needs.
       This is a DNS problem on THIS server (not Vanta Panel). Fix it, then re-run the installer:
         echo 'nameserver 1.1.1.1' | sudo tee /etc/resolv.conf
         echo 'nameserver 8.8.8.8' | sudo tee -a /etc/resolv.conf
       Then:  curl -fsSL https://get.vantapanel.com | sudo bash"
    else
      die "This server has no outbound internet access, so it can't download the packages Vanta Panel needs.
       This is a network problem on THIS server (not Vanta Panel). Check your firewall / default route /
       provider settings, confirm 'ping 1.1.1.1' works, then re-run:  curl -fsSL https://get.vantapanel.com | sudo bash"
    fi
  fi
}
preflight

# ---- access host: domain if given, otherwise this server's public IP -------
detect_ip(){
  local ip=""
  ip="$(curl -fsS --max-time 6 https://api.ipify.org 2>/dev/null || true)"
  [ -z "$ip" ] && ip="$(curl -fsS --max-time 6 https://ifconfig.me 2>/dev/null || true)"
  [ -z "$ip" ] && ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
  printf '%s' "$ip"
}
SERVER_IP="$(detect_ip)"
if [ -n "$DOMAIN" ]; then
  ACCESS_HOST="$DOMAIN";  ACCESS_IS_IP=0
else
  ACCESS_HOST="$SERVER_IP";  ACCESS_IS_IP=1
fi
[ -n "$ACCESS_HOST" ] || die "could not determine the server's public IP — set DOMAIN= or check networking"

# Postfix/Dovecot require myhostname to be a real hostname (with dots), never a
# bare IP. Derive a valid mail hostname: prefer an explicit DOMAIN, else the
# system FQDN if it has a dot, else a safe synthesized fallback.
if [ -n "$DOMAIN" ]; then
  MAIL_HOSTNAME="mail.${DOMAIN}"
else
  _fqdn="$(hostname -f 2>/dev/null || true)"
  case "$_fqdn" in
    *.*[!0-9.]*|*[!0-9.]*.*) MAIL_HOSTNAME="$_fqdn" ;;   # has a non-numeric dotted form
    *)                       MAIL_HOSTNAME="" ;;
  esac
  # reject bare IPs / empty / single-label names
  case "$MAIL_HOSTNAME" in
    ''|*' '*) MAIL_HOSTNAME="vantapanel.localdomain" ;;
  esac
  # final guard: if it's all digits and dots (an IP), replace it
  if printf '%s' "$MAIL_HOSTNAME" | grep -Eq '^[0-9.]+$'; then
    MAIL_HOSTNAME="vantapanel.localdomain"
  fi
fi

# ---- packages --------------------------------------------------------------
say "Installing packages"
export DEBIAN_FRONTEND=noninteractive
apt_lock_wait; apt-get -o DPkg::Lock::Timeout=300 update -y
apt-get -o DPkg::Lock::Timeout=300 install -y apache2 mariadb-server \
                   php-cli php-mysql php-mbstring php-curl php-xml php-zip php-gd php-intl php-bcmath \
                   certbot python3-certbot-apache python3-certbot-dns-cloudflare \
                   libapache2-mod-php apache2-utils sudo openssl curl pigz acl python3-venv \
                   debconf-utils dbconfig-common vsftpd unattended-upgrades

# Hard gate: the panel uses PHP 8.1+ features (enums, match, str_contains). If the
# distro's stock PHP is older, stop now with a clear message rather than later.
PHP_VER_ID="$(php -r 'echo PHP_VERSION_ID;' 2>/dev/null || echo 0)"
if [ "${PHP_VER_ID:-0}" -lt 80100 ]; then
  die "PHP $(php -r 'echo PHP_VERSION;' 2>/dev/null || echo unknown) is too old — Vanta Panel needs PHP 8.1 or newer. Use Ubuntu 22.04+/Debian 12+, or install PHP 8.1+ (e.g. the ondrej/php PPA) and re-run."
fi

# sodium is compiled INTO PHP 8.x — it is NOT a separate apt package. (Installing
# the unversioned "php-sodium" pulls the old PECL extension and drags in an
# ancient PHP.) The panel's signed-update + license checks require it, so verify.
if ! php -m 2>/dev/null | grep -qi '^sodium$'; then
  die "PHP's sodium extension is missing — Vanta Panel needs it for signed updates and licensing. It ships built into PHP 8.x; reinstall php-cli and re-run."
fi

# phpMyAdmin — install non-interactively. We preseed to skip dbconfig prompts
# (Vanta Panel manages DB access itself). The preseed is best-effort: if the
# debconf question isn't registered yet it must not abort the whole install,
# hence the `|| true`. Selecting noninteractive frontend also auto-defaults.
echo "phpmyadmin phpmyadmin/dbconfig-install boolean false" | debconf-set-selections 2>/dev/null || true
echo "phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2" | debconf-set-selections 2>/dev/null || true
apt-get -o DPkg::Lock::Timeout=300 install -y phpmyadmin || say "phpMyAdmin install skipped/failed (non-fatal) — the panel's built-in Database Manager still works"

# ---- automatic security updates ------------------------------------------
# Keep the customer's box patched without manual intervention.
say "Enabling automatic security updates"
echo 'unattended-upgrades unattended-upgrades/enable_auto_updates boolean true' | debconf-set-selections 2>/dev/null || true
dpkg-reconfigure -f noninteractive unattended-upgrades 2>/dev/null || true
systemctl enable --now unattended-upgrades 2>/dev/null || true
# Pin safe behaviour explicitly: security-only, daily, and NEVER an automatic
# reboot (the admin reboots on their own schedule — customer sites are never
# interrupted unexpectedly).
cat > /etc/apt/apt.conf.d/52vantapanel-unattended <<'UUEOF'
// Vanta Panel — safe auto-patching (security updates only, no automatic reboot).
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";

Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::SyslogEnable "true";
UUEOF

say "Enabling Apache modules"
a2enmod proxy proxy_http headers rewrite ssl >/dev/null

# ---- panel user ------------------------------------------------------------
if ! id "$PANEL_USER" >/dev/null 2>&1; then
  say "Creating system user '$PANEL_USER'"
  # A leftover group (e.g. from a previous partial uninstall) would make a bare
  # useradd fail. Ensure the group exists, then create the user bound to it.
  groupadd --system "$PANEL_USER" 2>/dev/null || true
  useradd --system --gid "$PANEL_USER" --home /var/lib/vantapanel \
          --shell /usr/sbin/nologin "$PANEL_USER"
fi

# ---- copy app --------------------------------------------------------------
say "Installing app to $APP_DIR"
mkdir -p "$APP_DIR"
# preserve an existing DB password (from a prior install) before we overwrite config.php
PRESERVE_DB_PASS=""
if [ -f "$APP_DIR/lib/config.php" ]; then
    PRESERVE_DB_PASS="$(php -r '$c=@include $argv[1]; $p=$c["db"]["pass"]??""; echo ($p!=="" && $p!=="__DB_PASS__")?$p:"";' "$APP_DIR/lib/config.php" 2>/dev/null || true)"
fi
cp -a "$SRC/." "$APP_DIR/"
# Keep bin/install.sh in $APP_DIR. publish-panel.sh packages the live $APP_DIR,
# so removing the installer here would produce release packages with no installer
# (a fresh-install bootstrap looks for bin/install.sh and fails without it).
# It is stored 0640 root:paneluser and never runs on its own — harmless to keep.

# ---- database --------------------------------------------------------------
say "Configuring state database"

# Idempotency: reuse the password preserved before the app copy (a prior
# install), so re-running the installer never desyncs MySQL from config.
DB_PASS="$PRESERVE_DB_PASS"
if [ -z "$DB_PASS" ]; then
    DB_PASS="$(openssl rand -base64 24 | tr -d '/+=' | head -c 24)"
fi

# CREATE-then-ALTER forces the MySQL user's password to match $DB_PASS on every
# run (new or repeat), so the panel and the DB can never disagree.
mysql <<SQL
CREATE DATABASE IF NOT EXISTS vantapanel CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS 'vantapanel'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
CREATE USER IF NOT EXISTS 'vantapanel'@'localhost' IDENTIFIED BY '${DB_PASS}';
ALTER USER 'vantapanel'@'127.0.0.1' IDENTIFIED BY '${DB_PASS}';
ALTER USER 'vantapanel'@'localhost' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON vantapanel.* TO 'vantapanel'@'127.0.0.1';
GRANT ALL PRIVILEGES ON vantapanel.* TO 'vantapanel'@'localhost';
FLUSH PRIVILEGES;
SQL

mysql vantapanel < "$APP_DIR/sql/schema.sql"

# write the password into config.php (only the placeholder is replaced; if the
# password was reused above, the freshly-copied config still has the placeholder)
php -r '
  $f = $argv[1]; $p = $argv[2];
  $c = file_get_contents($f);
  $c = str_replace("__DB_PASS__", addslashes($p), $c);
  file_put_contents($f, $c);
' "$APP_DIR/lib/config.php" "$DB_PASS"

# brand the install to the access host (domain or IP) — never leave vantapanel.com
php -r '
  $f=$argv[1]; $h=$argv[2];
  $c=file_get_contents($f);
  $c=preg_replace("/(\x27domain\x27\s*=>\s*\x27)[^\x27]*(\x27)/", "\${1}".$h."\${2}", $c, 1);
  file_put_contents($f,$c);
' "$APP_DIR/lib/config.php" "$ACCESS_HOST"

# sanity check the DB connection as it will be used by the panels
php -r '$c=require $argv[1];$d=$c["db"];new PDO("mysql:host={$d["host"]};dbname={$d["name"]}",$d["user"],$d["pass"]);echo "  db connection ok\n";' "$APP_DIR/lib/config.php" \
  || die "panel could not connect to the state DB (check config.php)"

# ---- worker (the only root bridge) ----------------------------------------
say "Installing privileged worker -> $WORKER"
install -o root -g root -m 0700 "$APP_DIR/bin/panel-worker.php" "$WORKER"

say "Installing sudo rule"
install -o root -g root -m 0440 "$APP_DIR/panel.sudoers" /etc/sudoers.d/vantapanel
visudo -cf /etc/sudoers.d/vantapanel >/dev/null || die "sudoers rule failed validation"

# ---- permissions -----------------------------------------------------------
say "Locking down permissions"
mkdir -p /var/lib/vantapanel/sessions
chown -R "$PANEL_USER:$PANEL_USER" /var/lib/vantapanel
chmod 0700 /var/lib/vantapanel/sessions
chown -R root:"$PANEL_USER" "$APP_DIR"
find "$APP_DIR" -type d -exec chmod 0750 {} \;
find "$APP_DIR" -type f -exec chmod 0640 {} \;
chmod 0640 "$APP_DIR/lib/config.php"   # contains DB password; group-readable only

# ---- phpMyAdmin (standard cookie auth) ------------------------------------
# The user panel provisions a per-account scoped MySQL user (<account>_qik)
# and shows the credentials on the phpMyAdmin page; users sign in normally.
# This is environment-independent (no signon session/cookie/proxy fragility).
# We keep phpMyAdmin's stock cookie auth and just ensure a clean default.
if [ -d /usr/share/phpmyadmin ] || [ -f /etc/phpmyadmin/config.inc.php ]; then
  say "Configuring phpMyAdmin (standard login)"

  # Remove any signon config from earlier Vanta Panel versions (idempotent).
  rm -f /etc/phpmyadmin/conf.d/vantapanel-signon.php 2>/dev/null || true

  # Ensure a server entry exists with cookie auth even when dbconfig was
  # skipped during install (preseeded dbconfig-install=false leaves $dbname
  # empty, so the stock config defines no server). This adds a clean one.
  mkdir -p /etc/phpmyadmin/conf.d
  cat > /etc/phpmyadmin/conf.d/vantapanel.php <<'PMACONF'
<?php
/* Vanta Panel — phpMyAdmin server (standard cookie auth).
 * Accounts log in with their scoped <account>_qik MySQL user; the panel
 * shows the credentials on the phpMyAdmin page. */
$i = 1;
$cfg['Servers'][$i]['auth_type']       = 'cookie';
$cfg['Servers'][$i]['host']            = '127.0.0.1';
$cfg['Servers'][$i]['port']            = 3306;
$cfg['Servers'][$i]['connect_type']    = 'tcp';
$cfg['Servers'][$i]['compress']        = false;
$cfg['Servers'][$i]['AllowNoPassword'] = false;
$cfg['ServerDefault'] = 1;
PMACONF
  chmod 0644 /etc/phpmyadmin/conf.d/vantapanel.php

  # Ensure phpMyAdmin's Apache config exists and is enabled. If phpMyAdmin was
  # ever purged on this box, apt purge removes conf-available/phpmyadmin.conf
  # permanently and a later reinstall does NOT restore it — leaving /phpmyadmin
  # returning 404 (no Directory grant). Recreate it if missing.
  if [ ! -f /etc/apache2/conf-available/phpmyadmin.conf ]; then
    cat > /etc/apache2/conf-available/phpmyadmin.conf <<'PMAAPACHE'
Alias /phpmyadmin /usr/share/phpmyadmin

<Directory /usr/share/phpmyadmin>
    Options SymLinksIfOwnerMatch
    DirectoryIndex index.php
    Require all granted
    <IfModule mod_php.c>
        php_admin_value upload_tmp_dir /var/lib/phpmyadmin/tmp
        php_admin_value open_basedir "/usr/share/phpmyadmin/:/etc/phpmyadmin/:/var/lib/phpmyadmin/:/usr/share/php/:/usr/share/javascript/"
    </IfModule>
</Directory>

<Directory /usr/share/phpmyadmin/setup>
    Require all denied
</Directory>
<Directory /usr/share/phpmyadmin/templates>
    Require all denied
</Directory>
<Directory /usr/share/phpmyadmin/libraries>
    Require all denied
</Directory>
PMAAPACHE
  fi
  a2enconf phpmyadmin >/dev/null 2>&1 || true
fi

# ---- vsftpd (FTP accounts) -------------------------------------------------
# FTP users are real system users (in each account's group) with nologin shell,
# jailed to their assigned directory. vsftpd handles auth via PAM (system users).
if command -v vsftpd >/dev/null 2>&1; then
  say "Configuring vsftpd (FTP accounts)"
  # back up the stock config once
  [ -f /etc/vsftpd.conf.vantapanel-orig ] || cp -n /etc/vsftpd.conf /etc/vsftpd.conf.vantapanel-orig 2>/dev/null || true
  cat > /etc/vsftpd.conf <<'VSFTPD'
# Vanta Panel vsftpd configuration
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
chroot_local_user=YES
allow_writeable_chroot=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
# FTPS (explicit FTP over TLS). Connections are encrypted; clients accept the
# self-signed cert. SFTP is intentionally NOT used (FTP users have nologin shell).
ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=NO
force_local_logins_ssl=NO
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
# passive mode port range (open these in the firewall if used)
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
# only allow FTP users that Vanta Panel creates (and account users); deny others
userlist_enable=NO
VSFTPD
  # advertise the server's public IP for passive-mode data connections (needed
  # behind NAT / when clients are remote). Falls back gracefully if unset.
  if [ -n "${SERVER_IP}" ]; then
    echo "pasv_address=${SERVER_IP}" >> /etc/vsftpd.conf
  fi
  # CRITICAL: Vanta Panel FTP users have a nologin shell (so they can't SSH).
  # vsftpd's PAM stack includes pam_shells, which rejects any user whose shell
  # is not listed in /etc/shells -> "530 Login incorrect" for valid users.
  # Whitelist nologin so FTP-only users can authenticate.
  if ! grep -qx '/usr/sbin/nologin' /etc/shells 2>/dev/null; then
    echo '/usr/sbin/nologin' >> /etc/shells
  fi
  if [ -x /usr/bin/false ] && ! grep -qx '/usr/bin/false' /etc/shells 2>/dev/null; then
    echo '/usr/bin/false' >> /etc/shells
  fi
  mkdir -p /var/run/vsftpd/empty
  mkdir -p /etc/vantapanel/ftp
  chmod 750 /etc/vantapanel/ftp
  systemctl enable --now vsftpd >/dev/null 2>&1 || true
  systemctl restart vsftpd >/dev/null 2>&1 || true
fi

# ---- remote MySQL: ensure MySQL listens for remote connections if used ----
# (kept conservative: we do NOT open bind-address here automatically, since
# that is a security decision. The Remote MySQL page grants per-host access;
# the admin must set bind-address=0.0.0.0 + firewall rules to actually allow it.)
mkdir -p /etc/vantapanel/remote-mysql
chmod 750 /etc/vantapanel/remote-mysql

# ---- mail server (Postfix + Dovecot, MySQL-driven virtual mailboxes) ------
# Mailboxes/forwarders live in the panel DB (mail_boxes, mail_forwarders) and
# mail is stored under each account's home (~/mail/<domain>/<user>). Postfix
# and Dovecot read the DB directly via SQL maps.
if [ "${VANTAPANEL_SKIP_MAIL:-0}" != "1" ]; then
  say "Installing mail server (Postfix + Dovecot)"
  echo "postfix postfix/main_mailer_type select Internet Site" | debconf-set-selections 2>/dev/null || true
  echo "postfix postfix/mailname string ${MAIL_HOSTNAME}" | debconf-set-selections 2>/dev/null || true
  apt-get -o DPkg::Lock::Timeout=300 install -y postfix postfix-mysql dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql \
    dovecot-sieve dovecot-managesieved spamassassin spamc \
    || say "mail packages failed to install (non-fatal) — email features will be unavailable"

  if command -v postfix >/dev/null 2>&1 && command -v dovecot >/dev/null 2>&1; then
    # The panel's self-signed cert is generated later in this script; mail needs
    # it now for TLS. Generate it early if it isn't there yet (idempotent).
    if [ ! -f "${CERT_DIR}/self.crt" ]; then
      mkdir -p "${CERT_DIR}"
      openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
        -keyout "${CERT_DIR}/self.key" -out "${CERT_DIR}/self.crt" \
        -subj "/CN=${ACCESS_HOST}" >/dev/null 2>&1 || true
      chmod 600 "${CERT_DIR}/self.key" 2>/dev/null || true
    fi
    # vmail not needed (mail lives in account homes owned by account users), but
    # Dovecot needs a way to map address -> uid/home. We resolve uid via the
    # account that owns the domain. Mail dirs are owned by the account user.

    # --- Postfix MySQL maps (read-only DB user is the panel DB user) ---
    DBP="$(grep -oP "'pass'\s*=>\s*'\K[^']+" "$APP_DIR/lib/config.php" | head -1)"
    mkdir -p /etc/postfix/mysql

    cat > /etc/postfix/mysql/virtual_domains.cf <<MYSQLCF
user = vantapanel
password = ${DBP}
hosts = 127.0.0.1
dbname = vantapanel
query = SELECT DISTINCT domain FROM mail_boxes WHERE domain='%s' AND active=1 UNION SELECT DISTINCT domain FROM mail_forwarders WHERE domain='%s'
MYSQLCF

    cat > /etc/postfix/mysql/virtual_mailboxes.cf <<MYSQLCF
user = vantapanel
password = ${DBP}
hosts = 127.0.0.1
dbname = vantapanel
query = SELECT CONCAT(a.username,'/',m.maildir) FROM mail_boxes m JOIN accounts a ON a.id=m.account_id WHERE m.address='%s' AND m.active=1
MYSQLCF

    cat > /etc/postfix/mysql/virtual_aliases.cf <<MYSQLCF
user = vantapanel
password = ${DBP}
hosts = 127.0.0.1
dbname = vantapanel
query = SELECT destination FROM mail_forwarders WHERE source='%s' OR source=CONCAT('@',SUBSTRING_INDEX('%s','@',-1))
MYSQLCF

    chmod 640 /etc/postfix/mysql/*.cf
    chgrp postfix /etc/postfix/mysql/*.cf 2>/dev/null || true

    # --- Postfix main.cf settings ---
    postconf -e "myhostname = ${MAIL_HOSTNAME}"
    postconf -e "virtual_mailbox_domains = mysql:/etc/postfix/mysql/virtual_domains.cf"
    postconf -e "virtual_mailbox_base = /home"
    postconf -e "virtual_mailbox_maps = mysql:/etc/postfix/mysql/virtual_mailboxes.cf"
    postconf -e "virtual_alias_maps = mysql:/etc/postfix/mysql/virtual_aliases.cf"
    postconf -e "virtual_transport = lmtp:unix:private/dovecot-lmtp"
    postconf -e "smtpd_sasl_type = dovecot"
    postconf -e "smtpd_sasl_path = private/auth"
    postconf -e "smtpd_sasl_auth_enable = yes"
    postconf -e "smtpd_tls_security_level = may"
    postconf -e "smtpd_tls_cert_file = ${CERT_DIR}/self.crt"
    postconf -e "smtpd_tls_key_file = ${CERT_DIR}/self.key"
    postconf -e "smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination"
    # enable submission (587) + smtps (465)
    if ! grep -q '^submission ' /etc/postfix/master.cf; then
      cat >> /etc/postfix/master.cf <<'MASTER'
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
MASTER
    fi

    # --- Dovecot SQL auth + mail location ---
    mkdir -p /etc/dovecot/conf.d
    cat > /etc/dovecot/dovecot-sql.conf.ext <<DSQL
driver = mysql
connect = host=127.0.0.1 dbname=vantapanel user=vantapanel password=${DBP}
default_pass_scheme = SHA512-CRYPT
password_query = SELECT address AS user, password FROM mail_boxes WHERE address='%u' AND active=1
user_query = SELECT CONCAT('/home/', a.username, '/', m.maildir) AS home, \
  CONCAT('maildir:/home/', a.username, '/', m.maildir) AS mail, \
  m.sys_uid AS uid, m.sys_gid AS gid FROM mail_boxes m JOIN accounts a ON a.id=m.account_id WHERE m.address='%u'
DSQL
    chmod 600 /etc/dovecot/dovecot-sql.conf.ext

    cat > /etc/dovecot/conf.d/99-vantapanel.conf <<'DCONF'
disable_plaintext_auth = yes
mail_privileged_group = mail
auth_mechanisms = plain login
passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
userdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}
service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}
protocol lmtp {
  mail_plugins = $mail_plugins sieve
}
plugin {
  sieve = file:~/sieve;active=~/.dovecot.sieve
}
ssl = yes
ssl_cert = <CERTCRT
ssl_key = <CERTKEY
protocols = imap pop3 lmtp sieve
DCONF
    # substitute cert paths (avoid heredoc var expansion issues)
    # Dovecot needs the '<' file-read prefix: ssl_cert = </path (without it, Dovecot
    # treats the path string as the cert body -> "no valid PEM certificate" -> ALL
    # TLS/SSL fails, so no external mail client can ever connect over 993/995/STARTTLS.
    sed -i "s#<CERTCRT#<${CERT_DIR}/self.crt#" /etc/dovecot/conf.d/99-vantapanel.conf
    sed -i "s#<CERTKEY#<${CERT_DIR}/self.key#" /etc/dovecot/conf.d/99-vantapanel.conf

    # CRITICAL: disable Dovecot's default PAM/system auth. Ubuntu ships
    # 10-auth.conf with `!include auth-system.conf.ext` (PAM), which intercepts
    # auth before our SQL passdb and fails with "user unknown" for virtual
    # mailboxes (they aren't Linux users). Comment it out so ONLY SQL is used.
    if [ -f /etc/dovecot/conf.d/10-auth.conf ]; then
      sed -i 's/^[[:space:]]*!include auth-system.conf.ext/#&/' /etc/dovecot/conf.d/10-auth.conf
    fi

    systemctl enable --now postfix dovecot >/dev/null 2>&1 || true
    systemctl restart postfix dovecot >/dev/null 2>&1 || true
    say "Mail server configured. NOTE: deliverability needs a PTR (reverse DNS)"
    say "record for ${SERVER_IP}, port 25 open at your VPS provider, and MX+SPF"
    say "DNS records per domain (set these in the panel's DNS Zone Editor)."
  fi

  # --- SpamAssassin (spam filtering) ---
  if command -v spamassassin >/dev/null 2>&1 || command -v spamd >/dev/null 2>&1; then
    say "Configuring SpamAssassin"
    mkdir -p /etc/vantapanel/spam
    chmod 750 /etc/vantapanel/spam
    # enable + start spamd
    if [ -f /etc/default/spamd ]; then
      sed -i 's/^ENABLED=.*/ENABLED=1/' /etc/default/spamd 2>/dev/null || true
    fi
    systemctl enable --now spamassassin >/dev/null 2>&1 || systemctl enable --now spamd >/dev/null 2>&1 || true
    # wire spamassassin into Postfix via a content filter on the smtp receive path
    if ! grep -q 'spamassassin' /etc/postfix/master.cf; then
      # add a spamassassin filter service and route smtp through it
      sed -i 's#^smtp\(\s\+inet.*\)#smtp\1\n  -o content_filter=spamassassin#' /etc/postfix/master.cf 2>/dev/null || true
      cat >> /etc/postfix/master.cf <<'SAMASTER'
spamassassin unix -     n       n       -       -       pipe
  user=debian-spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
SAMASTER
      systemctl restart postfix >/dev/null 2>&1 || true
    fi
    say "SpamAssassin running. Per-account thresholds are set in the panel."
  fi

  # --- Roundcube (webmail) ---
  if [ "${VANTAPANEL_SKIP_WEBMAIL:-0}" != "1" ]; then
    say "Installing Roundcube (webmail)"
    # roundcube uses dbconfig; preseed to use sqlite to avoid extra DB prompts
    echo "roundcube-core roundcube/dbconfig-install boolean true"       | debconf-set-selections 2>/dev/null || true
    echo "roundcube-core roundcube/database-type select sqlite3"        | debconf-set-selections 2>/dev/null || true
    echo "roundcube-core roundcube/reconfigure-webserver multiselect apache2" | debconf-set-selections 2>/dev/null || true
    apt-get -o DPkg::Lock::Timeout=300 install -y roundcube roundcube-core roundcube-sqlite3 \
      || say "Roundcube install skipped/failed (non-fatal) — Webmail will be unavailable"
    if [ -d /var/lib/roundcube ]; then
      # point roundcube at the local IMAP/SMTP server
      RCFG=/etc/roundcube/config.inc.php
      if [ -f "$RCFG" ]; then
        # IMAP/SMTP are on the same host (loopback). Use 127.0.0.1 (IPv4) to
        # avoid IPv6 ::1 resolution issues, plain IMAP on 143, STARTTLS SMTP on
        # 587. Roundcube renamed keys across versions (default_host->imap_host,
        # smtp_server->smtp_host in 1.6+), so we strip all variants and set both
        # the new and legacy keys to be version-proof.
        sed -i "/\$config\['default_host'\]/d; /\$config\['imap_host'\]/d; \
                /\$config\['smtp_server'\]/d; /\$config\['smtp_host'\]/d; \
                /\$config\['imap_port'\]/d; /\$config\['smtp_port'\]/d; \
                /\$config\['smtp_user'\]/d; /\$config\['smtp_pass'\]/d; \
                /\$config\['smtp_conn_options'\]/d; /\$config\['imap_conn_options'\]/d" "$RCFG"
        cat >> "$RCFG" <<'RCEXTRA'
// Vanta Panel mail connection (IPv4 loopback; version-proof keys)
$config['imap_host'] = ["127.0.0.1:143"];
$config['default_host'] = '127.0.0.1';
$config['imap_port'] = 143;
$config['smtp_host'] = 'tls://127.0.0.1:587';
$config['smtp_server'] = 'tls://127.0.0.1';
$config['smtp_port'] = 587;
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['smtp_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]];
$config['imap_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]];
RCEXTRA
      fi
      # the per-account vhosts already alias /webmail -> /var/lib/roundcube/public_html
      systemctl reload apache2 >/dev/null 2>&1 || true
      say "Roundcube installed. Webmail is available at https://<your-domain>/webmail"
    fi
  fi
fi

# ---- systemd ---------------------------------------------------------------
say "Installing systemd services"
cp "$APP_DIR/systemd/vantapanel-whm.service"  /etc/systemd/system/
cp "$APP_DIR/systemd/vantapanel-user.service" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now vantapanel-whm.service vantapanel-user.service
sleep 1
systemctl is-active --quiet vantapanel-whm.service  || die "vantapanel-whm failed to start (journalctl -u vantapanel-whm)"
systemctl is-active --quiet vantapanel-user.service || die "vantapanel-user failed to start (journalctl -u vantapanel-user)"

# ---- WHM Basic Auth gate (OPTIONAL — off by default) -----------------------
# By default /vwhm is protected by the panel's own login + optional 2FA, just
# like cPanel/WHM. A second HTTP Basic Auth prompt only confuses first-time
# admins (same "admin" username, different password). Set VANTAPANEL_WHM_GATE=1
# to add it back as defense-in-depth.
WHM_GATE="${VANTAPANEL_WHM_GATE:-0}"
GATE_PASS=""
if [ "$WHM_GATE" = "1" ]; then
  say "WHM Basic Auth gate (enabled)"
  if [ ! -f /etc/apache2/.vantapanel-admin ]; then
    GATE_PASS="$(gen_pass)"
    htpasswd -bc /etc/apache2/.vantapanel-admin admin "$GATE_PASS" >/dev/null
    echo "  Basic Auth  user: admin   pass: $GATE_PASS"
  else
    echo "  (existing /etc/apache2/.vantapanel-admin kept)"
  fi
fi

# ---- self-signed certificate (first-boot HTTPS, like fresh WHM) ------------
say "Generating self-signed certificate for ${ACCESS_HOST}"
mkdir -p "$CERT_DIR"
if [ ! -f "$CERT_DIR/self.crt" ]; then
  openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
    -keyout "$CERT_DIR/self.key" -out "$CERT_DIR/self.crt" \
    -subj "/CN=${ACCESS_HOST}" >/dev/null 2>&1
  chmod 600 "$CERT_DIR/self.key"
fi

# ---- apache: single path-based default vhost ------------------------------
setup_local_dns

say "Installing Apache vhost (panel on /vwhm and /vpanel)"
# stand down Ubuntu defaults and any legacy panel vhosts that would grab :80/:443
a2dissite 000-default default-ssl >/dev/null 2>&1 || true
a2dissite vantapanel-whm vantapanel-user vantapanel-whm-le-ssl vantapanel-user-le-ssl >/dev/null 2>&1 || true
rm -f /etc/apache2/sites-available/vantapanel-whm.conf /etc/apache2/sites-available/vantapanel-user.conf 2>/dev/null || true

PANEL_PROXY="$(cat <<'PROXY'
    RedirectMatch 301 ^/vwhm$   /vwhm/
    RedirectMatch 301 ^/vpanel$ /vpanel/
    ProxyPreserveHost On
    ProxyPass        /.well-known/ !
    ProxyPass        /vwhm/   http://127.0.0.1:2087/
    ProxyPassReverse /vwhm/   http://127.0.0.1:2087/
    ProxyPass        /vpanel/ http://127.0.0.1:2083/
    ProxyPassReverse /vpanel/ http://127.0.0.1:2083/
    # REST API: pretty /api/<resource> -> the user-panel API handler (?api=<resource>)
    RewriteEngine On
    RewriteRule "^/api/([a-z]+)/?$" "http://127.0.0.1:2083/?api=$1" [P,QSA,L]
PROXY
)"

# Add the HTTP Basic Auth gate on /vwhm only when explicitly enabled.
if [ "$WHM_GATE" = "1" ]; then
  PANEL_PROXY="$PANEL_PROXY
    <Location \"/vwhm/\">
        AuthType Basic
        AuthName \"Vanta WHM\"
        AuthUserFile /etc/apache2/.vantapanel-admin
        Require valid-user
    </Location>"
fi

cat > /etc/apache2/sites-available/000-vantapanel.conf <<CONF
# Vanta Panel — default access vhost. Answers on the server IP and any host
# that is not a specific account domain.  Panel lives under /vwhm and /vpanel.
<VirtualHost *:80>
    ServerName ${ACCESS_HOST}
    DocumentRoot /var/www/html
${PANEL_PROXY}
    ErrorLog \${APACHE_LOG_DIR}/vantapanel-error.log
    CustomLog \${APACHE_LOG_DIR}/vantapanel-access.log combined
</VirtualHost>
<VirtualHost *:443>
    ServerName ${ACCESS_HOST}
    DocumentRoot /var/www/html
    SSLEngine on
    SSLCertificateFile    ${CERT_DIR}/self.crt
    SSLCertificateKeyFile ${CERT_DIR}/self.key
    RequestHeader set X-Forwarded-Proto "https"
${PANEL_PROXY}
    ErrorLog \${APACHE_LOG_DIR}/vantapanel-error.log
    CustomLog \${APACHE_LOG_DIR}/vantapanel-access.log combined
</VirtualHost>
CONF
a2ensite 000-vantapanel.conf >/dev/null
[ "${WHM_GATE:-0}" = "1" ] || remove_whm_gate
apache2ctl configtest
systemctl reload apache2

# ---- WHM administrator (idempotent) ----------------------------------------
# Fresh install: create the admin with a generated password. Re-run (upgrade or
# repair): KEEP the existing admin untouched, unless ADMIN_PASS is explicitly
# given to force a reset. This is what makes "reinstall to upgrade" safe.
say "WHM administrator"
ADMIN_USER="${ADMIN_USER:-root}"
ADMIN_EXISTS="$(sudo -u "$PANEL_USER" php -r '
  $c = require $argv[1]; $d = $c["db"];
  try { $p = new PDO("mysql:host={$d["host"]};dbname={$d["name"]}", $d["user"], $d["pass"]);
        echo (int) $p->query("SELECT COUNT(*) FROM admins")->fetchColumn(); }
  catch (Throwable $e) { echo 0; }
' "$APP_DIR/lib/config.php" 2>/dev/null || echo 0)"

if [ "${ADMIN_EXISTS:-0}" -gt 0 ] && [ -z "${ADMIN_PASS:-}" ]; then
  ADMIN_PASS=""                       # keep current credentials; nothing reset
  echo "  existing admin kept (set ADMIN_PASS=... before install to force a reset)"
else
  ADMIN_PASS="${ADMIN_PASS:-$(gen_pass)}"
  sudo -u "$PANEL_USER" php "$APP_DIR/bin/vantapanel-admin.php" "$ADMIN_USER" "$ADMIN_PASS"
  echo "  WHM login   user: $ADMIN_USER   pass: $ADMIN_PASS"
fi

# Save credentials to a root-only file when we actually set a password this run
# (a re-run that keeps the existing admin leaves the previous file in place).
if [ -n "${ADMIN_PASS}" ]; then
  CREDS_FILE="/root/vantapanel-install.txt"
  {
    echo "Vanta Panel — admin credentials (generated $(date))"
    echo
    echo "WHM (admin):  https://${ACCESS_HOST}/vwhm"
    echo "User panel:   https://${ACCESS_HOST}/vpanel"
    echo
    echo "WHM login   user: ${ADMIN_USER}   pass: ${ADMIN_PASS}"
    if [ "$WHM_GATE" = "1" ] && [ -n "$GATE_PASS" ]; then
      echo "Basic gate  user: admin   pass: ${GATE_PASS}"
    fi
  } > "$CREDS_FILE"
  chmod 600 "$CREDS_FILE"
  echo "  credentials saved to $CREDS_FILE (root only)"
fi

# ---- scheduled-backup cron ------------------------------------------------
cat > /etc/cron.d/vantapanel-backups <<CRON
# vantapanel backup scheduler — runs every 5 minutes; executes any per-site full
# or DB-only schedule that is due (evaluation + locking are inside the runner).
*/5 * * * * root /usr/bin/php $APP_DIR/bin/vantapanel-cron.php >> /var/log/vantapanel-cron.log 2>&1
CRON
chmod 644 /etc/cron.d/vantapanel-backups
echo "  scheduled-backup cron installed at /etc/cron.d/vantapanel-backups"

# ---- anonymous install id (for the usage heartbeat) -----------------------
mkdir -p /etc/vantapanel
if [ ! -f /etc/vantapanel/install_id ]; then
  if [ -r /proc/sys/kernel/random/uuid ]; then
    cat /proc/sys/kernel/random/uuid > /etc/vantapanel/install_id
  else
    head -c16 /dev/urandom | od -An -tx1 | tr -d ' \n' > /etc/vantapanel/install_id
  fi
  chmod 640 /etc/vantapanel/install_id
  echo "  anonymous install id created (/etc/vantapanel/install_id)"
fi

# ---- done ------------------------------------------------------------------
SELF_NOTE=""
if [ "$ACCESS_IS_IP" = 1 ]; then
  SELF_NOTE="
 NOTE: this is a self-signed certificate (no domain yet), so your browser
 will warn on first visit — click through to proceed. To get a free trusted
 certificate, point a domain's A record at ${SERVER_IP} and run:
   sudo certbot --apache -d yourdomain.com"
fi

# (The LOGIN section of the finish banner is built below, after the colour vars.)

# ---- security hardening: firewall + fail2ban (idempotent, secure-by-default) --
# Locks the server to default-deny + only the ports the panel needs, and bans
# SSH/FTP brute-force. Best-effort: never aborts the install if a piece fails.
# SSH stays open to anywhere so a remote install can't lock itself out.
harden_server(){
  say "Hardening: firewall + brute-force protection"

  # --- ufw firewall: default-deny, allow only what the panel serves ---
  if apt-get -o DPkg::Lock::Timeout=300 install -y ufw >/dev/null 2>&1 && command -v ufw >/dev/null 2>&1; then
    if ufw status 2>/dev/null | grep -q "Status: active"; then
      # Firewall already configured (likely a re-install) — DON'T touch it, just
      # make sure the panel's required ports are allowed, then leave it alone.
      ufw allow 22/tcp  >/dev/null 2>&1 || true
      ufw allow 80/tcp  >/dev/null 2>&1 || true
      ufw allow 443/tcp >/dev/null 2>&1 || true
      ufw allow 53/tcp  >/dev/null 2>&1 || true   # DNS (authoritative PowerDNS) — also on upgrade re-runs
      ufw allow 53/udp  >/dev/null 2>&1 || true
      ufw allow 25/tcp  >/dev/null 2>&1 || true   # SMTP  (inbound mail from other servers)
      ufw allow 587/tcp >/dev/null 2>&1 || true   # submission (authenticated send)
      ufw allow 465/tcp >/dev/null 2>&1 || true   # SMTPS
      ufw allow 143/tcp >/dev/null 2>&1 || true   # IMAP
      ufw allow 993/tcp >/dev/null 2>&1 || true   # IMAPS
      ufw allow 110/tcp >/dev/null 2>&1 || true   # POP3
      ufw allow 995/tcp >/dev/null 2>&1 || true   # POP3S
      say "  firewall already active — left your rules intact (ensured 22/80/443 + mail ports allowed)"
    else
      ufw default deny incoming  >/dev/null 2>&1 || true
      ufw default allow outgoing >/dev/null 2>&1 || true
      ufw allow 22/tcp           >/dev/null 2>&1 || true   # SSH (kept open: don't lock out remote installs)
      ufw allow 80/tcp           >/dev/null 2>&1 || true   # HTTP  (hosted sites + panel path)
      ufw allow 443/tcp          >/dev/null 2>&1 || true   # HTTPS (hosted sites + panel path)
      ufw allow 21/tcp           >/dev/null 2>&1 || true   # FTP control
      ufw allow 40000:40100/tcp  >/dev/null 2>&1 || true   # FTP passive (matches vsftpd config above)
      ufw allow 53/tcp           >/dev/null 2>&1 || true   # DNS (authoritative PowerDNS)
      ufw allow 53/udp           >/dev/null 2>&1 || true   # DNS (authoritative PowerDNS)
      ufw allow 25/tcp           >/dev/null 2>&1 || true   # SMTP  (inbound mail from other servers)
      ufw allow 587/tcp          >/dev/null 2>&1 || true   # submission (authenticated send)
      ufw allow 465/tcp          >/dev/null 2>&1 || true   # SMTPS
      ufw allow 143/tcp          >/dev/null 2>&1 || true   # IMAP
      ufw allow 993/tcp          >/dev/null 2>&1 || true   # IMAPS
      ufw allow 110/tcp          >/dev/null 2>&1 || true   # POP3
      ufw allow 995/tcp          >/dev/null 2>&1 || true   # POP3S
      ufw --force enable         >/dev/null 2>&1 || true
      say "  firewall active (default-deny; open: 22, 80, 443, 21, 40000-40100, 53, mail 25/465/587/143/993/110/995)"
    fi
  else
    say "  WARNING: could not install/enable ufw — skipping firewall (you can set it up later)"
  fi

  # --- fail2ban: auto-ban SSH + FTP brute-force ---
  if apt-get -o DPkg::Lock::Timeout=300 install -y fail2ban >/dev/null 2>&1; then
    if [ ! -f /etc/fail2ban/jail.local ]; then
      cat > /etc/fail2ban/jail.local <<'F2B'
[DEFAULT]
# Never ban localhost. Add a static admin IP here (space-separated) if you have one.
ignoreip = 127.0.0.1/8 ::1
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport

[sshd]
enabled  = true
port     = ssh
logpath  = /var/log/auth.log

[vsftpd]
enabled  = true
port     = ftp,ftp-data,40000:40100
logpath  = /var/log/vsftpd.log
backend  = polling
F2B
    fi
    systemctl enable fail2ban  >/dev/null 2>&1 || true
    systemctl restart fail2ban >/dev/null 2>&1 || true
    say "  fail2ban active (sshd + vsftpd jails)"
  else
    say "  WARNING: could not install fail2ban — skipping brute-force protection"
  fi
}
harden_server

# Branded finish banner (hardcoded — no figlet dependency on the target box).
# Guidance (WHAT NEXT / MANAGE / PRIVACY) prints first; the ASCII logo and the
# ACCESS + LOGIN block print DEAD LAST so the credentials end the screen.
_M=$'\033[1;35m'; _C=$'\033[1;36m'; _B=$'\033[1m'; _D=$'\033[2m'; _R=$'\033[0m'

# If we set a password this run, show it; on a re-run that kept the admin, say so.
if [ -n "${ADMIN_PASS}" ]; then
  ADMIN_PASS_SHOW="${ADMIN_PASS}"
  CREDS_NOTE="   Saved to /root/vantapanel-install.txt (root only) — also printed above."
else
  ADMIN_PASS_SHOW="(unchanged — your existing admin password still works)"
  CREDS_NOTE="   Your existing admin login is unchanged."
fi

# Build the LOGIN + gate-reset sections of the banner (depends on the gate).
# The gate is OFF by default (VANTAPANEL_WHM_GATE=0): signing in to the panel
# needs exactly ONE password — the admin login shown below.
if [ "$WHM_GATE" = "1" ]; then
  GATE_SHOW="${GATE_PASS:-(unchanged - kept existing /etc/apache2/.vantapanel-admin)}"
  LOGIN_BLOCK=" ${_C}LOGIN  —  /vwhm asks for TWO logins, in this order${_R}
   1) Browser gate (Basic Auth)
        user:  admin
        pass:  ${GATE_SHOW}
   2) Vanta Panel admin
        user:  ${ADMIN_USER}
        pass:  ${ADMIN_PASS_SHOW}"
  GATE_RESET="
   Reset the browser gate:
     sudo htpasswd /etc/apache2/.vantapanel-admin admin"
else
  LOGIN_BLOCK=" ${_C}LOGIN${_R}
   Open  https://${ACCESS_HOST}/vwhm  and sign in:
        user:  ${ADMIN_USER}
        pass:  ${ADMIN_PASS_SHOW}"
  GATE_RESET=""
fi

cat <<DONE

 ${_C}WHAT NEXT${_R}
   1. Open  https://${ACCESS_HOST}/vwhm  and sign in.
   2. Create your first hosting account in WHM. That account gets its
      own /vpanel login, where its email and databases are managed.
   3. Point a domain at this server and issue free trusted SSL to
      replace the self-signed cert:
        sudo certbot --apache -d yourdomain.com
   4. The free tier is capped at 1 account / 1 email / 1 database.
      To lift the caps, activate a license on the WHM "License" page
      (get a key at https://vantapanel.com/pricing).

 ${_C}MANAGE${_R}
   Reset the admin password:
     sudo -u ${PANEL_USER} php ${APP_DIR}/bin/vantapanel-admin.php ${ADMIN_USER} 'NewPass'${GATE_RESET}

 ${_C}PRIVACY${_R}
   Vanta Panel sends one anonymous usage ping per day (a random id, version,
   plan, and account count). No domains, emails, IP addresses, or site data
   are collected. To opt out:
     sudo touch /etc/vantapanel/no-telemetry
DONE

# ---- ASCII logo + access details — the LAST thing on screen ----------------
printf '\n%s\n' "$_M"
cat <<'ART'
   __     __          _          ____                  _
   \ \   / /_ _ _ __ | |_ __ _  |  _ \ __ _ _ __   ___| |
    \ \ / / _` | '_ \| __/ _` | | |_) / _` | '_ \ / _ \ |
     \ V / (_| | | | | || (_| | |  __/ (_| | | | |  __/ |
      \_/ \__,_|_| |_|\__\__,_| |_|   \__,_|_| |_|\___|_|
ART
printf '%s' "$_R"

cat <<DONE
${_D}              self-hosted hosting control panel${_R}
============================================================
 ${_B}Vanta Panel is installed and running.${_R}

 ${_C}ACCESS${_R}
   WHM (admin):   https://${ACCESS_HOST}/vwhm
   User panel:    https://${ACCESS_HOST}/vpanel

${LOGIN_BLOCK}
${CREDS_NOTE}
${SELF_NOTE}
============================================================
DONE
