#!/usr/bin/env bash
export LC_ALL=C.UTF-8 2>/dev/null || true
shopt -s extglob
[ -t 1 ] || { echo "Run this in a real terminal"; exit 1; }
VERSION="1.6.4"

rgb() { printf '\e[38;2;%s;%s;%sm' "$1" "$2" "$3"; }
N=$'\e[0m'; B=$'\e[1m'
RED=$(rgb 255 60 80);  ACC=$(rgb 255 115 135); WHT=$(rgb 236 236 246)
GRN=$(rgb 80 250 150); DIM=$(rgb 125 125 145); YEL=$(rgb 255 190 80)
SEL=$'\e[48;2;190;24;52m\e[1;38;2;255;255;255m'

grad() {
  local s=$1 n=${#1} i r g b c out=""
  for ((i = 0; i < n; i++)); do
    r=$(($2 + ($5 - $2) * i / (n > 1 ? n - 1 : 1)))
    g=$(($3 + ($6 - $3) * i / (n > 1 ? n - 1 : 1)))
    b=$(($4 + ($7 - $4) * i / (n > 1 ? n - 1 : 1)))
    printf -v c '\e[38;2;%d;%d;%dm' "$r" "$g" "$b"; out+="$c${s:i:1}"
  done
  printf '%s%s' "$B$out" "$N"
}
TITLE=$(grad "C R I M S O N   G A T E" 255 70 90 255 175 60)
BAN=('   ██████╗██████╗ ██╗███╗   ███╗███████╗ ██████╗ ███╗   ██╗'
     '  ██╔════╝██╔══██╗██║████╗ ████║██╔════╝██╔═══██╗████╗  ██║'
     '  ██║     ██████╔╝██║██╔████╔██║███████╗██║   ██║██╔██╗ ██║'
     '  ██║     ██╔══██╗██║██║╚██╔╝██║╚════██║██║   ██║██║╚██╗██║'
     '  ╚██████╗██║  ██║██║██║ ╚═╝ ██║███████║╚██████╔╝██║ ╚████║'
     '   ╚═════╝╚═╝  ╚═╝╚═╝╚═╝     ╚═╝╚══════╝ ╚═════╝ ╚═╝  ╚═══╝')

ENVF=/etc/crimson/paths.env; XCFG=/usr/local/etc/xray/config.json
TLS_CRT=/etc/crimson/tls/server.crt
LIVE=0; [ -r "$ENVF" ] && LIVE=1
rnd() { head -c "$1" /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n'; }
uid() { cat /proc/sys/kernel/random/uuid 2>/dev/null || echo "7c1e3f5a-9b2d-4e61-8a07-3d5c2f9b1e44"; }
getv() { [ -r "$ENVF" ] && sed -n "s/^$1=\"\{0,1\}\([^\"]*\)\"\{0,1\}\$/\1/p" "$ENVF" | head -1; }
H0=$(rnd 16); H1=$(rnd 16); H2=$(rnd 16); H3=$(rnd 16); H4=$(rnd 16)
UN=(default ali sara); US=("$(uid)" "$(uid)" "$(uid)"); TP=("$(rnd 16)" "$(rnd 16)" "$(rnd 16)")
SSK=$(head -c32 /dev/urandom 2>/dev/null | base64 | tr -d '\n'); SSK=${SSK:-q3Jp7mXk0aVn2LwZ8uYtB1cHd4eFgS6o}
SSK=${SSK//+/%2B}; SSK=${SSK//\//%2F}; SSK=${SSK//=/%3D}

cnt() { local n=""; if [ -r "$XCFG" ] && command -v jq >/dev/null 2>&1; then
  n=$(jq -r --arg t "$1" '[.inbounds[]?|select(.tag==$t)|.settings.clients[]?]|length' "$XCFG" 2>/dev/null); fi
  echo "${n:-$2}"; }
svc() { if command -v systemctl >/dev/null 2>&1 && systemctl cat "$1" >/dev/null 2>&1; then
  systemctl is-active --quiet "$1" && echo 1 || echo 0; else echo 1; fi; }

load_data() {
  local v iss sub end now
  SERVER_ADDR=$(getv SERVER_ADDR); SERVER_ADDR=${SERVER_ADDR:-vpn.example.com}
  REMOTE=$(getv REMOTE_PROXY);     REMOTE=${REMOTE:-$SERVER_ADDR:443}
  SNI=$(getv CLIENT_SNI);          SNI=${SNI:-$SERVER_ADDR}
  HOSTH=$(getv CLIENT_HOST);       HOSTH=${HOSTH:-$SERVER_ADDR}
  SEC=$(getv CLIENT_SEC);          SEC=${SEC:-auto}
  WS_VLESS=$(getv WSP_VLESS);  WS_VLESS=${WS_VLESS:-/assets/js/chunk.$H0.js}
  WS_VMESS=$(getv WSP_VMESS);  WS_VMESS=${WS_VMESS:-/_next/static/chunks/$H1}
  WS_TROJAN=$(getv WSP_TROJAN); WS_TROJAN=${WS_TROJAN:-/static/media/$H2}
  WS_SS=$(getv WSP_SS);        WS_SS=${WS_SS:-/cdn/assets/$H3}
  WS_SSH=$(getv WSP_SSH);      WS_SSH=${WS_SSH:-/socket/v1/$H4}
  RHOST=${REMOTE%:*}; RPORT=${REMOTE##*:}; [[ $RPORT =~ ^[0-9]+$ ]] || RPORT=443
  case $SEC in tls) TLSON=1 ;; none) TLSON=0 ;;
    *) case $RPORT in 80|8080|8880|2052|2082|2086|2095) TLSON=0 ;; *) TLSON=1 ;; esac ;; esac
  CERT_OK=1; CKIND="Let's Encrypt"; CDAYS=64
  CFP=$(printf 'crimson-demo' | sha256sum | cut -d' ' -f1)
  if [ -r "$TLS_CRT" ] && command -v openssl >/dev/null 2>&1; then
    iss=$(openssl x509 -issuer -noout -in "$TLS_CRT" 2>/dev/null | sed 's/^issuer=//')
    sub=$(openssl x509 -subject -noout -in "$TLS_CRT" 2>/dev/null | sed 's/^subject=//')
    if [ "$iss" = "$sub" ]; then CKIND="self-signed"
    elif grep -qi "Let's Encrypt\|ISRG\|R3\|R1[0-9]\|E[0-9]" <<< "$iss"; then CKIND="Let's Encrypt"; else CKIND="CA"; fi
    end=$(openssl x509 -enddate -noout -in "$TLS_CRT" 2>/dev/null | cut -d= -f2); end=$(date -d "$end" +%s 2>/dev/null)
    now=$(date +%s); CDAYS=$(((${end:-$now} - now) / 86400))
    openssl x509 -checkend 0 -noout -in "$TLS_CRT" >/dev/null 2>&1 || CERT_OK=0
    CFP=$(openssl x509 -in "$TLS_CRT" -outform DER 2>/dev/null | sha256sum | cut -d' ' -f1)
  fi
  if ((TLSON == 0)); then TLSMODE="plain WebSocket (no TLS)"
  elif [ "$CKIND" = "self-signed" ] && [ "${RHOST,,}" = "${SERVER_ADDR,,}" ]; then TLSMODE="TLS, self-signed cert pinned"
  else TLSMODE="TLS, verified by the remote proxy cert"; fi
  XV=$(xray version 2>/dev/null | head -1 | awk '{print $2}'); XV=${XV:-25.9.11}
  ((LIVE)) && XU=INSTALLED || XU=CURRENT
  N_VLESS=$(cnt vless-ws 3); N_VMESS=$(cnt vmess-ws 2); N_TROJAN=$(cnt trojan-ws 1); N_SS=$(cnt ss-ws 1)
  if getent group tunnel-only >/dev/null 2>&1; then
    N_SSH=$(getent group tunnel-only | awk -F: '{print $4}' | tr ',' '\n' | grep -c .)
  else N_SSH=5; fi
  N_TOTAL=$((N_VLESS + N_VMESS + N_TROJAN + N_SS + N_SSH))
  S_NGX=$(svc nginx); S_XRAY=$(svc xray); S_SSH=$(svc crimson-ssh-ws)
  S_UFW=$(svc ufw); S_F2B=$(svc fail2ban)
  S_BBR=1; [ -r /proc/sys/net/ipv4/tcp_congestion_control ] && \
    { grep -q bbr /proc/sys/net/ipv4/tcp_congestion_control && S_BBR=1 || S_BBR=0; }
  OSN=$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-Linux}")
  IPV=$(hostname -I 2>/dev/null | awk '{print $1}'); IPV=${IPV:-n/a}
  read -r RT RA < <(free -m 2>/dev/null | awk '/^Mem:/{print $2,$7}'); RT=${RT:-0}; RA=${RA:-0}; RU=$((RT - RA))
  read -r DU DT DP < <(df -h / 2>/dev/null | awk 'NR==2{print $3,$2,$5}'); DU=${DU:-?}; DT=${DT:-?}; DP=${DP%\%}; DP=${DP:-0}
  UPV=$(uptime -p 2>/dev/null | sed 's/^up //'); UPV=${UPV:-n/a}
  LOAD=$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null); LOAD=${LOAD:-n/a}
  KERN=$(uname -r 2>/dev/null); NCPU=$(nproc 2>/dev/null); NCPU=${NCPU:-1}
}

