#!/usr/bin/env bash
# ============================================================
# FRP 一体化管理脚本（frps 服务端 + frpc 客户端 + Web 面板）
#
# 适用: 任意 systemd Linux 发行版
#   Debian / Ubuntu / Armbian / RHEL(CentOS/Rocky/AlmaLinux) /
#   openSUSE / Arch 等（自动识别 apt/dnf/yum/pacman/zypper/apk）
# 架构: amd64 / arm64 / arm(软浮点) / arm_hf(硬浮点 armv7) / riscv64 /
#   loong64 / mips / mipsle / mips64 / mips64le（自动识别）
#   注: frp 新版已不再发布 linux 386 包，32 位 x86 会明确提示
#
# 用法:
#   sudo bash mcfrpmanager              # 交互菜单
#   sudo bash mcfrpmanager frps         # 直接安装服务端
#   sudo bash mcfrpmanager frpc         # 直接安装客户端 + 面板
#   子命令: frps frpc update status restart logs password backup uninstall
#
# 远程一键安装（已部署在 frp.mc06.net，主页命令自动跟随访问域名）:
#   curl -fsSL https://frp.mc06.net/mcfrpmanager -o mcfrpmanager && \
#     sudo env FRP_FILE_BASE=https://frp.mc06.net/files bash mcfrpmanager
#
# 文件获取优先级（全部可本地化，彻底脱离 GitHub）:
#   1. 脚本同目录的 files/ 子目录（推荐，与 FILE_BASE 结构完全一致）:
#      frpc-manager.py
#      frp/frp_sha256_checksums.txt      <- 官方 SHA256 校验表
#      frp/frp_<版本>_linux_<架构>.tar.gz <- amd64/arm64/arm/arm_hf/
#                                          riscv64/loong64/mips/mipsle/
#                                          mips64/mips64le
#      python/SHA256SUMS                 <- 独立 Python 官方校验表
#      python/cpython-*.tar.gz           <- 独立 Python（系统无 python3 时用，
#                                          覆盖 amd64/arm64/arm_hf/arm/riscv64）
#   2. FILE_BASE（你自己的 HTTP 文件服务器，见下方配置区）
#   3. GitHub 官方 + 加速镜像（最后回退）
#   下载的 frp 包会自动用 frp/frp_sha256_checksums.txt 校验（若存在）
#   Python 优先用系统自带的 python3，仅系统没有时才安装独立版
# ============================================================

GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; PLAIN='\033[0m'
info()  { echo -e "${GREEN}[信息]${PLAIN} $1"; }
warn()  { echo -e "${YELLOW}[警告]${PLAIN} $1"; }
error() { echo -e "${RED}[错误]${PLAIN} $1"; exit 1; }

# ============================================================
# 文件源配置（按需修改）
# ------------------------------------------------------------
# FILE_BASE 已预置为 https://frp.mc06.net/files（自有文件服务器），
# 所有机器优先从这里下载文件，不依赖 GitHub。
# 换自己的服务器时改成对应地址，例如:
#   FILE_BASE="http://你的服务器IP或域名/frp/files"
# 该目录里需放置（与本地 files/ 子目录内容完全相同）:
#   frpc-manager.py
#   frp/frp_sha256_checksums.txt
#   frp/frp_<版本>_linux_<架构>.tar.gz（各架构按需）
#   python/SHA256SUMS + python/cpython-*.tar.gz（独立 Python，按需）
# 留空 = 不使用自有服务器（本地文件 -> GitHub 回退）
# 也可用环境变量临时指定: sudo env FRP_FILE_BASE=http://x/y bash mcfrpmanager
# ============================================================
FILE_BASE="${FRP_FILE_BASE:-https://frp.mc06.net/files}"
FRP_VER="0.61.1"

# 独立 Python（python-build-standalone 项目，静态构建无依赖）
# 仅在系统没有 python3 时才会安装，装到 LOCAL_PY_DIR
PY_VER="3.11.18"
PY_TAG="20261026"
PY_PREFIX="cpython-${PY_VER}+${PY_TAG}"
LOCAL_PY_DIR="/opt/frp-python"

FRPS_BIN="/usr/local/bin/frps"
FRPC_BIN="/usr/local/bin/frpc"
FRP_DIR="/etc/frp"
FRPS_CONF="${FRP_DIR}/frps.toml"
FRPC_CONF="${FRP_DIR}/frpc.toml"
PANEL_DIR="/opt/frpc-manager"
PANEL_SCRIPT="${PANEL_DIR}/frpc-manager.py"
BACKUP_DIR="${PANEL_DIR}/backups"
MAX_BACKUPS=20

# GitHub 官方 + 加速镜像（最后回退用）
GH_MIRRORS=(
    "https://github.com/fatedier/frp/releases/download"
    "https://ghproxy.net/https://github.com/fatedier/frp/releases/download"
    "https://ghfast.top/https://github.com/fatedier/frp/releases/download"
    "https://gh-proxy.com/https://github.com/fatedier/frp/releases/download"
)

# 独立 Python 下载地址（python-build-standalone，最后回退用）
PY_MIRRORS=(
    "https://github.com/astral-sh/python-build-standalone/releases/download/${PY_TAG}"
    "https://ghproxy.net/https://github.com/astral-sh/python-build-standalone/releases/download/${PY_TAG}"
    "https://gh-proxy.com/https://github.com/astral-sh/python-build-standalone/releases/download/${PY_TAG}"
)

# ---------- 通用检查 ----------
[ "$(id -u)" -eq 0 ] || error "请使用 root 权限运行: sudo bash mcfrpmanager"
command -v systemctl >/dev/null 2>&1 || error "未找到 systemctl，需要支持 systemd 的系统"
[ -d /run/systemd/system ] || error "systemd 未在运行（容器/WSL 环境常见），请在真实系统上执行"

SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
[ -n "$SCRIPT_DIR" ] || SCRIPT_DIR="$(pwd)"

