#!/usr/bin/env bash
# MAX RustDesk 一体管理器
# 服务端 HBBS/HBBR + Linux 客户端安装、配置、运行与诊断
# 用法: bash <(curl -fsSL https://dl.unvmax.com/linux/scripts/rustdesk/latest.sh)
set -Eeuo pipefail

ACTION="${1:-menu}"
RUSTDESK_REPO="rustdesk/rustdesk-server"
RUSTDESK_CLIENT_REPO="rustdesk/rustdesk"
INSTALL_DIR="/opt/rustdesk-server"
BIN_DIR="/usr/local/bin"
HBBS_BIN="${BIN_DIR}/hbbs"; HBBR_BIN="${BIN_DIR}/hbbr"
SERVER_DIR="/etc/rustdesk-server"; SERVER_ENV="${SERVER_DIR}/server.env"
CLIENT_DIR="/etc/rustdesk-client"; CLIENT_ENV="${CLIENT_DIR}/client.env"
HBBS_SERVICE="/etc/systemd/system/hbbs.service"; HBBR_SERVICE="/etc/systemd/system/hbbr.service"
BACKUP_ROOT="/root/max-backup/rustdesk"; TMP_DIR=""

BC='\033[38;5;220m'; BG='\033[38;5;220m'; Y='\033[1;33m'; R='\033[0;31m'
BW='\033[38;5;255m'; W='\033[38;5;250m'; GR='\033[38;5;242m'; N='\033[0m'
red(){ printf '%b%s%b\n' "$R" "$*" "$N"; }; green(){ printf '%b%s%b\n' "$BG" "$*" "$N"; }
yellow(){ printf '%b%s%b\n' "$Y" "$*" "$N"; }; blue(){ printf '%b%s%b\n' "$BC" "$*" "$N"; }
die(){ red "错误：$*"; exit 1; }; cleanup(){ if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then rm -rf "$TMP_DIR"; fi; return 0; }; trap cleanup EXIT
need_root(){ [[ $EUID -eq 0 ]] || die "请使用 root 运行。"; }; pause_menu(){ echo; echo -ne "  ${GR}按 Enter 返回菜单...${N}"; read -r _ || true; }
header(){ clear 2>/dev/null || true; echo; echo -e "  ${BC}┌──────────────────────────────────────────────────┐${N}"; echo -e "  ${BG}│     🖥️  MAX RustDesk 一体管理器${N}"; echo -e "  ${BC}└──────────────────────────────────────────────────┘${N}"; echo; }
section(){ echo -e "  ${BC}── $1 ───────────────────────────────────${N}"; }
item(){ printf '  %b%-4s%b  %b%-12s%b %b%s%b\n' "$BC" "$1" "$N" "$BW" "$2" "$N" "$GR" "$3" "$N"; }
arch(){ case "$(uname -m)" in x86_64|amd64) echo x86_64;; aarch64|arm64) echo aarch64;; *) die "暂不支持架构：$(uname -m)";; esac; }
backup(){
  local label="$1" d
  d="$BACKUP_ROOT/$label-$(date +%Y%m%d-%H%M%S)"; install -d -m 700 "$d"
  [[ -d "$SERVER_DIR" ]] && cp -a "$SERVER_DIR" "$d/"
  [[ -d "$CLIENT_DIR" ]] && cp -a "$CLIENT_DIR" "$d/"
  for f in "$HBBS_SERVICE" "$HBBR_SERVICE" /etc/systemd/system/rustdesk-hbbs.service /etc/systemd/system/rustdesk-hbbr.service /id_ed25519 /id_ed25519.pub /db_v2.sqlite3; do
    [[ -f "$f" ]] && cp -a "$f" "$d/"
  done
  echo "$d"
}
latest_server(){ curl -fsSL --connect-timeout 12 "https://api.github.com/repos/$RUSTDESK_REPO/releases/latest" | sed -nE 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/p' | head -1; }
latest_client(){ curl -fsSL --connect-timeout 12 "https://api.github.com/repos/$RUSTDESK_CLIENT_REPO/releases/latest" | sed -nE 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/p' | head -1; }
download_server(){
  need_root; local ver a asset url zip; ver="$(latest_server)"; [[ -n "$ver" ]] || die "获取 RustDesk Server 版本失败。"; a="$(arch)"; case "$a" in x86_64) asset='rustdesk-server-linux-amd64.zip';; aarch64) asset='rustdesk-server-linux-arm64v8.zip';; esac; TMP_DIR="$(mktemp -d)"; zip="$TMP_DIR/rustdesk-server.zip"; url="https://github.com/$RUSTDESK_REPO/releases/download/$ver/$asset"
  blue "下载 RustDesk Server $ver（$a）..."; curl -fL --retry 2 --connect-timeout 15 -o "$zip" "$url" || die "下载失败：$url"; command -v unzip >/dev/null 2>&1 || { apt-get update -qq && apt-get install -y -qq unzip; }; unzip -q "$zip" -d "$TMP_DIR/unpack"; install -d -m 755 "$INSTALL_DIR"; find "$TMP_DIR/unpack" -type f \( -name hbbs -o -name hbbr \) -exec install -m 755 {} "$INSTALL_DIR/" \; ; [[ -x "$INSTALL_DIR/hbbs" && -x "$INSTALL_DIR/hbbr" ]] || die "压缩包中未找到 hbbs/hbbr。"; install -m 755 "$INSTALL_DIR/hbbs" "$HBBS_BIN"; install -m 755 "$INSTALL_DIR/hbbr" "$HBBR_BIN"; green "RustDesk Server 已安装。"
}
write_services(){ cat >"$HBBS_SERVICE" <<EOF
[Unit]
Description=RustDesk ID/Rendezvous Server (hbbs)
After=network-online.target
Wants=network-online.target
[Service]
EnvironmentFile=$SERVER_ENV
WorkingDirectory=$SERVER_DIR
ExecStart=$HBBS_BIN -r \${RUSTDESK_RELAY}
Restart=always
RestartSec=5
LimitNOFILE=1048576
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF
cat >"$HBBR_SERVICE" <<EOF
[Unit]
Description=RustDesk Relay Server (hbbr)
After=network-online.target hbbs.service
Wants=network-online.target
[Service]
WorkingDirectory=$SERVER_DIR
ExecStart=$HBBR_BIN
Restart=always
RestartSec=5
LimitNOFILE=1048576
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
EOF
}
open_ports(){ command -v ufw >/dev/null 2>&1 || return; for p in 21115/tcp 21116/tcp 21116/udp 21117/tcp 21118/tcp 21119/tcp; do ufw allow "$p" comment 'MAX-RustDesk' >/dev/null 2>&1 || true; done; }
install_server(){
  need_root; header; section "安装 RustDesk 服务端"; local public relay; read -rp '客户端连接域名/IP（默认本机公网 IP）: ' public; public="${public:-$(curl -4fsS --connect-timeout 6 https://api.ipify.org || hostname -I | awk '{print $1}')}"; read -rp "中继地址 [${public}:21117]: " relay; relay="${relay:-$public:21117}"; backup server-install >/dev/null; download_server; install -d -m 700 "$SERVER_DIR"; cat >"$SERVER_ENV" <<EOF
RUSTDESK_RELAY=$relay
RUSTDESK_PUBLIC=$public
EOF
  write_services; systemctl daemon-reload; systemctl enable --now hbbs hbbr; open_ports; sleep 1; systemctl is-active --quiet hbbs && systemctl is-active --quiet hbbr || { journalctl -u hbbs -u hbbr -n 40 --no-pager; die '服务启动失败。'; }; green 'RustDesk 服务端已启动。'; echo -e "  ${GR}客户端 ID Server：${W}$public${N}"; echo -e "  ${GR}Relay Server：${W}$relay${N}"; echo -e "  ${Y}请在客户端设置中填入 ID/Relay Server，并导入下方公钥。${N}"; server_key; pause_menu
}
unit_exists(){ systemctl cat "$1" >/dev/null 2>&1; }
server_mode(){
  if unit_exists rustdesk-hbbs || unit_exists rustdesk-hbbr; then echo legacy; else echo managed; fi
}
hbbs_unit(){ [[ "$(server_mode)" == legacy ]] && echo rustdesk-hbbs || echo hbbs; }
hbbr_unit(){ [[ "$(server_mode)" == legacy ]] && echo rustdesk-hbbr || echo hbbr; }
server_key_file(){
  if [[ "$(server_mode)" == legacy ]]; then echo /id_ed25519.pub; else echo "$SERVER_DIR/id_ed25519.pub"; fi
}
server_service_summary(){
  local service="$1"
  if ! unit_exists "$service"; then printf '%b○ 未安装%b' "$GR" "$N"
  elif systemctl is-active --quiet "$service"; then printf '%b● 运行中%b' "$BG" "$N"
  else printf '%b● 已停止%b' "$Y" "$N"; fi
}
server_autostart_summary(){
  local service="$1"
  if systemctl is-enabled --quiet "$service" 2>/dev/null; then printf '%b✅ 已启用%b' "$BG" "$N"
  else printf '%b❌ 未启用%b' "$GR" "$N"; fi
}
show_server_status_summary(){
  local version public relay key_state mode hu ru key
  mode="$(server_mode)"; hu="$(hbbs_unit)"; ru="$(hbbr_unit)"; key="$(server_key_file)"
  version="$($HBBS_BIN -V 2>/dev/null || $HBBS_BIN --version 2>/dev/null || true)"; version="${version%%$'\n'*}"; version="${version:-未安装}"
  public="$(sed -nE 's/^RUSTDESK_PUBLIC=(.*)/\1/p' "$SERVER_ENV" 2>/dev/null | head -1 || true)"
  relay="$(sed -nE 's/^RUSTDESK_RELAY=(.*)/\1/p' "$SERVER_ENV" 2>/dev/null | head -1 || true)"
  [[ -f "$key" ]] && key_state="${BG}✅ 已生成${N}" || key_state="${GR}❌ 未生成${N}"
  echo -e "  ${BC}┌── 服务端实时状态 ────────────────────────────────┐${N}"
  echo -e "  ${GR}│${N}  核心 ${W}${version}${N}   ${GR}模式：${W}$([[ "$mode" == legacy ]] && echo 原有服务兼容 || echo MAX 托管)${N}"
  echo -e "  ${GR}│${N}  HBBS ID 服务   $(server_service_summary "$hu")  $(server_autostart_summary "$hu")"
  echo -e "  ${GR}│${N}  HBBR 中继服务  $(server_service_summary "$ru")  $(server_autostart_summary "$ru")"
  [[ -n "$public" ]] && echo -e "  ${GR}│${N}  ID Server ${W}${public}${N}   Relay ${W}${relay:-未配置}${N}"
  echo -e "  ${GR}│${N}  服务端公钥  ${key_state}"
  echo -e "  ${BC}└──────────────────────────────────────────────────┘${N}"
  echo
}
server_key(){ local key; key="$(server_key_file)"; [[ -f "$key" ]] || { yellow '尚未生成服务端公钥。'; return; }; echo -e "  ${BC}公钥：${N}"; cat "$key"; }
server_info(){ local mode hu ru; header; section "RustDesk 服务端状态"; mode="$(server_mode)"; hu="$(hbbs_unit)"; ru="$(hbbr_unit)"; if ! unit_exists "$hu" && ! unit_exists "$ru"; then yellow '未安装服务端。'; pause_menu; return; fi; echo -e "  ${GR}运行模式：${W}$([[ "$mode" == legacy ]] && echo 原有服务兼容 || echo MAX 托管)${N}"; printf '  hbbs：'; systemctl is-active "$hu" 2>/dev/null || true; printf '  hbbr：'; systemctl is-active "$ru" 2>/dev/null || true; if [[ -f "$SERVER_ENV" ]]; then echo -e "  ${GR}连接配置：${N}"; sed 's/^/    /' "$SERVER_ENV"; else echo -e "  ${GR}原服务未使用独立环境文件，保留原启动参数与根目录密钥。${N}"; fi; server_key; echo; ss -tulnp | grep -E 'hbbs|hbbr' || true; pause_menu; }
server_restart(){ need_root; local hu ru; hu="$(hbbs_unit)"; ru="$(hbbr_unit)"; unit_exists "$hu" || die '未安装服务端。'; systemctl restart "$hu" "$ru"; systemctl is-active --quiet "$hu" && systemctl is-active --quiet "$ru" && green '服务端已重启。'; pause_menu; }
server_logs(){ local hu ru; hu="$(hbbs_unit)"; ru="$(hbbr_unit)"; header; journalctl -u "$hu" -u "$ru" -n 100 --no-pager || true; pause_menu; }
server_edit(){ need_root; [[ "$(server_mode)" != legacy ]] || die '原有服务未使用 server.env；为避免改变现有链路，暂不提供配置编辑。'; [[ -f "$SERVER_ENV" ]] || die '未安装服务端。'; backup server-edit >/dev/null; ${EDITOR:-nano} "$SERVER_ENV"; systemctl restart hbbs hbbr; green '配置已保存并重启。'; pause_menu; }
install_client(){
  need_root; header; section "安装 Linux RustDesk 客户端"; local a deb url ver; a="$(arch)"; [[ "$a" == x86_64 ]] || die '当前自动安装仅提供 x86_64 Debian/Ubuntu 客户端，请使用官方客户端包。'; command -v apt-get >/dev/null || die '当前自动安装仅支持 Debian/Ubuntu。'; ver="$(latest_client)"; [[ -n "$ver" ]] || die '获取 RustDesk 客户端最新版本失败。'; TMP_DIR="$(mktemp -d)"; deb="$TMP_DIR/rustdesk.deb"; url="https://github.com/$RUSTDESK_CLIENT_REPO/releases/download/${ver}/rustdesk-${ver}-x86_64.deb"; blue "下载 RustDesk Linux 客户端 v${ver}..."; curl -fL --retry 2 --connect-timeout 15 -o "$deb" "$url" || die '下载失败；可从 RustDesk 官方 Releases 手工安装。'; apt-get update -qq; apt-get install -y -qq "$deb" || apt-get -f install -y; command -v rustdesk >/dev/null || die '客户端安装未完成。'; install -d -m 700 "$CLIENT_DIR"; read -rp 'ID Server（域名/IP）: ' id; read -rp 'Relay Server（域名/IP:端口）: ' relay; read -rp '服务器公钥: ' key; [[ -n "$id" && -n "$relay" && -n "$key" ]] || die 'ID Server、Relay Server 和公钥不能为空。'; cat >"$CLIENT_ENV" <<EOF
RUSTDESK_ID_SERVER=$id
RUSTDESK_RELAY_SERVER=$relay
RUSTDESK_KEY=$key
EOF
  green 'Linux RustDesk 客户端已安装。'; yellow '桌面客户端请打开 RustDesk → 设置 → 网络，填入上述 ID/Relay Server 和 Key。'; pause_menu
}
client_info(){ header; section "Linux 客户端状态"; command -v rustdesk >/dev/null && green "已安装：$(rustdesk --version 2>/dev/null || echo RustDesk)" || yellow '未检测到 RustDesk 客户端。'; [[ -f "$CLIENT_ENV" ]] && { echo -e "  ${GR}保存的连接配置：${N}"; sed 's/^/    /' "$CLIENT_ENV"; }; pause_menu; }
server_uninstall(){ need_root; local mode hu ru; mode="$(server_mode)"; hu="$(hbbs_unit)"; ru="$(hbbr_unit)"; read -rp '确认卸载 RustDesk 服务端（保留完整备份）？输入 YES: ' ok; [[ "$ok" == YES ]] || return; backup server-uninstall >/dev/null; systemctl disable --now "$hu" "$ru" 2>/dev/null || true; if [[ "$mode" == legacy ]]; then rm -f /etc/systemd/system/rustdesk-hbbs.service /etc/systemd/system/rustdesk-hbbr.service /id_ed25519 /id_ed25519.pub /db_v2.sqlite3 /db_v2.sqlite3-wal /db_v2.sqlite3-shm; else rm -f "$HBBS_SERVICE" "$HBBR_SERVICE"; rm -rf "$SERVER_DIR" "$INSTALL_DIR"; fi; systemctl daemon-reload; green 'RustDesk 服务端已卸载，完整备份仍在。'; pause_menu; }
client_uninstall(){ need_root; read -rp '确认卸载 RustDesk 客户端配置？输入 YES: ' ok; [[ "$ok" == YES ]] || return; backup client-uninstall >/dev/null; rm -rf "$CLIENT_DIR"; command -v apt-get >/dev/null && apt-get remove -y rustdesk >/dev/null 2>&1 || true; green 'RustDesk 客户端及本脚本配置已清理。'; pause_menu; }
server_menu(){ while true; do header; show_server_status_summary; section 'HBBS + HBBR 服务端'; item S1 '安装/重装' '下载服务端、写 systemd、放行标准端口'; item S2 '查看状态' '服务、公钥、端口与连接配置'; item S3 '重启服务' '重启当前 HBBS + HBBR 服务'; item S4 '查看日志' '查看当前服务最近 100 行日志'; item S5 '编辑连接配置' '原有服务保持只读，MAX 托管可编辑'; item S0 '卸载服务端' '完整备份后卸载当前服务'; item 0 '返回' '返回主菜单'; echo; read -rp '  输入编号 → ' c || c=0; case "${c^^}" in S1) install_server;;S2) server_info;;S3) server_restart;;S4) server_logs;;S5) server_edit;;S0) server_uninstall;;0) return;;*) yellow '无效选项。'; sleep 1;;esac; done; }
client_menu(){ while true; do header; section 'Linux RustDesk 客户端'; item C1 '安装客户端' '下载官方 Debian/Ubuntu 客户端'; item C2 '查看配置' '查看已保存的 ID/Relay/Key'; item C0 '卸载客户端' '清理客户端与本脚本配置'; item 0 '返回' '返回主菜单'; echo; read -rp '  输入编号 → ' c || c=0; case "${c^^}" in C1) install_client;;C2) client_info;;C0) client_uninstall;;0) return;;*) yellow '无效选项。'; sleep 1;;esac; done; }
main(){ while true; do header; show_server_status_summary; section 'RustDesk 服务端 + Linux 客户端一体管理'; item 1 '服务端管理' 'HBBS/HBBR、端口、公钥和运行日志'; item 2 '客户端管理' 'Linux 客户端安装及服务器配置保存'; item 0 '退出' '结束程序'; echo; read -rp '  输入编号 → ' c || c=0; case "$c" in 1) server_menu;;2) client_menu;;0) exit 0;;*) yellow '无效选项。'; sleep 1;;esac; done; }
case "$ACTION" in menu|"") main;; server) server_menu;; client) client_menu;; *) die "用法：$0 [menu|server|client]";; esac
