From dbbac3d146a864c57715e6d5974cbe736101e4d6 Mon Sep 17 00:00:00 2001 From: Chaos Date: Thu, 2 Jul 2026 15:35:59 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=A4=A7=E4=BC=98=E5=8C=96=EF=BC=9ATo?= =?UTF-8?q?ken=E9=BB=91=E5=90=8D=E5=8D=95+=E6=89=8B=E5=8A=A8=E6=B3=A8?= =?UTF-8?q?=E5=85=A5+=E5=B7=B2=E6=9C=89frpc=E9=85=8D=E7=BD=AE=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python网关改进: - Token黑名单机制:401失效token自动加入黑名单避免重复扫描 - /set_token API端点:支持手动注入有效Token - /health 端点增加 blacklisted 计数 - 内存扫描结果先收集再过滤,避免立即缓存无效token Shell部署脚本改进: - 已有frpc配置时跳过frps连接信息输入,仅需网关端口 - 步骤重新编号 1-5: 检测frpc->检测配置->配置参数->处理代理->部署 --- ai/hwaishell.sh | 158 +++++++++++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 54 deletions(-) diff --git a/ai/hwaishell.sh b/ai/hwaishell.sh index 2b9f5bd..7c9f4ad 100644 --- a/ai/hwaishell.sh +++ b/ai/hwaishell.sh @@ -214,47 +214,8 @@ else log_info "系统已安装 frpc。" fi -# ================= 配置网关与 frpc 代理参数 ================= -log_step "2/5" "配置 frpc 连接与网关端口参数:" - -# frps 服务器连接信息(必填 —— 管道模式下必须通过环境变量提供) -if [ -t 0 ]; then - # 交互模式 - while [ -z "$FRPS_ADDR" ]; do - read -p "请输入 frps 服务器地址 (例: 1.2.3.4): " FRPS_ADDR - [ -z "$FRPS_ADDR" ] && log_error "此项为必填,请输入!" - done - read -p "请输入 frps 服务器端口 (默认 7000): " FRPS_PORT - FRPS_PORT=${FRPS_PORT:-7000} - read -p "请输入 frps 认证 token (默认留空): " FRPS_TOKEN -else - # 管道模式:必须通过环境变量提供关键参数 - if [ -z "$FRPS_ADDR" ]; then - log_error "管道模式下必须通过环境变量提供参数!" - log_error "用法: FRPS_ADDR=1.2.3.4 FRPS_PORT=7000 FRPS_TOKEN=xxx curl -sSL ... | bash" - log_error "或下载后交互式: curl -sSL -o hwaishell.sh && bash hwaishell.sh" - exit 1 - fi - FRPS_PORT=${FRPS_PORT:-7000} - FRPS_TOKEN=${FRPS_TOKEN:-} -fi - -PROXY_NAME="${PROXY_NAME:-$(read_or_default "请输入代理名称 (默认 huawei-dynamic-gateway): " "huawei-dynamic-gateway")}" -GATEWAY_PORT="${GATEWAY_PORT:-$(read_or_default "请输入 Python 动态网关的本地监听端口 (默认 8080): " "8080")}" -REMOTE_PORT="${REMOTE_PORT:-$(read_or_default "请输入在 frps 公网端暴露的端口 (默认 18000): " "18000")}" - -# 端口有效性检查 -if ! [[ "$GATEWAY_PORT" =~ ^[0-9]+$ ]] || [ "$GATEWAY_PORT" -lt 1 ] || [ "$GATEWAY_PORT" -gt 65535 ]; then - log_error "无效的本地端口: $GATEWAY_PORT" - exit 1 -fi -if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_PORT" -lt 1 ] || [ "$REMOTE_PORT" -gt 65535 ]; then - log_error "无效的远程端口: $REMOTE_PORT" - exit 1 -fi - -# ================= 生成/追加 frpc 配置 ================= -log_step "3/5" "检测并生成 frpc 配置文件..." +# ================= 查找现有 frpc 配置 ================= +log_step "2/5" "检测 frpc 配置文件..." FRPC_CONFIG="" FRPC_TYPE="toml" @@ -276,6 +237,61 @@ if [ -z "$FRPC_CONFIG" ]; then fi fi +HAS_EXISTING_FRPC=false +if [ -n "$FRPC_CONFIG" ]; then + HAS_EXISTING_FRPC=true + log_info "找到已有 frpc 配置: $FRPC_CONFIG" +fi + +# ================= 配置网关与 frpc 代理参数 ================= +log_step "3/5" "配置网关与代理参数:" + +if $HAS_EXISTING_FRPC; then + # 已有 frpc 配置,只需输入网关端口 + log_info "使用已有 frpc 配置,仅需输入网关相关参数" + GATEWAY_PORT="${GATEWAY_PORT:-$(read_or_default "请输入 Python 动态网关的本地监听端口 (默认 8080): " "8080")}" + REMOTE_PORT="${REMOTE_PORT:-$(read_or_default "请输入在 frps 公网端暴露的端口 (默认 18000): " "18000")}" + PROXY_NAME="${PROXY_NAME:-$(read_or_default "请输入代理名称 (默认 huawei-dynamic-gateway): " "huawei-dynamic-gateway")}" +else + # 没有 frpc 配置,需要完整输入 frps 连接信息 + log_info "未找到 frpc 配置,需要输入 frps 连接信息" + if [ -t 0 ]; then + while [ -z "$FRPS_ADDR" ]; do + read -p "请输入 frps 服务器地址 (例: 1.2.3.4): " FRPS_ADDR + [ -z "$FRPS_ADDR" ] && log_error "此项为必填,请输入!" + done + read -p "请输入 frps 服务器端口 (默认 7000): " FRPS_PORT + FRPS_PORT=${FRPS_PORT:-7000} + read -p "请输入 frps 认证 token (默认留空): " FRPS_TOKEN + else + if [ -z "$FRPS_ADDR" ]; then + log_error "管道模式下必须通过环境变量提供参数!" + log_error "用法: FRPS_ADDR=1.2.3.4 FRPS_PORT=7000 FRPS_TOKEN=xxx curl -sSL ... | bash" + log_error "或下载后交互式: curl -sSL -o hwaishell.sh && bash hwaishell.sh" + exit 1 + fi + FRPS_PORT=${FRPS_PORT:-7000} + FRPS_TOKEN=${FRPS_TOKEN:-} + fi + read -p "请输入代理名称 (默认 huawei-dynamic-gateway): " PROXY_NAME 2>/dev/null || PROXY_NAME="" + PROXY_NAME="${PROXY_NAME:-huawei-dynamic-gateway}" + GATEWAY_PORT="${GATEWAY_PORT:-$(read_or_default "请输入 Python 动态网关的本地监听端口 (默认 8080): " "8080")}" + REMOTE_PORT="${REMOTE_PORT:-$(read_or_default "请输入在 frps 公网端暴露的端口 (默认 18000): " "18000")}" +fi + +# 端口有效性检查 +if ! [[ "$GATEWAY_PORT" =~ ^[0-9]+$ ]] || [ "$GATEWAY_PORT" -lt 1 ] || [ "$GATEWAY_PORT" -gt 65535 ]; then + log_error "无效的本地端口: $GATEWAY_PORT" + exit 1 +fi +if ! [[ "$REMOTE_PORT" =~ ^[0-9]+$ ]] || [ "$REMOTE_PORT" -lt 1 ] || [ "$REMOTE_PORT" -gt 65535 ]; then + log_error "无效的远程端口: $REMOTE_PORT" + exit 1 +fi + +# ================= 生成/追加 frpc 配置 ================= +log_step "4/5" "处理 frpc 代理规则..." + # 构建代理规则块 build_proxy_block() { local type=$1 # toml | ini @@ -301,9 +317,8 @@ BLK fi } -if [ -n "$FRPC_CONFIG" ]; then +if $HAS_EXISTING_FRPC; then # --- 已有 frpc 配置:追加代理规则 --- - log_info "识别到 frpc 配置: $FRPC_CONFIG (${FRPC_TYPE})" if confirm_or_default "是否自动将华为云转发规则追加到该文件? (y/n, 默认 y): " "y"; then if grep -q "${PROXY_NAME}" "$FRPC_CONFIG" 2>/dev/null; then log_warn "frpc 配置中已存在 ${PROXY_NAME} 规则,跳过追加" @@ -315,7 +330,6 @@ if [ -n "$FRPC_CONFIG" ]; then fi else # --- 没有 frpc 配置:自动创建 --- - log_warn "未找到 frpc 配置文件" if confirm_or_default "是否自动创建 frpc 配置文件并添加网关代理规则? (y/n, 默认 y): " "y"; then FRPC_CONFIG="/etc/frp/frpc.toml" mkdir -p /etc/frp @@ -334,7 +348,7 @@ BLK fi # ================= 安装 Python 依赖 ================= -log_step "4/5" "部署 Python 运行环境..." +log_step "5/5" "部署 Python 运行环境并启动服务..." # 检查 python3 PYTHON_CMD="" @@ -397,7 +411,6 @@ $PIP_CMD install --upgrade pip --quiet -i https://pypi.tuna.tsinghua.edu.cn/simp $PIP_CMD install flask requests --quiet -i https://pypi.tuna.tsinghua.edu.cn/simple # ================= 创建网关核心代码 ================= -log_step "5/5" "生成网关代码并启动服务..." mkdir -p /usr/local/bin /var/log /var/run cat << 'PYEOF' > /usr/local/bin/huawei_gateway.py @@ -452,6 +465,13 @@ class TokenCache: self._lock = threading.RLock() self._last_scan = 0 self._scan_interval = 60 # 扫描间隔最小 60 秒 + self._blacklist = set() # 已失效的 token 指纹 + + def _fingerprint(self, token): + """取 token 前 16 + 后 16 字符做指纹""" + if len(token) <= 32: + return token + return token[:16] + token[-16:] def get(self): with self._lock: @@ -465,6 +485,19 @@ class TokenCache: self._token = token self._expires_at = time.time() + ttl self._last_scan = time.time() + self._blacklist.discard(self._fingerprint(token)) + + def blacklist_current(self): + """将当前 token 加入黑名单""" + with self._lock: + if self._token: + self._blacklist.add(self._fingerprint(self._token)) + self._token = None + self._expires_at = 0 + + def is_blacklisted(self, token): + with self._lock: + return self._fingerprint(token) in self._blacklist def is_scan_cooldown(self): with self._lock: @@ -564,18 +597,22 @@ def find_token_in_memory(): # 先扫描优先级进程 all_pids = priority_pids + other_pids + found_tokens = [] with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: futures = {executor.submit(scan_pid_mem, pid): pid for pid in all_pids} for future in as_completed(futures): try: token = future.result(timeout=5) - if token: - cache.set(token) - logger.info(f"Token 已刷新 (来源 PID: {futures[future]})") - return token + if token and not cache.is_blacklisted(token): + found_tokens.append((token, futures[future])) except Exception: continue + for token, pid in found_tokens: + cache.set(token) + logger.info(f"Token 已刷新 (来源 PID: {pid})") + return token + return cache.get() # 返回可能过期的缓存作为兜底 @@ -589,10 +626,23 @@ def health(): return { "status": "healthy", "token_cached": token is not None, - "token_expires_in": max(0, cache._expires_at - time.time()) if hasattr(cache, '_expires_at') else 0 + "token_expires_in": max(0, cache._expires_at - time.time()) if hasattr(cache, '_expires_at') else 0, + "blacklisted": len(cache._blacklist) if hasattr(cache, '_blacklist') else 0 }, 200 +@app.route('/set_token', methods=['POST']) +def set_token(): + """手动注入有效 Token""" + data = request.get_json(force=True, silent=True) if request.is_json else {} + token = data.get('token', '') + if not token or len(token) < 100: + return {"error": "请提供有效的 token"}, 400 + cache.set(token) + logger.info("Token 已手动注入") + return {"status": "ok", "token_fingerprint": cache._fingerprint(token)}, 200 + + @app.route('/v2/', methods=['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE']) def proxy(subpath): if request.method == 'OPTIONS': @@ -631,10 +681,10 @@ def proxy(subpath): stream=False ) - # 401 兜底:Token 可能提前过期,强制刷新重试一次 + # 401 兜底:Token 可能提前过期,加入黑名单后强制刷新重试 if resp.status_code == 401: - logger.warning("收到 401,尝试强制刷新 Token 并重试...") - cache.clear() + logger.warning("收到 401,将当前 Token 加入黑名单并强制刷新...") + cache.blacklist_current() new_token = find_token_in_memory() if new_token and new_token != real_token: headers['Authorization'] = f'Bearer {new_token}'