# ---------- 包管理器识别 ----------
PKG_MGR="none"
if command -v apt-get >/dev/null 2>&1; then PKG_MGR="apt"
elif command -v dnf >/dev/null 2>&1; then PKG_MGR="dnf"
elif command -v yum >/dev/null 2>&1; then PKG_MGR="yum"
elif command -v pacman >/dev/null 2>&1; then PKG_MGR="pacman"
elif command -v zypper >/dev/null 2>&1; then PKG_MGR="zypper"
elif command -v apk >/dev/null 2>&1; then PKG_MGR="apk"
fi

pkg_install() {
    case "$PKG_MGR" in
        apt)    apt-get update -y >/dev/null 2>&1; apt-get install -y "$1" ;;
        dnf)    dnf install -y "$1" ;;
        yum)    yum install -y "$1" ;;
        pacman) pacman -Sy --noconfirm "$1" ;;
        zypper) zypper --non-interactive install "$1" ;;
        apk)    apk add "$1" ;;
        *)      return 1 ;;
    esac
}

# ---------- 基础工具 ----------
download() {
    # $1 URL  $2 目标文件
    if command -v curl >/dev/null 2>&1; then
        curl -fsSL --connect-timeout 15 --retry 2 -o "$2" "$1"
    elif command -v wget >/dev/null 2>&1; then
        wget -q --timeout=15 -O "$2" "$1"
    else
        return 1
    fi
}

get_arch() {
    # 返回值与 frp 官方 release 文件名中的架构标识一致
    # 官方 32 位 ARM 包有两种: linux_arm(GOARM=5 软浮点) / linux_arm_hf(GOARM=7 硬浮点)
    case "$(uname -m)" in
        x86_64|amd64)              echo "amd64" ;;
        aarch64|arm64)             echo "arm64" ;;
        armv7l|armv7|armhf)        echo "arm_hf" ;;
        armv6l|armv5tel|armv5*|arm) echo "arm" ;;
        riscv64)                   echo "riscv64" ;;
        loongarch64)               echo "loong64" ;;
        mips)                      echo "mips" ;;
        mipsle)                    echo "mipsle" ;;
        mips64)                    echo "mips64" ;;
        mips64le)                  echo "mips64le" ;;
        i386|i686)                 echo "386" ;;
        *)                         return 1 ;;
    esac
}

rand_token() {
    head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n'
}

port_in_use() {
    if command -v ss >/dev/null 2>&1; then
        ss -tln 2>/dev/null | awk '{print $4}' | grep -q ":$1$"
    elif command -v netstat >/dev/null 2>&1; then
        netstat -tln 2>/dev/null | awk '{print $4}' | grep -q ":$1$"
    else
        return 1
    fi
}

# 交互提问（回车取默认值；防 EOF）
ask() {
    # $1 提示  $2 默认值
    local a
    read -r -p "$1 [默认: $2]: " a || a=""
    if [ -n "$a" ]; then echo "$a"; else echo "$2"; fi
}

ask_yn() {
    # $1 提示（含 [y/N]）  $2 默认 y/n
    local a
    read -r -p "$1 " a || a=""
    if [ -z "$a" ]; then a="$2"; fi
    case "$a" in
        [yY]|[yY][eE][sS]) return 0 ;;
        *) return 1 ;;
    esac
}

# ---------- 组件探测 ----------
unit_exists() {
    systemctl list-unit-files "$1.service" 2>/dev/null | grep -q "$1"
}

svc_active() { systemctl is-active "$1" 2>/dev/null || true; }

panel_installed() { [ -f "$PANEL_SCRIPT" ]; }

get_panel_port() {
    local py
    py=$(get_py_bin 2>/dev/null) || { echo 8999; return; }
    if [ -f "${PANEL_DIR}/panel-data.json" ]; then
        "$py" -c "import json; print(json.load(open('${PANEL_DIR}/panel-data.json', encoding='utf-8')).get('port') or 8999)" 2>/dev/null || echo 8999
    else
        echo 8999
    fi
}

get_ip() {
    local ip
    ip=$(hostname -I 2>/dev/null | awk '{print $1}')
    if [ -z "$ip" ]; then ip="本机IP"; fi
    echo "$ip"
}

conf_val() {
    # $1 配置文件  $2 键名（如 bindPort / auth.token）→ 输出值（去引号）
    sed -n "s/^[[:space:]]*${2//./\\.}[[:space:]]*=[[:space:]]*\"\{0,1\}\([^\"[:space:]]*\).*/\1/p" "$1" 2>/dev/null | head -n 1
}

# ---------- 依赖 ----------
ensure_net_tool() {
    if command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1; then
        return 0
    fi
    warn "未安装 curl/wget，尝试安装 curl ..."
    if ! pkg_install curl; then
        warn "curl 安装失败（若所需文件都在脚本同目录，可忽略此警告）"
    fi
}

get_py_bin() {
    # 输出面板当前应使用的 python 解释器路径（系统优先，其次独立版）
    if command -v python3 >/dev/null 2>&1; then
        command -v python3
        return 0
    fi
    if [ -x "${LOCAL_PY_DIR}/bin/python3" ]; then
        echo "${LOCAL_PY_DIR}/bin/python3"
        return 0
    fi
    return 1
}

py_tarball_for_arch() {
    # $1: frp 架构名 -> 输出独立 Python 包名（无对应包时返回 1）
    # 官方(python-build-standalone)未提供 loong64/mips 系列与 armv5/6 包
    case "$1" in
        amd64)   echo "${PY_PREFIX}-x86_64-unknown-linux-musl-install_only.tar.gz" ;;
        arm64)   echo "${PY_PREFIX}-aarch64-unknown-linux-musl-install_only.tar.gz" ;;
        arm_hf)  echo "${PY_PREFIX}-armv7-unknown-linux-gnueabihf-install_only.tar.gz" ;;
        arm)     echo "${PY_PREFIX}-armv7-unknown-linux-gnueabi-install_only.tar.gz" ;;
        riscv64) echo "${PY_PREFIX}-riscv64-unknown-linux-gnu-install_only.tar.gz" ;;
        *)       return 1 ;;
    esac
}

