重大优化:Token黑名单+手动注入+已有frpc配置跳过输入
Python网关改进: - Token黑名单机制:401失效token自动加入黑名单避免重复扫描 - /set_token API端点:支持手动注入有效Token - /health 端点增加 blacklisted 计数 - 内存扫描结果先收集再过滤,避免立即缓存无效token Shell部署脚本改进: - 已有frpc配置时跳过frps连接信息输入,仅需网关端口 - 步骤重新编号 1-5: 检测frpc->检测配置->配置参数->处理代理->部署
This commit is contained in:
+104
-54
@@ -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 <url> && 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 <url> && 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/<path:subpath>', 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}'
|
||||
|
||||
Reference in New Issue
Block a user