setdim() {
  COLS=$(tput cols 2>/dev/null || echo 80); ROWS=$(tput lines 2>/dev/null || echo 24)
  W=$((COLS - 2)); ((W > 98)) && W=98; ((W < 62)) && W=62
  M=$(((COLS - W - 2) / 2)); ((M < 0)) && M=0
  printf -v HL '%*s' "$W" ''; HL=${HL// /═}
  BIGB=0; ((ROWS >= 44)) && BIGB=1
}

CL=(); FL=(); OFF=0; AUTO=0; SELLINE=0
strip() { local s=$1; s=${s//$'\e'\[*([0-9;])m/}; REPLY=${#s}; }
ln()   { local x; printf -v x '%*s%s%s' "$M" '' "$1" "$N"; CL+=("$x"); }
row()  { strip "$1"; local p=$((W - REPLY)); ((p < 0)) && p=0; printf -v SP '%*s' "$p" ''; ln "${RED}║${N}$1${N}${SP}${RED}║"; }
crow() { strip "$1"; local l=$(((W - REPLY) / 2)); ((l < 0)) && l=0; printf -v CP '%*s' "$l" ''; row "$CP$1"; }
top()  { ln "${RED}╔${HL}╗"; }
bot()  { ln "${RED}╚${HL}╝"; }
sec()  { local t=" $1 " f; f=$((W - 1 - ${#t})); ((f < 0)) && f=0; printf -v SP '%*s' "$f" ''; SP=${SP// /═}
         ln "${RED}╠═${N}${WHT}${B}${t}${N}${RED}${SP}╣"; }
trunc() { local v=$1 n=$2; if ((${#v} > n)); then TR="${v:0:n-1}…"; else TR=$v; fi; }
kv()   { local l; printf -v l '%-13s' "$1"; trunc "$2" $((W - 18)); row " ${DIM}${l}${N} ${WHT}${TR}${N}"; }
kvc()  { local l; printf -v l '%-13s' "$1"; row " ${DIM}${l}${N} $2"; }
bar()  { local p=$1 w=$2 f c a b; ((p > 100)) && p=100; ((p < 0)) && p=0; f=$((p * w / 100)); c=$GRN
         ((p >= 70)) && c=$YEL; ((p >= 90)) && c=$RED
         printf -v a '%*s' "$f" ''; printf -v b '%*s' $((w - f)) ''; BAR="${c}${a// /█}${DIM}${b// /░}${N}"; }
badge() { if [ "$1" = 1 ]; then BD="${GRN}● ONLINE${N} "; else BD="${RED}● OFFLINE${N}"; fi; }
chk()  { if [ "$1" = 1 ]; then row " ${GRN}✓${N} ${WHT}$2${N}"; else row " ${RED}✗${N} ${WHT}$2${N}"; fi; }
wrapc() { local s=$1 col=$2 n=$((W - 6)) i; for ((i = 0; i < ${#s}; i += n)); do row "    ${col}${s:i:n}${N}"; done; }
opt()  { row " ${YEL}[$1]${N} ${WHT}$2${N}"; }

header() {
  local i c tag
  top; row ""
  if ((BIGB)); then
    for i in 0 1 2 3 4 5; do printf -v c '\e[38;2;255;%d;%dm' $((55 + i * 24)) $((85 - i * 10)); crow "${c}${BAN[i]}"; done
    crow "${DIM}G A T E${N}"
  else crow "$TITLE"; fi
  crow "${DIM}v$VERSION · VLESS · VMess · Trojan · SS2022 · SSH-WS${N}"
  if ((LIVE)); then tag="${GRN}● LIVE${N} ${DIM}real data from this server${N}"
  else tag="${YEL}● DEMO${N} ${DIM}sample data (CrimsonGate not found here)${N}"; fi
  crow "$tag"; row ""
}

LB=("Install / Reinstall" "Connection links" "TLS certificate" "Live status"
    "VLESS (TLS)" "VLESS non-TLS" "VMess" "Trojan" "Shadowsocks 2022" "SSH over WebSocket"
    "Remote/SNI/Host" "Restart services" "Quick self-test" "DEEP AUDIT" "Update Xray-core"
    "Repair" "Harden BBR/f2b" "Uninstall" "Exit")
DS=("Fresh install or full reinstall (new credentials and paths)"
    "All links: VLESS, VMess, Trojan, SS2022, SSH-WS + endpoint"
    "Renew / replace the TLS certificate, self-signed, own files"
    "Services, accounts, paths and Xray version"
    "VLESS-WS users (TLS profile): add, delete, list + links"
    "VLESS over plain WebSocket (security=none, usually :80)"
    "VMess-WS users: add, delete, list + links"
    "Trojan-WS users: add, delete, list + links"
    "Shadowsocks 2022: links, regenerate key, change cipher"
    "SSH over WebSocket: users, connection info, payload"
    "Remote Proxy, SNI, Host header and security mode"
    "Restart Xray, SSH bridge and Nginx, then self-test"
    "Quick self-test: files, services, WS upgrades, data path"
    "Deep audit: 10 layers + end-to-end for each protocol"
    "Update Xray-core (SHA256/512 verified, auto rollback)"
    "Repair Xray, sshd, bridge, Nginx and firewall rules"
    "Harden: BBR, permissions, fail2ban, firewall, time sync"
    "Uninstall CrimsonGate (Nginx/UFW packages are kept)"
    "Leave the menu")
SELI=0; BUF=""; VIEW=menu

cell() {
  local i=$1 num mark=" " plain pad nn half=$(((W - 2) / 2))
  ((i == 18)) && num=0 || num=$((i + 1))
  ((i == SELI)) && mark="▸"
  printf -v nn '%2s' "$num"; plain=" $mark $nn  ${LB[i]}"
  printf -v pad '%*s' $((half - ${#plain})) ''
  if ((i == SELI)); then CELL="${SEL}${plain}${pad}${N}"
  else CELL=" ${mark} ${ACC}${nn}${N}  ${WHT}${LB[i]}${N}${pad}"; fi
}
menu_pairs() {
  local i a b
  for ((i = $1; i <= $2; i += 2)); do
    cell "$i"; a=$CELL; b=""
    if ((i + 1 <= $2)); then cell $((i + 1)); b=$CELL; fi
    ((i == SELI || i + 1 == SELI)) && SELLINE=${#CL[@]}
    row " ${a}${b}"
  done
}

build_menu() {
  CL=(); SELLINE=0; local ram up
  header
  sec "STATUS"
  bar $((RT > 0 ? RU * 100 / RT : 0)) 10; ram=$BAR; trunc "$UPV" 16; up=$TR
  row " ${ACC}${IPV}${N} ${DIM}│${N} ${WHT}RAM${N} $ram ${WHT}$((RT > 0 ? RU * 100 / RT : 0))%${N} ${DIM}│ up $up${N}"
  badge "$S_NGX"; local b1=$BD; badge "$S_XRAY"; local b2=$BD; badge "$S_SSH"; local b3=$BD
  row " ${WHT}Nginx${N} $b1 ${WHT}Xray${N} $b2 ${WHT}SSH-WS${N} $b3"
  badge "$CERT_OK"
  row " ${WHT}Cert${N}  $BD ${DIM}$CKIND · ${CDAYS}d left${N}"
  row " ${WHT}Core${N}  Xray $XV  ${YEL}[$XU]${N}"
  row " ${WHT}VLESS${N} $N_VLESS  ${WHT}VMess${N} $N_VMESS  ${WHT}Trojan${N} $N_TROJAN  ${WHT}SS${N} $N_SS  ${WHT}SSH${N} $N_SSH  ${DIM}│ total${N} ${ACC}$N_TOTAL${N}"
  sec "SETUP";     menu_pairs 0 3
  sec "PROTOCOLS"; menu_pairs 4 9
  sec "SYSTEM";    menu_pairs 10 18
  sec "ABOUT THIS ENTRY"
  trunc "${DS[SELI]}" $((W - 5)); row " ${YEL}▸${N} ${WHT}${TR}${N}"
  bot
  FL=("   ${DIM}↑↓←→ move   ⏎ open   Tab dashboard   r refresh   q quit${N}"
      "   ${ACC}›${N} ${WHT}${BUF}${N}${DIM}_${N}")
}

build_dash() {
  CL=(); local p r1 r2 s
  header
  sec "SERVER"
  kv "OS" "$OSN"
  kv "Address" "$SERVER_ADDR  ($IPV)"
  kv "Kernel" "$KERN · $NCPU cores · load $LOAD"
  p=$((RT > 0 ? RU * 100 / RT : 0)); bar "$p" 24
  kvc "RAM" "$BAR ${WHT}${p}%${N} ${DIM}${RU}/${RT}M${N}"
  bar "$DP" 24; kvc "Disk /" "$BAR ${WHT}${DP}%${N} ${DIM}${DU}/${DT}${N}"
  kv "Uptime" "$UPV"
  sec "SERVICES"
  badge "$S_NGX";  kvc "Nginx" "$BD ${DIM}public :80 :443 (decoy site + WS routes)${N}"
  badge "$S_XRAY"; kvc "Xray" "$BD ${DIM}127.0.0.1:10000-10003 (security:none)${N}"
  badge "$S_SSH";  kvc "SSH-WS" "$BD ${DIM}127.0.0.1:9022 → sshd (tunnel-only)${N}"
  badge "$CERT_OK"; kvc "Certificate" "$BD ${DIM}$CKIND · ${CDAYS}d left${N}"
  kvc "Xray core" "${WHT}$XV${N}  ${YEL}[$XU]${N}"
  sec "ACCOUNTS"
  for s in "VLESS:$N_VLESS" "VMess:$N_VMESS" "Trojan:$N_TROJAN" "SS2022:$N_SS" "SSH:$N_SSH"; do
    bar $((${s#*:} * 10)) 20; kvc "${s%%:*}" "$BAR ${WHT}${s#*:}${N}"
  done
  kvc "Total" "${ACC}${B}$N_TOTAL${N} ${DIM}accounts${N}"
  sec "CLIENT ENDPOINT"
  kv "Remote Proxy" "$REMOTE"; kv "TLS SNI" "$SNI"; kv "WS Host" "$HOSTH"; kv "Security" "$TLSMODE"
  sec "WEBSOCKET PATHS"
  kv "VLESS" "$WS_VLESS"; kv "VMess" "$WS_VMESS"; kv "Trojan" "$WS_TROJAN"; kv "Shadowsocks" "$WS_SS"; kv "SSH-WS" "$WS_SSH"
  sec "PORTS"
  chk 1 "80/tcp   Nginx (decoy + ACME + WS)       443/tcp  Nginx TLS"
  chk 1 "22/tcp   sshd (public SSH)"
  chk 1 "10000 vless · 10001 vmess · 10002 trojan · 10003 ss"
  chk 1 "9022 ssh-ws bridge   — all internal ports on 127.0.0.1 only"
  sec "SECURITY"
  chk "$S_UFW" "UFW: deny incoming, allow SSH / 80 / 443"
  chk "$S_F2B" "fail2ban protecting sshd"
  chk "$S_BBR" "BBR + fq congestion control"
  chk 1 "sysctl harden profile (rp_filter, redirects, kptr)"
  chk 1 "Time sync (chrony) — required for VMess"
  chk 1 "Group tunnel-only · ForceCommand hold · PermitTTY no"
  chk 1 "Xray unit: NoNewPrivileges, ProtectSystem=strict"
  chk 1 "nftables guard: tunnel users blocked from private nets"
  chk 1 "certbot.timer auto-renew + nginx deploy hook"
  bot
  FL=("   ${DIM}↑↓ PgUp PgDn scroll   Tab menu   r refresh   q quit${N}")
}

paint() {
  local avail=$((ROWS - ${#FL[@]})) total=${#CL[@]} i f="" j
  ((avail < 5)) && avail=5
  if ((AUTO)); then
    if ((SELLINE < OFF)); then OFF=$((SELLINE - 2)); fi
    if ((SELLINE >= OFF + avail)); then OFF=$((SELLINE - avail + 2)); fi
    ((SELLINE < avail - 1)) && OFF=0
  fi
  ((OFF > total - avail)) && OFF=$((total - avail)); ((OFF < 0)) && OFF=0
  printf '\e[H'
  for ((i = OFF; i < OFF + avail && i < total; i++)); do printf '%s\e[K\n' "${CL[i]}"; done
  printf '\e[J'
  for ((j = 0; j < ${#FL[@]}; j++)); do ((j > 0)) && f+=$'\n'; f+="${FL[j]}"; done
  printf '%s' "$f"
}

getkey() {
  local rc k2
  IFS= read -rsn1 K; rc=$?
  if ((rc > 128)); then setdim; return 2; fi
  ((rc != 0)) && return 1
  if [[ $K == $'\e' ]]; then
    read -rsn2 -t 0.02 k2; K=$K$k2
    [[ $k2 == '['[56] ]] && read -rsn1 -t 0.02 _
  fi
  return 0
}

viewer() {
  OFF=0; AUTO=0
  while :; do
    FL=("   ${DIM}↑↓ PgUp PgDn scroll   any other key: back${N}")
    paint
    getkey; local rc=$?
    ((rc == 1)) && return; ((rc == 2)) && continue
    case $K in
      $'\e[A') OFF=$((OFF - 1)) ;; $'\e[B') OFF=$((OFF + 1)) ;;
      $'\e[5') OFF=$((OFF - 10)) ;; $'\e[6') OFF=$((OFF + 10)) ;;
      *) return ;;
    esac
  done
}

scr() { CL=(); header; sec "$1"; }

mk_link() {
  local q n=${UN[$2]} e p tls="" js b64 hp="$RHOST" pt="$RPORT"
  ((TLSON)) && { tls=tls; q="security=tls&sni=$SNI&fp=chrome&alpn=http%2F1.1"; } || q="security=none"
  case $1 in
    vless)  e=${WS_VLESS//\//%2F}
            LK="vless://${US[$2]}@$REMOTE?encryption=none&$q&type=ws&host=$HOSTH&path=$e#$SERVER_ADDR-$n-VLESS" ;;
    vless0) e=${WS_VLESS//\//%2F}; case $RPORT in 443|8443|2053|2083|2087|2096) pt=80 ;; esac
            LK="vless://${US[$2]}@$hp:$pt?encryption=none&security=none&type=ws&host=$HOSTH&path=$e#$SERVER_ADDR-$n-VLESS" ;;
    vmess)  js="{\"v\":\"2\",\"ps\":\"$SERVER_ADDR-$n-VMess\",\"add\":\"$RHOST\",\"port\":\"$RPORT\",\"id\":\"${US[$2]}\",\"aid\":\"0\",\"scy\":\"auto\",\"net\":\"ws\",\"type\":\"none\",\"host\":\"$HOSTH\",\"path\":\"$WS_VMESS\",\"tls\":\"$tls\",\"sni\":\"$SNI\",\"alpn\":\"http/1.1\",\"fp\":\"chrome\"}"
            b64=$(printf '%s' "$js" | base64 | tr -d '\n'); LK="vmess://$b64" ;;
    trojan) e=${WS_TROJAN//\//%2F}
            LK="trojan://${TP[$2]}@$REMOTE?$q&type=ws&host=$HOSTH&path=$e#$SERVER_ADDR-$n-Trojan" ;;
    ss)     e=${WS_SS//\//%2F}
            LK="ss://2022-blake3-aes-256-gcm:$SSK@$REMOTE?type=ws&$q&host=$HOSTH&path=$e#$SERVER_ADDR-SS" ;;
  esac
}
users_block() {
  local i n=$3 show=$3; ((show > 3)) && show=3
  sec "USERS ($n)"
  for ((i = 0; i < show; i++)); do
    row " ${WHT}$((i + 1)))${N} ${B}${UN[i]}${N}"; mk_link "$1" "$i"; wrapc "$LK" "$ACC"
  done
  ((n > 3)) && row " ${DIM}… and $((n - 3)) more user(s)${N}"
}

scr_proto() {
  scr "$2 · WebSocket"
  opt 1 "Add user"; opt 2 "Delete user"; opt 3 "List users + links"; opt 0 "Back"
  users_block "$1" "$2" "$3"
  sec "NOTE"
  if ((TLSON)); then row " ${DIM}$TLSMODE${N}"; else row " ${DIM}plain WebSocket on port $RPORT${N}"; fi
  row " ${DIM}Demo: menu actions are display-only here.${N}"
  bot; viewer
}
scr_links() {
  scr "CONNECTION LINKS"
  users_block vless VLESS "$N_VLESS"
  users_block vmess VMess "$N_VMESS"
  users_block trojan Trojan "$N_TROJAN"
  sec "VLESS NON-TLS (security=none)"; mk_link vless0 0; row " ${WHT}default${N}"; wrapc "$LK" "$ACC"
  sec "SHADOWSOCKS 2022"; kv "Cipher" "2022-blake3-aes-256-gcm"; mk_link ss 0; wrapc "$LK" "$ACC"
  sec "SSH-WS"; ssh_info_rows
  sec "ENDPOINT (client profile)"
  kv "Remote Proxy" "$REMOTE"; kv "SNI" "$SNI"; kv "Host" "$HOSTH"; kv "Security" "$TLSMODE"
  bot; viewer
}
scr_notls() {
  local p=80; scr "VLESS non-TLS (security=none)"
  case $RPORT in 443|8443|2053|2083|2087|2096) p=80 ;; *) p=$RPORT ;; esac
  row " ${DIM}Same VLESS users; link forces security=none (plain WebSocket).${N}"
  row " ${DIM}Nginx already upgrades WebSocket on port 80 for the VLESS path.${N}"
  users_block vless0 VLESS "$N_VLESS"
  sec "CLIENT FIELDS"; kv "Remote host" "$RHOST"; kv "Port in link" "$p"; kv "Host header" "$HOSTH"; kv "Path" "$WS_VLESS"
  bot; viewer
}
scr_status() {
  scr "LIVE STATUS"
  kv "address" "$SERVER_ADDR"
  kv "profile" "remote=$REMOTE sni=$SNI host=$HOSTH"
  badge "$S_NGX"; kvc "nginx" "$BD"; badge "$S_XRAY"; kvc "xray" "$BD"; badge "$S_SSH"; kvc "ssh-ws" "$BD"
  kv "cert" "${CDAYS}d remaining ($CKIND)"
  kv "paths" "vless=$WS_VLESS"; kv "" "vmess=$WS_VMESS"; kv "" "trojan=$WS_TROJAN"; kv "" "ss=$WS_SS"; kv "" "ssh=$WS_SSH"
  kv "accounts" "VLESS=$N_VLESS VMess=$N_VMESS Trojan=$N_TROJAN SS=$N_SS SSH=$N_SSH"
  kv "xray" "Xray $XV (go1.x linux/amd64)"
  bot; viewer
}
scr_cert() {
  local fp up c1 c2; fp=$CFP; up=${fp^^}; up=$(sed 's/../&:/g; s/:$//' <<< "$up"); c1=${up:0:48}; c2=${up:48}
  scr "TLS CERTIFICATE"
  row " ${DIM}Chain: certbot webroot → standalone → self-signed${N}"
  kv "Server addr" "$SERVER_ADDR"; badge "$CERT_OK"; kvc "Status" "$BD ${DIM}kind=$CKIND  days=${CDAYS}d${N}"
  sec "SHA-256 FINGERPRINT"; row " ${WHT}${fp:0:32}${N}"; row " ${WHT}${fp:32}${N}"
  row " ${DIM}${c1}${N}"; row " ${DIM}${c2}${N}"
  sec "ACTIONS"
  opt 1 "Show fingerprint / expiry"; opt 2 "Re-issue certificate (force renew / replace)"
  opt 3 "Force self-signed certificate"; opt 4 "Use my own certificate files"
  opt 5 "Test auto-renew (certbot renew --dry-run)"; opt 0 "Back"
  sec "AUTO-RENEW"; chk 1 "certbot.timer enabled"; chk 1 "deploy hook: copy cert + reload nginx"
  bot; viewer
}
scr_ss() {
  scr "SHADOWSOCKS 2022 · WebSocket"
  kv "Cipher" "2022-blake3-aes-256-gcm"
  opt 1 "Show links"; opt 2 "Regenerate key"; opt 3 "Change cipher"; opt 0 "Back"
  sec "CIPHERS"
  row " ${YEL}a)${N} 2022-blake3-aes-256-gcm       ${DIM}AES hardware${N}"
  row " ${YEL}b)${N} 2022-blake3-chacha20-poly1305 ${DIM}phones${N}"
  row " ${YEL}c)${N} 2022-blake3-aes-128-gcm       ${DIM}lightest${N}"
  sec "LINK (v2rayN, v2rayNG, NekoBox, Hiddify)"; mk_link ss 0; wrapc "$LK" "$ACC"
  bot; viewer
}
ssh_info_rows() {
  kv "Remote Proxy" "$REMOTE"; kv "TLS" "$TLSMODE"; ((TLSON)) && kv "TLS SNI" "$SNI"
  kv "WS Host" "$HOSTH"; kv "Port" "$RPORT"; kv "Path" "$WS_SSH"
  kv "Payload" ""; wrapc "GET $WS_SSH HTTP/1.1[crlf]Host: $HOSTH[crlf]Connection: Upgrade[crlf]User-Agent: [ua][crlf]Upgrade: websocket[crlf][crlf]" "$ACC"
  row " ${DIM}After HTTP 101 the stream is raw TCP to OpenSSH (not RFC 6455).${N}"
}
scr_ssh() {
  local i
  scr "SSH · WebSocket"
  opt 1 "Add user"; opt 2 "Delete user"; opt 3 "List users"; opt 4 "Connection info"; opt 0 "Back"
  sec "USERS ($N_SSH)"; row " ${DIM}USER                     EXPIRES${N}"
  for ((i = 0; i < N_SSH && i < 6; i++)); do
    printf -v SP '%-24s %s' "user$((i + 1))" "$(date -d "+$((30 - i * 4)) days" +%F 2>/dev/null || echo 2026-11-06)"; row " ${WHT}$SP${N}"
  done
  sec "CONNECTION INFO"; ssh_info_rows
  bot; viewer
}
scr_profile() {
  scr "CLIENT ENDPOINT (Remote / SNI / Host)"
  kv "Remote Proxy" "$REMOTE"; kv "TLS SNI" "$SNI"; kv "WS Host" "$HOSTH"; kv "Security" "$TLSMODE"
  sec "ACTIONS"
  opt 1 "Set Remote Proxy  (host:port — any domain/IP, any port)"
  opt 2 "Set SNI"; opt 3 "Set Host header"; opt 4 "Security: auto / tls / none"
  opt 5 "Reset to this server:443"; opt 0 "Back"
  sec "RULES"
  row " ${DIM}auto = TLS off on 80, 8080, 8880, 2052, 2082, 2086, 2095${N}"
  row " ${DIM}self-signed cert is pinned only when Remote = this server${N}"
  bot; viewer
}
scr_uninstall() {
  scr "UNINSTALL"
  row " ${RED}This removes CrimsonGate services and configuration.${N}"
  row " ${DIM}Nginx / UFW packages and firewall rules stay.${N}"
  sec "WOULD BE REMOVED"
  for s in "xray + crimson-ssh-ws services" "/usr/local/bin/xray, configs, geo assets" "Nginx crimson.conf + WS include + decoy site" \
           "SSH drop-in + tunnel-only group (users: ask)" "/etc/crimson, certbot deploy hook, logrotate"; do row " ${YEL}•${N} ${WHT}$s${N}"; done
  sec "DEMO"; row " ${GRN}Nothing is removed here — this UI is display-only.${N}"
  bot; viewer
}

STEP=0; STEPT=0; P=0; WN=0; FAILS=0
flow_head() { CL=(); header; sec "$1"; bot; printf '\e[H\e[J'; printf '%s\n' "${CL[@]}"; }
pm()   { printf '%*s%s\n' "$((M + 2))" '' "$1"; }
spin() { local f='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' i; for ((i = 0; i < ${2:-5}; i++)); do
           printf '\r%*s%s%s%s %s' "$((M + 4))" '' "$ACC" "${f:i%10:1}" "$N" "$1"; sleep 0.05; done; printf '\r\e[K'; }
stp()  { STEP=$((STEP + 1)); printf '\n%*s%s[%d/%d]%s %s%s%s\n' "$((M + 2))" '' "$ACC" "$STEP" "$STEPT" "$N" "$WHT$B" "$1" "$N"; }
sub()  { spin "$1" 5; printf '%*s%s✓%s %s\n' "$((M + 4))" '' "$GRN" "$N" "$1"; }
sc()   { printf '\n%*s%s▸ %s%s\n' "$((M + 2))" '' "$ACC" "$1" "$N"; }
o()    { printf '%*s%s✓%s %s\n' "$((M + 4))" '' "$GRN" "$N" "$1"; P=$((P + 1)); sleep 0.02; }
w()    { printf '%*s%s!%s %s\n' "$((M + 4))" '' "$YEL" "$N" "$1"; WN=$((WN + 1)); sleep 0.02; }
flow_wait() { printf '\n%*s%spress any key to go back%s' "$((M + 2))" '' "$DIM" "$N"; IFS= read -rsn1 _; printf '\e[H\e[J'; }
results() {
  printf '\n%*s%sResults:%s  %spass=%s%s  %sfail=%s%s  %swarn=%s%s\n' "$((M + 2))" '' "$WHT" "$N" "$GRN" "$P" "$N" "$RED" "$FAILS" "$N" "$YEL" "$WN" "$N"
  if ((FAILS == 0)); then pm "${GRN}✓${N} ${WHT}$1${N}"; else pm "${YEL}!${N} ${WHT}$FAILS check(s) failed.${N}"; fi
}

selftest_body() {
  local n s
  P=0; WN=0; FAILS=0
  sc "Binaries & paths"
  for n in "xray binary executable" "xray version runs" "python3 present" "nginx binary present" "openssl present" "jq present" "curl present" "sshd present" \
           "config dir exists" "paths.env exists" "TLS cert file" "TLS key file" "TLS certificate: ONLINE (not expired)" "Xray config exists" \
           "SSH bridge script + executable" "SSH hold script" "Nginx crimson conf + WS include" "decoy site root"; do o "$n"; done
  sc "Permissions"; o "TLS key mode is 600"; o "TLS cert mode is 644"; o "ENV file mode is 600"
  sc "Services"; for s in nginx xray crimson-ssh-ws; do o "service $s: active"; o "service $s: enabled"; done
  sc "Configuration"; o "Xray configuration: valid"; o "Nginx configuration: valid"; o "sshd configuration: valid"
  for n in vless-ws vmess-ws trojan-ws ss-ws; do o "Xray inbound $n: 127.0.0.1 · security:none"; done
  sc "WebSocket paths"; o "5/5 paths valid"; o "5/5 paths unique"
  sc "HTTP / TLS surface"; o "HTTP decoy (:80): 200"; o "HTTPS decoy (:443): 200"; o "Certificate valid >30 days"
  o "Arbitrary SNI+Host TLS: 101 + pinned fingerprint"
  sc "WebSocket upgrades"
  for n in VLESS VMess Trojan Shadowsocks; do o "$n WS :443 profile SNI/Host: 101"; o "$n WS :80 (no TLS): 101"; done
  sc "Profile, links & time"; o "Client profile syntax: OK"; o "Generated links: valid ($TLSMODE)"
  o "Shadowsocks cipher: 2022-blake3-aes-256-gcm (key length OK)"; o "Clock synchronization: OK (VMess)"
  sc "End-to-end data path"
  for n in vless vmess trojan ss; do spin "$n through Nginx → Xray → Internet" 4; o "$n data path (client -> Nginx -> Xray -> Internet): HTTP 204"; done
  sc "SSH-WS"; o "SSH bridge local: 101 + banner"; o "SSH-WS full path TLS+Nginx+bridge+sshd"; o "SSH tunnel-only ForceCommand policy"; o "SSH group tunnel-only exists"
  sc "Network exposure"; o "Internal ports: localhost only"
  for n in 10000 10001 10002 10003 9022; do o "Port $n listening on 127.0.0.1"; done
  o "Port 80 has a listener"; o "Port 443 has a listener"
  sc "Extras"; o "geoip.dat present"; o "geosite.dat present"; o "Remote Proxy ($REMOTE) reaches this server"
  o "Harden sysctl file present"; o "fail2ban active"; o "BBR congestion control active"
  results "ALL REQUIRED CHECKS PASSED."
}

deep_body() {
  local n
  P=0; WN=0; FAILS=0
  sc "Layer 1 — Binaries & packages"; for n in xray nginx python3 openssl jq curl ss ufw; do o "binary: $n"; done
  sc "Layer 2 — Files & permissions"; for n in paths.env server.crt server.key config.json crimson.conf ssh-ws-bridge.py ssh-hold.sh; do o "exists: $n"; done
  o "TLS key mode 600"; o "ENV mode 600"
  sc "Layer 3 — Services"; for n in nginx xray crimson-ssh-ws; do o "active: $n"; o "enabled: $n"; done
  sc "Layer 4 — Configuration validity"; o "Xray JSON valid"; o "Nginx -t OK"; o "sshd -t OK"
  sc "Layer 5 — Localhost-only binding (security)"; for n in 10000 10001 10002 10003 9022; do o "port $n on 127.0.0.1"; done; o "no internal ports public"
  sc "Layer 6 — HTTP/TLS surface"; o "HTTP :80 decoy 200"; o "HTTPS :443 decoy 200"; o "certificate valid >24h"
  sc "Layer 7 — WebSocket 101 upgrades"
  o "VLESS path $WS_VLESS → 101"; o "VMess path $WS_VMESS → 101"; o "Trojan path $WS_TROJAN → 101"; o "SS path $WS_SS → 101"; o "SSH path $WS_SSH → 101"
  sc "Layer 8 — End-to-end data path"
  for n in vless vmess trojan ss; do spin "$n end-to-end" 4; o "$n data path: HTTP 204"; done
  sc "Layer 9 — SSH tunnel-only policy"; o "ForceCommand hold present"; o "ForceCommand in drop-in"; o "group tunnel-only"
  sc "Layer 10 — Time & harden profile"; o "NTP/time OK"; o "sysctl harden present"; o "fail2ban active"; o "BBR active"
  results "ALL CRITICAL CHECKS PASSED — stack is healthy."
}

flow_install() {
  flow_head "INSTALL / REINSTALL"; STEP=0; STEPT=11
  stp "Installing packages"; sub "Updating package lists"; sub "Installing nginx, openssl, jq, ufw, chrony, certbot ..."; sub "verify: required packages present"
  stp "Checking the system"; sub "Nginx default_server conflict check"; sub "Ports 80/443 and internal ports are free"; sub "System clock synchronized (VMess needs it)"
  stp "Opening firewall ports 80/443 (required before ACME)"; sub "UFW allow SSH + 80 + 443"; sub "iptables INPUT accept 80/443"
  stp "Issuing TLS certificate (certbot webroot → standalone → self-signed)"; sub "certbot webroot (Let's Encrypt)"; sub "auto-renew: certbot.timer + deploy hook"; sub "verify: TLS certificate and key match"
  stp "Installing Xray-core"; sub "Downloading Xray zip + .dgst"; sub "Xray SHA256 verified"; sub "Xray SHA512 verified"; sub "Xray binary smoke test OK"
  stp "Configuring Xray"; sub "Service user xray + hardened systemd unit"; sub "Config: vless / vmess / trojan / ss on 127.0.0.1"; sub "verify: Xray running with localhost inbounds"
  stp "Configuring the SSH tunnel and bridge"; sub "sshd drop-in + group tunnel-only"; sub "nftables private-destination guard"; sub "verify: SSH-WS bridge and tunnel-only policy"
  stp "Configuring Nginx (ports 80 and 443)"; sub "Opaque random WebSocket paths"; sub "verify: Nginx active and config valid"
  stp "Configuring the firewall"; sub "UFW defaults applied"; sub "verify: UFW active"
  stp "Applying harden profile"; sub "BBR / sysctl / permissions / fail2ban / NTP"
  stp "Full self-test"; selftest_body
  pm ""; pm "${GRN}✓${N} ${WHT}CrimsonGate $VERSION installed and verified.${N}  ${DIM}(demo — nothing was changed)${N}"
  flow_wait
}
flow_restart() {
  flow_head "RESTART SERVICES"; STEP=0; STEPT=4
  stp "Time sync"; sub "chrony / timedatectl"
  stp "sshd + SSH bridge"; sub "setup_ssh: drop-in re-applied"; sub "crimson-ssh-ws restarted"
  stp "Xray + Nginx"; sub "xray restarted"; sub "nginx restarted"
  stp "Self-test"; selftest_body; flow_wait
}
flow_selftest() { flow_head "SELF-TEST · CrimsonGate $VERSION"; selftest_body; flow_wait; }
flow_deep() {
  CL=(); header; sec "DEEP AUDIT v$VERSION"; bot; printf '\e[H\e[J'; printf '%s\n' "${CL[@]}"
  deep_body; flow_wait
}
flow_update() {
  flow_head "UPDATE XRAY-CORE"; STEP=0; STEPT=5
  kvc_plain() { pm "${DIM}$1${N} ${WHT}$2${N}"; }
  kvc_plain "installed :" "$XV"; kvc_plain "latest    :" "$XV  ($XU)"
  stp "Backup"; sub "Xray binary + geo assets"
  stp "Download + verify"; sub "Xray zip + .dgst"; sub "SHA256 / SHA512 match"; sub "unzip integrity + smoke test"
  stp "Compatibility"; sub "new Xray accepts the current config"
  stp "Install + restart"; sub "binary replaced atomically"; sub "xray active"
  stp "Rollback guard"; sub "armed — would restore the old binary on failure"
  pm ""; pm "${GRN}✓${N} ${WHT}Xray updated.${N}  ${DIM}(demo)${N}"; flow_wait
}
flow_repair() {
  flow_head "REPAIR"; STEP=0; STEPT=6
  stp "Xray service"; sub "setup_xray_env"; sub "Shadowsocks cipher check / legacy upgrade"
  stp "SSH"; sub "setup_ssh (drop-in, group, ForceCommand)"; sub "setup_bridge (bridge + unit)"
  stp "Nginx"; sub "write_nginx_full (+ nginx -t, reload)"
  stp "Firewall"; sub "setup_firewall"
  stp "Services"; sub "xray restarted"; sub "crimson-ssh-ws active"
  stp "Self-test"; selftest_body; flow_wait
}
flow_harden() {
  flow_head "HARDEN"; STEP=0; STEPT=5
  stp "Hardening: kernel network (BBR + buffers)"; sub "sysctl profile written"; sub "BBR enabled"
  stp "Hardening: file permissions"; sub "key 600 · cert 644 · config 640 · env 600"
  stp "Hardening: fail2ban (SSH)"; sub "jail sshd: maxretry 5, bantime 1h"
  stp "Hardening: firewall refresh"; sub "UFW defaults applied (deny incoming, allow SSH/80/443)"
  stp "Hardening: time sync (required for VMess)"; sub "Clock synchronized"
  pm ""; pm "${GRN}✓${N} ${WHT}Harden complete. Re-run: crimson test${N}  ${DIM}(demo)${N}"; flow_wait
}

open_item() {
  case $1 in
    0) flow_install ;;   1) scr_links ;;    2) scr_cert ;;     3) scr_status ;;
    4) scr_proto vless VLESS "$N_VLESS" ;;  5) scr_notls ;;
    6) scr_proto vmess VMess "$N_VMESS" ;;  7) scr_proto trojan Trojan "$N_TROJAN" ;;
    8) scr_ss ;;  9) scr_ssh ;;  10) scr_profile ;;  11) flow_restart ;;
    12) flow_selftest ;;  13) flow_deep ;;  14) flow_update ;;  15) flow_repair ;;
    16) flow_harden ;;  17) scr_uninstall ;;  18) exit 0 ;;
  esac
  printf '\e[H\e[J'
}