install_local_python() {
    # 系统无 python3 时，安装独立 Python 到 LOCAL_PY_DIR
    # 来源: files/python/ -> FILE_BASE/python/ -> PY_MIRRORS
    local arch pyball tmpd ok url want have
    arch=$(get_arch 2>/dev/null) || return 1
    pyball=$(py_tarball_for_arch "$arch") || {
        info "独立 Python 无 ${arch} 架构包（官方未提供），改用系统包管理器"
        return 1
    }
    tmpd=$(mktemp -d)

    # 1) 本地 files/python/
    if [ -f "${SCRIPT_DIR}/files/python/${pyball}" ]; then
        info "使用本地文件: ${SCRIPT_DIR}/files/python/${pyball}"
        cp "${SCRIPT_DIR}/files/python/${pyball}" "${tmpd}/${pyball}"
    else
        ok=0
        # 2) FILE_BASE/python/
        if [ -n "$FILE_BASE" ]; then
            info "从自有服务器下载: ${FILE_BASE%/}/python/${pyball}"
            if download "${FILE_BASE%/}/python/${pyball}" "${tmpd}/${pyball}"; then
                ok=1
            else
                warn "自有服务器下载失败，回退 GitHub ..."
            fi
        fi
        # 3) GitHub 官方 + 加速镜像
        if [ "$ok" -ne 1 ]; then
            for url in "${PY_MIRRORS[@]}"; do
                info "下载独立 Python: ${url}/${pyball}"
                if download "${url}/${pyball}" "${tmpd}/${pyball}"; then
                    ok=1
                    break
                fi
            done
        fi
        if [ "$ok" -ne 1 ]; then
            rm -rf "$tmpd"
            info "独立 Python 下载失败，改用系统包管理器"
            return 1
        fi
    fi

    # SHA256 校验（本地 files/python/SHA256SUMS 存在时）
    if [ -f "${SCRIPT_DIR}/files/python/SHA256SUMS" ] && command -v sha256sum >/dev/null 2>&1; then
        want=$(awk -v f="$pyball" '$2 == f {print $1; exit}' "${SCRIPT_DIR}/files/python/SHA256SUMS")
        if [ -n "$want" ]; then
            have=$(sha256sum "${tmpd}/${pyball}" 2>/dev/null | awk '{print $1}')
            if [ "$have" != "$want" ]; then
                rm -rf "$tmpd"
                warn "独立 Python 包 SHA256 校验失败: ${pyball}，改用系统包管理器"
                return 1
            fi
            info "SHA256 校验通过"
        fi
    fi

    # 解压（install_only 包顶层就是 python/ 目录）
    info "安装独立 Python 到 ${LOCAL_PY_DIR} ..."
    if ! tar -xzf "${tmpd}/${pyball}" -C "${tmpd}"; then
        rm -rf "$tmpd"
        warn "独立 Python 解压失败，改用系统包管理器"
        return 1
    fi
    if [ ! -d "${tmpd}/python" ]; then
        rm -rf "$tmpd"
        warn "独立 Python 包结构异常，改用系统包管理器"
        return 1
    fi
    rm -rf "$LOCAL_PY_DIR"
    if ! mv "${tmpd}/python" "$LOCAL_PY_DIR"; then
        rm -rf "$tmpd" "$LOCAL_PY_DIR"
        warn "独立 Python 安装失败，改用系统包管理器"
        return 1
    fi
    rm -rf "$tmpd"

    # 验证可运行（armv5/6 老设备跑不了 armv7 包，此处自动兜底回退）
    if ! "${LOCAL_PY_DIR}/bin/python3" -V >/dev/null 2>&1; then
        rm -rf "$LOCAL_PY_DIR"
        warn "独立 Python 无法在本机运行（CPU 不兼容），改用系统包管理器"
        return 1
    fi
    info "独立 Python: $(${LOCAL_PY_DIR}/bin/python3 -V 2>&1)"
    return 0
}

ensure_python3() {
    # 优先级: 系统 python3 -> 本地独立 Python -> 包管理器
    if command -v python3 >/dev/null 2>&1; then
        info "python3: $(python3 -V 2>&1)"
        return 0
    fi
    warn "系统未安装 python3，尝试使用独立 Python ..."
    if install_local_python; then
        return 0
    fi
    warn "改用包管理器安装 python3 ..."
    if ! pkg_install python3; then
        error "python3 安装失败，请手动安装后重试（面板必需）"
    fi
    info "python3: $(python3 -V 2>&1)"
}

# ============================================================
# 文件获取（核心：三级回退）
# ============================================================
verify_sha256() {
    # $1 待校验文件  $2 该文件在 checksums 表中的文件名
    # 校验表来源: files/frp/frp_sha256_checksums.txt（兼容旧位置）
    # 无校验表 / 无 sha256sum 命令 / 表中无此文件时跳过（返回 0）
    local cs="" want have
    if [ -f "${SCRIPT_DIR}/files/frp/frp_sha256_checksums.txt" ]; then
        cs="${SCRIPT_DIR}/files/frp/frp_sha256_checksums.txt"
    elif [ -f "${SCRIPT_DIR}/files/frp_sha256_checksums.txt" ]; then
        cs="${SCRIPT_DIR}/files/frp_sha256_checksums.txt"
    elif [ -f "${SCRIPT_DIR}/frp_sha256_checksums.txt" ]; then
        cs="${SCRIPT_DIR}/frp_sha256_checksums.txt"
    fi
    [ -n "$cs" ] || return 0
    command -v sha256sum >/dev/null 2>&1 || return 0
    want=$(awk -v f="$2" '$2 == f {print $1; exit}' "$cs")
    [ -n "$want" ] || return 0
    have=$(sha256sum "$1" 2>/dev/null | awk '{print $1}')
    [ "$have" = "$want" ]
}

fetch_file() {
    # $1 文件名  $2 目标路径  $3 权限(默认 644)
    # 优先级: 脚本同目录(含 files/ 子目录) -> FILE_BASE -> 失败
    local fname="$1" dest="$2" mode="${3:-644}" src
    for src in "${SCRIPT_DIR}/files/${fname}" "${SCRIPT_DIR}/${fname}"; do
        if [ -f "$src" ]; then
            info "使用本地文件: $src"
            install -m "$mode" "$src" "$dest"
            return 0
        fi
    done
    if [ -n "$FILE_BASE" ]; then
        info "从自有服务器下载: ${FILE_BASE%/}/${fname}"
        if download "${FILE_BASE%/}/${fname}" "$dest"; then
            return 0
        fi
        warn "自有服务器下载失败: ${FILE_BASE%/}/${fname}"
        rm -f "$dest"
    fi
    return 1
}

fetch_frp_bin() {
    # $1: frps 或 frpc —— 安装对应二进制到 /usr/local/bin
    local want="$1" arch tarball tmpd srcdir ok url
    arch=$(get_arch) || error "不支持的 CPU 架构: $(uname -m)"
    if [ "$arch" = "386" ]; then
        error "frp v${FRP_VER} 官方未提供 linux 386 包（新版 frp 已停止发布 32 位 x86），建议更换 64 位系统"
    fi
    tarball="frp_${FRP_VER}_linux_${arch}.tar.gz"
    info "CPU 架构: $(uname -m) -> ${arch}"

    tmpd=$(mktemp -d)

    # 1) 本地 tar 包（files/frp/ -> files/ -> 脚本同目录）
    srcdir=""
    if [ -f "${SCRIPT_DIR}/files/frp/${tarball}" ]; then
        srcdir="${SCRIPT_DIR}/files/frp"
    elif [ -f "${SCRIPT_DIR}/files/${tarball}" ]; then
        srcdir="${SCRIPT_DIR}/files"
    elif [ -f "${SCRIPT_DIR}/${tarball}" ]; then
        srcdir="${SCRIPT_DIR}"
    fi

    if [ -n "$srcdir" ]; then
        info "使用本地文件: ${srcdir}/${tarball}"
        cp "${srcdir}/${tarball}" "${tmpd}/${tarball}"
    else
        ok=0
        # 2) 自有文件服务器（frp/ 子目录，与本地 files/frp/ 结构一致）
        if [ -n "$FILE_BASE" ]; then
            info "从自有服务器下载: ${FILE_BASE%/}/frp/${tarball}"
            if download "${FILE_BASE%/}/frp/${tarball}" "${tmpd}/${tarball}"; then
                ok=1
            else
                warn "自有服务器下载失败，回退 GitHub ..."
            fi
        fi
        # 3) GitHub 官方 + 加速镜像
        if [ "$ok" -ne 1 ]; then
            for url in "${GH_MIRRORS[@]}"; do
                info "下载 frp v${FRP_VER} (${arch}): ${url}/v${FRP_VER}/${tarball}"
                if download "${url}/v${FRP_VER}/${tarball}" "${tmpd}/${tarball}"; then
                    ok=1
                    break
                fi
                warn "下载失败，尝试下一个地址 ..."
            done
        fi
        if [ "$ok" -ne 1 ]; then
            rm -rf "$tmpd"
            error "frp 下载失败。可将 ${tarball} 放到脚本同目录的 files/frp/（或 files/）后重试"
        fi
    fi

    # SHA256 校验（本地包与下载包统一校验；无校验表时自动跳过）
    if ! verify_sha256 "${tmpd}/${tarball}" "$tarball"; then
        rm -rf "$tmpd"
        error "SHA256 校验失败: ${tarball}（文件不完整或被篡改，请删除后重新获取）"
    fi

    info "解压并安装 ${want} ..."
    tar -xzf "${tmpd}/${tarball}" -C "${tmpd}"
    install -m 755 "${tmpd}/frp_${FRP_VER}_linux_${arch}/${want}" "/usr/local/bin/${want}"
    rm -rf "$tmpd"
}