cleanup() { printf '\e[?25h\e[?1049l%s' "$N"; }
trap cleanup EXIT
trap 'exit 130' INT TERM
trap setdim WINCH
printf '\e[?1049h\e[?25l'
setdim; load_data

jump() {
  local n
  [[ $BUF == +([0-9]) ]] || return
  n=$((10#$BUF))
  if ((n == 0)); then SELI=18
  elif ((n >= 1 && n <= 18)); then SELI=$((n - 1))
  else BUF=${BUF: -1}; jump; fi
}

while :; do
  if [ "$VIEW" = menu ]; then build_menu; AUTO=1; else build_dash; AUTO=0; fi
  paint
  getkey; rc=$?
  ((rc == 1)) && break; ((rc == 2)) && continue
  case $K in
    $'\e[A') if [ "$VIEW" = menu ]; then ((SELI >= 2)) && SELI=$((SELI - 2)); else OFF=$((OFF - 1)); fi; BUF="" ;;
    $'\e[B') if [ "$VIEW" = menu ]; then ((SELI + 2 <= 18)) && SELI=$((SELI + 2)); else OFF=$((OFF + 1)); fi; BUF="" ;;
    $'\e[C') [ "$VIEW" = menu ] && ((SELI < 18)) && SELI=$((SELI + 1)); BUF="" ;;
    $'\e[D') [ "$VIEW" = menu ] && ((SELI > 0)) && SELI=$((SELI - 1)); BUF="" ;;
    $'\e[5') OFF=$((OFF - 10)) ;;
    $'\e[6') OFF=$((OFF + 10)) ;;
    $'\t'|i|I) if [ "$VIEW" = menu ]; then VIEW=dash; else VIEW=menu; fi; OFF=0; BUF="" ;;
    '') if [ "$VIEW" = menu ]; then open_item "$SELI"; BUF=""; fi ;;
    [0-9]) if [ "$VIEW" = menu ]; then BUF+=$K; jump; fi ;;
    k) [ "$VIEW" = menu ] && ((SELI >= 2)) && SELI=$((SELI - 2)); BUF="" ;;
    j) [ "$VIEW" = menu ] && ((SELI + 2 <= 18)) && SELI=$((SELI + 2)); BUF="" ;;
    h) [ "$VIEW" = menu ] && ((SELI > 0)) && SELI=$((SELI - 1)); BUF="" ;;
    l) [ "$VIEW" = menu ] && ((SELI < 18)) && SELI=$((SELI + 1)); BUF="" ;;
    r|R) load_data; BUF="" ;;
    q|Q) exit 0 ;;
    $'\x7f') BUF="" ;;
  esac
done