# ============================================================
# 1. 安装 frps 服务端
# ============================================================
do_install_frps() {
    set -e
    info "包管理器: ${PKG_MGR}"
    ensure_net_tool

    # ---------- 二进制 ----------
    if [ -x "$FRPS_BIN" ]; then
        info "已安装 frps: $($FRPS_BIN -v)（跳过下载）"
    else
        fetch_frp_bin frps
        info "frps 已安装: $($FRPS_BIN -v)"
    fi

    # ---------- 配置文件 ----------
    mkdir -p "$FRP_DIR"
    if [ -f "$FRPS_CONF" ]; then
        info "检测到已有 frps 配置，保持不变: $FRPS_CONF"
        info "（如需修改，直接编辑该文件后选择「重启服务」）"
    else
        echo ""
        info "生成 frps 配置（回车使用默认值）..."
        local bp tk vhp vhs p
        bp=$(ask "  bindPort（frpc 连接端口）" "7000")
        tk=$(ask "  auth.token（留空 = 自动生成随机 token）" "")
        if [ -z "$tk" ]; then tk=$(rand_token); fi
        vhp=$(ask "  vhostHTTPPort（http 代理用；80=免端口访问，8080=配合 nginx 反代，0=不开启）" "8080")
        vhs=$(ask "  vhostHTTPSPort（https 代理用，0=不开启）" "0")

        for p in "$bp" "$vhp" "$vhs"; do
            if [ "$p" = "0" ]; then continue; fi
            if port_in_use "$p"; then
                warn "端口 ${p} 似乎已被占用，请确认后继续"
            fi
        done

        cat > "$FRPS_CONF" <<EOF
# frps 服务端配置（生成于 $(date '+%Y-%m-%d %H:%M:%S')）
# 文档: https://gofrp.org
bindPort = ${bp}
auth.token = "${tk}"
EOF
        if [ "$vhp" != "0" ]; then
            echo "vhostHTTPPort = ${vhp}" >> "$FRPS_CONF"
        fi
        if [ "$vhs" != "0" ]; then
            echo "vhostHTTPSPort = ${vhs}" >> "$FRPS_CONF"
        fi

        # 可选 dashboard
        echo ""
        if ask_yn "  是否开启 frps 自带 dashboard（网页查看连接状态）? [y/N]" "n"; then
            local dp du dw
            dp=$(ask "    dashboard 端口" "7500")
            du=$(ask "    dashboard 用户名" "admin")
            dw=$(rand_token)
            cat >> "$FRPS_CONF" <<EOF
webServer.addr = "0.0.0.0"
webServer.port = ${dp}
webServer.user = "${du}"
webServer.password = "${dw}"
EOF
            info "dashboard: http://$(get_ip):${dp}  用户: ${du}  密码: ${dw}"
        fi
    fi

    # ---------- systemd 服务 ----------
    if unit_exists frps; then
        info "已存在 frps.service，跳过创建"
    else
        info "创建 frps.service（配置: $FRPS_CONF）..."
        cat > /etc/systemd/system/frps.service <<EOF
[Unit]
Description=frp Server (frps) - 内网穿透服务端
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
Restart=on-failure
RestartSec=5s
ExecStart=${FRPS_BIN} -c ${FRPS_CONF}
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
EOF
        systemctl daemon-reload
        info "frps.service 已创建"
    fi
    systemctl enable frps.service >/dev/null 2>&1 || true

    info "启动 frps ..."
    systemctl restart frps.service
    sleep 1
    set +e

    # ---------- 摘要 ----------
    local tk_show bp_show vhp_show
    tk_show=$(conf_val "$FRPS_CONF" "auth.token")
    bp_show=$(conf_val "$FRPS_CONF" "bindPort")
    vhp_show=$(conf_val "$FRPS_CONF" "vhostHTTPPort")
    echo ""
    echo -e "${GREEN}================= frps 安装完成 =================${PLAIN}"
    echo ""
    echo "  配置文件:   $FRPS_CONF"
    echo "  连接端口:   ${bp_show}"
    echo "  auth token: ${tk_show}   <-- frpc 端要填这个"
    if [ -n "$vhp_show" ]; then
        echo "  vhostHTTP:  ${vhp_show}（http 类型代理的访问端口）"
    fi
    echo ""
    warn "  请在服务器防火墙/安全组放行: ${bp_show} $(if [ -n "$vhp_show" ]; then echo "$vhp_show"; fi)"
    echo ""
    info "  下一步: 在内网机器上运行本脚本选「安装 frpc」，"
    info "  面板「服务端」页填 服务器IP:${bp_show} 和上面的 token"
    echo ""
}

# ============================================================
# 2. 安装 frpc 客户端 + 管理面板
# ============================================================
do_install_frpc() {
    set -e
    info "包管理器: ${PKG_MGR}"
    ensure_net_tool

    # ---------- 二进制 ----------
    if [ -x "$FRPC_BIN" ]; then
        info "已安装 frpc: $($FRPC_BIN -v)（跳过下载）"
    elif command -v frpc >/dev/null 2>&1; then
        warn "系统已有 frpc: $(command -v frpc)，跳过安装"
    else
        fetch_frp_bin frpc
        info "frpc 已安装: $($FRPC_BIN -v)"
    fi

    # ---------- 初始配置文件 ----------
    mkdir -p "$FRP_DIR"
    local sa="" sp="" st="" conf_placeholder=""
    if [ ! -f "$FRPC_CONF" ] && [ ! -f "${FRP_DIR}/frpc.ini" ]; then
        echo ""
        if ask_yn "  是否现在填写 frps 服务器信息? [y/N]" "n"; then
            info "填好即自动连上，无需再进面板配置；不确定的项直接回车用默认值"
            sa=$(ask "  服务器地址 serverAddr（frps 服务器的公网 IP 或域名）" "")
            sp=$(ask "  服务器端口 serverPort" "7000")
            st=$(ask "  auth token（frps 安装完成时显示的那串）" "")
        fi
        if [ -n "$sa" ]; then
            cat > "$FRPC_CONF" <<EOF
# frpc 客户端配置（推荐用 Web 面板管理，也可手动编辑）
serverAddr = "${sa}"
serverPort = ${sp}

[auth]
token = "${st}"
EOF
            info "服务端信息已写入配置: ${sa}:${sp}"
            [ -n "$st" ] || warn "token 为空: frps 端若设置了 token 将无法连接"
        else
            cat > "$FRPC_CONF" <<EOF
# frpc 客户端配置（推荐用 Web 面板管理，也可手动编辑）
# 安装完成后打开面板，在「服务端」页填写真实的服务器地址和 token
serverAddr = "127.0.0.1"
serverPort = 7000

[auth]
token = ""
EOF
            conf_placeholder="1"
            warn "使用占位配置，服务器信息请安装完成后到面板「服务端」页填写"
        fi
    else
        info "检测到已有 frpc 配置，保持不变"
    fi

    # ---------- frpc systemd 服务 ----------
    if unit_exists frpc; then
        info "已存在 frpc.service，跳过创建"
    else
        CONF_USE="$FRPC_CONF"
        if [ -f "${FRP_DIR}/frpc.ini" ] && [ ! -f "$FRPC_CONF" ]; then
            CONF_USE="${FRP_DIR}/frpc.ini"
        fi
        FRPC_USE="$FRPC_BIN"
        if [ ! -x "$FRPC_BIN" ]; then
            FRPC_USE="$(command -v frpc)"
        fi

        info "创建 frpc.service（配置: $CONF_USE）..."
        cat > /etc/systemd/system/frpc.service <<EOF
[Unit]
Description=frp client (frpc) - 内网穿透客户端
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
Restart=on-failure
RestartSec=5s
ExecStart=${FRPC_USE} -c ${CONF_USE}
ExecReload=${FRPC_USE} reload -c ${CONF_USE}
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
EOF
        systemctl daemon-reload
        info "frpc.service 已创建"
    fi
    systemctl enable frpc.service >/dev/null 2>&1 || true

    # ---------- 安装面板 ----------
    ensure_python3

    info "安装面板到 $PANEL_DIR ..."
    mkdir -p "$PANEL_DIR"
    if ! fetch_file "frpc-manager.py" "${PANEL_DIR}/frpc-manager.py"; then
        error "未获取到 frpc-manager.py！请任选其一:\n  1) 把 frpc-manager.py 和 mcfrpmanager 放在同一目录再运行\n  2) 在 FILE_BASE 指向的服务器上提供该文件"
    fi

    if unit_exists frpc-manager; then
        info "已存在 frpc-manager.service，跳过创建"
    else
        PY_BIN="$(get_py_bin)" || error "未找到可用的 Python 解释器"
        cat > /etc/systemd/system/frpc-manager.service <<EOF
[Unit]
Description=frpc Web Manager - frpc 网页管理面板
After=network.target network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=${PANEL_DIR}
ExecStart=${PY_BIN} ${PANEL_DIR}/frpc-manager.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
        systemctl daemon-reload
    fi
    systemctl enable frpc-manager.service >/dev/null 2>&1 || true

    # ---------- 设置面板登录密码 ----------
    echo ""
    warn "=============== 设置面板登录密码 ==============="
    warn "密码至少 8 位。面板可能暴露到公网，请务必使用强密码！"
    PY_BIN="$(get_py_bin)" || error "未找到可用的 Python 解释器"
    "$PY_BIN" "${PANEL_DIR}/frpc-manager.py" set-password || error "设置密码失败"

    # ---------- 启动服务 ----------
    info "启动服务..."
    local check_since
    check_since="$(date '+%Y-%m-%d %H:%M:%S')"
    systemctl restart frpc.service 2>/dev/null || true
    systemctl restart frpc-manager.service

    sleep 3
    set +e

    # ---------- 连接检查（只看本次重启后的日志，避免历史日志误报） ----------
    local conn_ok=""
    if journalctl -u frpc.service --since "$check_since" -n 30 --no-pager 2>/dev/null | grep -q "login to server success"; then
        conn_ok="1"
    fi

    echo ""
    echo -e "${GREEN}================= 安装完成 =================${PLAIN}"
    echo ""
    echo "  面板地址:  http://$(get_ip):$(get_panel_port)"
    echo "  登录密码:  刚才设置的密码"
    if [ -n "$conf_placeholder" ]; then
        echo "  连接状态:  未连接（占位配置）—— 服务器信息请到面板「服务端」页填写"
    elif [ -n "$conn_ok" ]; then
        echo -e "  连接状态:  ${GREEN}已成功连接 frps 服务器${PLAIN}"
    else
        echo "  连接状态:  未检测到连接成功（请核对 地址/端口/token，"
        echo "             可到面板「服务端」页修改后重连）"
    fi
    echo ""
    echo "  后续管理:  sudo bash mcfrpmanager  （状态/重启/改密码/备份/卸载）"
    echo ""
    warn "  安全提示: 若盒子有公网 IP 直接访问面板端口，请使用强密码；"
    warn "  更稳妥的方式是把面板端口也通过 frp 代理出去访问。"
    echo ""
}

# ============================================================
# 3. 更新面板程序
# ============================================================
do_update() {
    panel_installed || error "面板未安装，请先选择安装"
    local new="" tmp=""
    if [ -f "${SCRIPT_DIR}/files/frpc-manager.py" ]; then
        new="${SCRIPT_DIR}/files/frpc-manager.py"
    elif [ -f "${SCRIPT_DIR}/frpc-manager.py" ]; then
        new="${SCRIPT_DIR}/frpc-manager.py"
    elif [ -n "$FILE_BASE" ]; then
        tmp=$(mktemp)
        info "从自有服务器下载新版面板: ${FILE_BASE%/}/frpc-manager.py"
        if download "${FILE_BASE%/}/frpc-manager.py" "$tmp"; then
            new="$tmp"
        fi
    fi
    if [ -z "$new" ]; then
        error "未找到新版 frpc-manager.py（脚本同目录或 FILE_BASE 服务器）"
    fi
    if cmp -s "$new" "$PANEL_SCRIPT"; then
        info "面板程序与当前安装版本一致，无需更新"
        if [ -n "$tmp" ]; then rm -f "$tmp"; fi
        return
    fi
    install -m 644 "$new" "$PANEL_SCRIPT"
    if [ -n "$tmp" ]; then rm -f "$tmp"; fi
    systemctl restart frpc-manager.service
    info "面板程序已更新并重启（密码、配置、备份全部保留）"
}

# ============================================================
# 4. 查看运行状态
# ============================================================
do_status() {
    echo ""
    echo -e "${CYAN}---------------- 运行状态 ----------------${PLAIN}"
    # frps
    if unit_exists frps; then
        st=$(svc_active frps)
        if [ "$st" = "active" ]; then
            echo -e "  frps 服务:   ${GREEN}● 运行中${PLAIN}"
        else
            echo -e "  frps 服务:   ${RED}○ 未运行 (${st})${PLAIN}"
        fi
        if [ -x "$FRPS_BIN" ]; then
            echo "  frps 版本:   $($FRPS_BIN -v 2>/dev/null || echo 未知)"
        fi
        echo "  配置文件:   $FRPS_CONF"
        local bp vhp
        bp=$(conf_val "$FRPS_CONF" "bindPort")
        vhp=$(conf_val "$FRPS_CONF" "vhostHTTPPort")
        if [ -n "$bp" ]; then echo "  bindPort:    ${bp}"; fi
        if [ -n "$vhp" ]; then echo "  vhostHTTP:   ${vhp}"; fi
    else
        echo -e "  frps 服务:   ${YELLOW}未安装${PLAIN}"
    fi
    # frpc
    if unit_exists frpc; then
        st=$(svc_active frpc)
        if [ "$st" = "active" ]; then
            echo -e "  frpc 服务:   ${GREEN}● 运行中${PLAIN}"
        else
            echo -e "  frpc 服务:   ${RED}○ 未运行 (${st})${PLAIN}"
        fi
        if [ -x "$FRPC_BIN" ]; then
            echo "  frpc 版本:   $($FRPC_BIN -v 2>/dev/null || echo 未知)"
        fi
    else
        echo -e "  frpc 服务:   ${YELLOW}未安装${PLAIN}"
    fi
    # 面板
    if panel_installed; then
        st=$(svc_active frpc-manager)
        if [ "$st" = "active" ]; then
            echo -e "  面板服务:   ${GREEN}● 运行中${PLAIN}"
            echo -e "  面板地址:   ${GREEN}http://$(get_ip):$(get_panel_port)${PLAIN}"
        else
            echo -e "  面板服务:   ${RED}○ 未运行 (${st})${PLAIN}"
        fi
    else
        echo -e "  面板服务:   ${YELLOW}未安装${PLAIN}"
    fi
    # frpc 配置文件
    conf=""
    if [ -f "$FRPC_CONF" ]; then
        conf="$FRPC_CONF"
    elif [ -f "${FRP_DIR}/frpc.ini" ]; then
        conf="${FRP_DIR}/frpc.ini"
    fi
    if [ -n "$conf" ]; then
        echo "  frpc 配置:  $conf"
    fi
    echo -e "${CYAN}--------------------------------------------${PLAIN}"
}

# ============================================================
# 5. 重启服务
# ============================================================
do_restart() {
    local opts=() svcs=() idx c n
    if unit_exists frps; then opts+=("frps 服务端"); svcs+=("frps.service"); fi
    if unit_exists frpc; then opts+=("frpc 客户端"); svcs+=("frpc.service"); fi
    if panel_installed; then opts+=("管理面板"); svcs+=("frpc-manager.service"); fi
    if [ ${#opts[@]} -eq 0 ]; then
        warn "未发现已安装的服务"
        return
    fi
    n=${#opts[@]}
    echo ""
    for idx in "${!opts[@]}"; do
        echo "  $((idx+1))) 重启 ${opts[$idx]}"
    done
    echo "  $((n+1))) 全部重启"
    read -r -p "请选择 [1-$((n+1))]: " c || c=""
    if [ "$c" -ge 1 ] 2>/dev/null && [ "$c" -le "$n" ] 2>/dev/null; then
        if systemctl restart "${svcs[$((c-1))]}"; then
            info "${opts[$((c-1))]} 已重启"
        else
            warn "${opts[$((c-1))]} 重启失败"
        fi
    elif [ "$c" -eq "$((n+1))" ] 2>/dev/null; then
        for idx in "${!svcs[@]}"; do
            if systemctl restart "${svcs[$idx]}" 2>/dev/null; then
                info "${opts[$idx]} 已重启"
            else
                warn "${opts[$idx]} 重启失败"
            fi
        done
    else
        warn "无效选择: $c"
    fi
}

# ============================================================
# 6. 查看实时日志
# ============================================================
do_logs() {
    local opts=() svcs=() idx c
    if unit_exists frps; then opts+=("frps 服务端"); svcs+=("frps"); fi
    if unit_exists frpc; then opts+=("frpc 客户端"); svcs+=("frpc"); fi
    if panel_installed; then opts+=("管理面板"); svcs+=("frpc-manager"); fi
    if [ ${#opts[@]} -eq 0 ]; then
        warn "未发现已安装的服务"
        return
    fi
    echo ""
    for idx in "${!opts[@]}"; do
        echo "  $((idx+1))) ${opts[$idx]} 日志"
    done
    read -r -p "请选择 [1-${#opts[@]}]: " c || c=""
    if [ "$c" -ge 1 ] 2>/dev/null && [ "$c" -le "${#opts[@]}" ] 2>/dev/null; then
        journalctl -u "${svcs[$((c-1))]}" -f -n 50 --no-pager
        info "（已退出日志查看）"
    else
        warn "无效选择: $c"
    fi
}

# ============================================================
# 7. 修改面板登录密码
# ============================================================
do_password() {
    panel_installed || error "面板未安装，请先选择安装"
    local py
    py=$(get_py_bin) || error "未找到可用的 Python 解释器（请重新运行安装）"
    "$py" "$PANEL_SCRIPT" set-password
}

# ============================================================
# 8. 手动备份当前配置
# ============================================================
do_backup() {
    panel_installed || error "面板未安装，请先选择安装"
    conf=""
    if [ -f "$FRPC_CONF" ]; then
        conf="$FRPC_CONF"
    elif [ -f "${FRP_DIR}/frpc.ini" ]; then
        conf="${FRP_DIR}/frpc.ini"
    fi
    if [ -z "$conf" ]; then
        warn "未找到 frpc 配置文件，无需备份"
        return
    fi
    ext="${conf##*.}"
    name="config-$(date +%Y%m%d-%H%M%S).${ext}"
    mkdir -p "$BACKUP_DIR"
    cp "$conf" "${BACKUP_DIR}/${name}"
    info "已备份: ${BACKUP_DIR}/${name}"
    # 与面板一致：最多保留 20 份，超出删除最旧
    (
        cd "$BACKUP_DIR" || exit 1
        set -- *
        [ -e "$1" ] || set --
        while [ "$#" -gt "$MAX_BACKUPS" ]; do
            rm -f "$1"
            shift
        done
    )
    info "可在面板「备份」页查看并一键恢复"
}

# ============================================================
# 9. 卸载（按组件选择）
# ============================================================
do_uninstall() {
    echo ""
    local removed=0
    warn "以下操作将逐个询问，可只卸载部分组件"

    # ---- frps ----
    if unit_exists frps || [ -x "$FRPS_BIN" ]; then
        if ask_yn "卸载 frps 服务端? [y/N]" "n"; then
            systemctl stop frps 2>/dev/null || true
            systemctl disable frps 2>/dev/null || true
            rm -f /etc/systemd/system/frps.service
            rm -f "$FRPS_BIN"
            info "frps 已卸载"
            removed=1
        else
            info "已保留 frps（二进制与 unit 文件均未改动）"
        fi
    else
        info "未安装 frps，跳过"
    fi

    # ---- frpc ----
    if unit_exists frpc || [ -x "$FRPC_BIN" ]; then
        if ask_yn "卸载 frpc 客户端? [y/N]" "n"; then
            systemctl stop frpc 2>/dev/null || true
            systemctl disable frpc 2>/dev/null || true
            rm -f /etc/systemd/system/frpc.service
            rm -f "$FRPC_BIN"
            info "frpc 已卸载"
            removed=1
        else
            info "已保留 frpc（二进制与 unit 文件均未改动）"
        fi
    else
        info "未安装 frpc，跳过"
    fi

    # ---- 面板 ----
    if panel_installed || unit_exists frpc-manager; then
        if ask_yn "卸载管理面板? [y/N]" "n"; then
            systemctl stop frpc-manager 2>/dev/null || true
            systemctl disable frpc-manager 2>/dev/null || true
            rm -f /etc/systemd/system/frpc-manager.service
            if [ -d "$PANEL_DIR" ]; then
                if ask_yn "  连同面板数据（密码/备份）一起删除? [y/N]" "n"; then
                    rm -rf "$PANEL_DIR"
                    info "已删除面板目录: $PANEL_DIR"
                else
                    info "已保留 $PANEL_DIR（重装面板时密码与备份可复用）"
                fi
            fi
            if [ -d "$LOCAL_PY_DIR" ]; then
                if ask_yn "  删除脚本安装的独立 Python ($LOCAL_PY_DIR)? [y/N]" "n"; then
                    rm -rf "$LOCAL_PY_DIR"
                    info "已删除独立 Python: $LOCAL_PY_DIR"
                else
                    info "已保留独立 Python（重装面板时可直接复用）"
                fi
            fi
            info "管理面板已卸载"
            removed=1
        else
            info "已保留管理面板"
        fi
    else
        info "未安装面板，跳过"
    fi

    systemctl daemon-reload 2>/dev/null || true
    systemctl reset-failed 2>/dev/null || true

    # ---- 配置目录 ----
    if [ -d "$FRP_DIR" ] && [ "$removed" -eq 1 ]; then
        if [ -x "$FRPS_BIN" ] || [ -x "$FRPC_BIN" ]; then
            info "保留配置目录 $FRP_DIR（另一端仍在使用）"
        elif ask_yn "是否删除配置目录 $FRP_DIR? [y/N]" "n"; then
            rm -rf "$FRP_DIR"
            info "已删除配置目录: $FRP_DIR"
        else
            info "已保留配置目录: $FRP_DIR（重装时可复用）"
        fi
    fi

    echo ""
    if [ "$removed" -eq 1 ]; then
        info "卸载完成"
    else
        info "未卸载任何组件"
    fi
}

# ============================================================
# 主菜单
# ============================================================
show_installed() {
    local parts=()
    if unit_exists frps || [ -x "$FRPS_BIN" ]; then parts+=("frps"); fi
    if unit_exists frpc || [ -x "$FRPC_BIN" ]; then parts+=("frpc"); fi
    if panel_installed; then parts+=("面板"); fi
    if [ ${#parts[@]} -eq 0 ]; then
        echo "  当前未安装任何组件"
    else
        echo "  已安装: ${parts[*]}"
    fi
}

main_menu() {
    while true; do
        echo ""
        echo -e "${CYAN}==========================================${PLAIN}"
        echo -e "${CYAN}     FRP 一体化管理脚本 (frps+frpc)      ${PLAIN}"
        echo -e "${CYAN}==========================================${PLAIN}"
        show_installed
        echo ""
        echo "  1) 安装 frps 服务端"
        echo "  2) 安装 frpc 客户端 + 管理面板"
        echo "  3) 更新面板程序（保留密码与配置）"
        echo "  4) 查看运行状态"
        echo "  5) 重启服务"
        echo "  6) 查看实时日志"
        echo "  7) 修改面板登录密码"
        echo "  8) 手动备份 frpc 配置"
        echo "  9) 卸载"
        echo "  0) 退出"
        echo ""
        read -r -p "请选择 [0-9]: " choice || exit 0
        case "$choice" in
            1) do_install_frps ;;
            2) do_install_frpc ;;
            3) do_update ;;
            4) do_status ;;
            5) do_restart ;;
            6) do_logs ;;
            7) do_password ;;
            8) do_backup ;;
            9) do_uninstall ;;
            0|q|Q) exit 0 ;;
            *) warn "无效选择: $choice" ;;
        esac
    done
}

# ---------- 入口 ----------
case "${1:-}" in
    frps|install-frps)      do_install_frps ;;
    frpc|install|install-frpc) do_install_frpc ;;
    update)                 do_update ;;
    status)                 do_status ;;
    restart)                do_restart ;;
    logs)                   do_logs ;;
    password)               do_password ;;
    backup)                 do_backup ;;
    uninstall)              do_uninstall ;;
    "")                     main_menu ;;
    *)
        echo "用法: sudo bash mcfrpmanager [frps|frpc|update|status|restart|logs|password|backup|uninstall]"
        echo "      无参数进入交互菜单"
        exit 1
        ;;
esac
