优化华为云网关脚本:支持容器环境、生产级WSGI、智能内存扫描

- 新增 systemd 检测,容器环境自动使用 nohup 模式
- 优先使用 waitress/gunicorn 生产服务器
- 智能进程优先级扫描 + 分段内存读取
- 多镜像源下载回退机制
- 增加健康检查端点 /health
- 配置自动备份 + 端口有效性验证
- 虚拟环境支持避免 root pip 警告
This commit is contained in:
2026-07-02 14:38:27 +08:00
parent 9be2cbd58e
commit f480f251fb
+532 -128
View File
@@ -7,250 +7,631 @@ YELLOW='\033[0;33m'
BLUE='\033[0;36m' BLUE='\033[0;36m'
PLAIN='\033[0m' PLAIN='\033[0m'
# ================= 工具函数 =================
log_info() { echo -e "${GREEN}[INFO]${PLAIN} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${PLAIN} $1"; }
log_error() { echo -e "${RED}[ERROR]${PLAIN} $1"; }
log_step() { echo -e "\n${BLUE}[${1}]${PLAIN} ${2}"; }
# 检查是否有 systemd
has_systemd() {
[ -d /run/systemd/system ] || [ -d /var/run/systemd/system ] || systemctl --version &>/dev/null
}
# 安全的进程启动(支持 systemd 和 nohup 两种模式)
safe_start_service() {
local name=$1
local cmd=$2
local service_file=$3
if has_systemd; then
systemctl daemon-reload 2>/dev/null
systemctl enable "$name" --now 2>/dev/null
log_info "$name 已通过 systemd 启动"
else
# 容器/无 systemd 环境:使用 nohup + 简单的 PID 文件管理
local pid_file="/var/run/${name}.pid"
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
log_warn "$name 已在运行 (PID: $(cat "$pid_file"))"
return 0
fi
nohup $cmd > "/var/log/${name}.log" 2>&1 &
echo $! > "$pid_file"
log_info "$name 已通过 nohup 启动 (PID: $!)"
fi
}
# 检查服务状态
get_service_status() {
local name=$1
if has_systemd; then
systemctl is-active "$name" 2>/dev/null || echo "unknown"
else
local pid_file="/var/run/${name}.pid"
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
echo "active (nohup, PID: $(cat "$pid_file"))"
else
echo "inactive"
fi
fi
}
# 重启服务
restart_service() {
local name=$1
if has_systemd; then
systemctl restart "$name" 2>/dev/null
else
local pid_file="/var/run/${name}.pid"
[ -f "$pid_file" ] && kill "$(cat "$pid_file")" 2>/dev/null
sleep 1
fi
}
echo -e "${BLUE}==================================================${PLAIN}" echo -e "${BLUE}==================================================${PLAIN}"
echo -e "${GREEN} 华为云 Token 动态网关 (6小时高能效缓存版) 部署 ${PLAIN}" echo -e "${GREEN} 华为云 Token 动态网关 (6小时高能效缓存版) 部署 ${PLAIN}"
echo -e "${BLUE}==================================================${PLAIN}" echo -e "${BLUE}==================================================${PLAIN}"
# 1. 检查权限 # 1. 检查权限
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo -e "${RED}错误:请使用 root 用户运行此脚本!${PLAIN}" log_error "请使用 root 用户运行此脚本!"
exit 1 exit 1
fi fi
# 检测环境类型
IS_CONTAINER=false
if [ -f /.dockerenv ] || grep -qE 'docker|containerd|kubepods' /proc/1/cgroup 2>/dev/null; then
IS_CONTAINER=true
log_warn "检测到容器环境,将使用 nohup 模式替代 systemd"
fi
# ================= 检测并安装 frpc ================= # ================= 检测并安装 frpc =================
echo -e "\n${YELLOW}[1/5] 正在检测 frpc 环境...${PLAIN}" log_step "1/5" "正在检测 frpc 环境..."
if ! command -v frpc &> /dev/null && [ ! -f "/usr/local/bin/frpc" ] && [ ! -f "/usr/bin/frpc" ]; then if ! command -v frpc &>/dev/null && [ ! -f "/usr/local/bin/frpc" ] && [ ! -f "/usr/bin/frpc" ]; then
echo -e "${RED}未检测到 frpc${PLAIN}" log_warn "未检测到 frpc"
read -p "是否需要为您自动下载并安装最新版 frpc (使用国内加速镜像)? (y/n, 默认 y): " INSTALL_FRP read -p "是否需要为您自动下载并安装最新版 frpc (使用国内加速镜像)? (y/n, 默认 y): " INSTALL_FRP
INSTALL_FRP=${INSTALL_FRP:-y} INSTALL_FRP=${INSTALL_FRP:-y}
if [[ "$INSTALL_FRP" == "y" || "$INSTALL_FRP" == "Y" ]]; then if [[ "$INSTALL_FRP" == "y" || "$INSTALL_FRP" == "Y" ]]; then
ARCH=$(uname -m) ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ]; then case "$ARCH" in
FRP_ARCH="amd64" x86_64) FRP_ARCH="amd64" ;;
elif [ "$ARCH" = "aarch64" ]; then aarch64) FRP_ARCH="arm64" ;;
FRP_ARCH="arm64" armv7l) FRP_ARCH="arm" ;;
else i386|i686) FRP_ARCH="386" ;;
echo -e "${RED}错误:不支持的系统架构 $ARCH,请手动安装 frpc。${PLAIN}" *)
log_error "不支持的系统架构 $ARCH,请手动安装 frpc。"
exit 1
;;
esac
FRP_VERSION="0.61.0"
log_info "开始通过国内节点下载 frpc v${FRP_VERSION} (${FRP_ARCH})..."
# 使用多个镜像源,自动回退
MIRRORS=(
"https://ghfast.top/https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
"https://mirror.ghproxy.com/https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
"https://github.moeyy.xyz/https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
)
DOWNLOADED=false
for url in "${MIRRORS[@]}"; do
log_info "尝试下载: $url"
if wget --timeout=30 --tries=2 --no-check-certificate -O frp.tar.gz "$url" 2>/dev/null; then
DOWNLOADED=true
break
fi
done
if [ "$DOWNLOADED" != "true" ]; then
log_error "所有镜像源下载失败!请检查网络或手动安装 frpc。"
exit 1 exit 1
fi fi
FRP_VERSION="0.58.1" tar -zxf frp.tar.gz > /dev/null 2>&1 || { log_error "解压失败"; exit 1; }
echo -e "${GREEN}开始通过国内节点下载 frpc v${FRP_VERSION} ($FRP_ARCH)...${PLAIN}" cd frp_${FRP_VERSION}_linux_${FRP_ARCH} || { log_error "无法进入解压目录"; exit 1; }
FRP_URL="https://mirror.ghproxy.com/https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
wget --no-check-certificate -O frp.tar.gz "$FRP_URL"
if [ $? -ne 0 ]; then echo -e "${RED}下载失败!${PLAIN}"; exit 1; fi
tar -zxvf frp.tar.gz > /dev/null
cd frp_${FRP_VERSION}_linux_${FRP_ARCH} || { echo -e "${RED}错误:无法进入解压目录!${PLAIN}"; exit 1; }
cp frpc /usr/local/bin/ && chmod +x /usr/local/bin/frpc cp frpc /usr/local/bin/ && chmod +x /usr/local/bin/frpc
mkdir -p /etc/frp mkdir -p /etc/frp
if [ ! -f "/etc/frp/frpc.toml" ]; then if [ ! -f "/etc/frp/frpc.toml" ]; then
cat << EOF > /etc/frp/frpc.toml cat << 'FRPCFG' > /etc/frp/frpc.toml
serverAddr = "x.x.x.x" serverAddr = "x.x.x.x"
serverPort = 7000 serverPort = 7000
EOF auth.method = "token"
auth.token = "your_token_here"
FRPCFG
log_warn "已生成 frpc 配置模板,请编辑 /etc/frp/frpc.toml 填入正确的服务器信息"
fi fi
cat << EOF > /etc/systemd/system/frpc.service # 生成 systemd 服务文件(即使当前无 systemd,也保留以便后续使用)
cat << 'FRPSVC' > /etc/systemd/system/frpc.service
[Unit] [Unit]
Description=Frp Client Service Description=Frp Client Service
After=network.target After=network-online.target
Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=root User=root
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
StartLimitInterval=60s
StartLimitBurst=3
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
ExecReload=/usr/local/bin/frpc reload -c /etc/frp/frpc.toml ExecReload=/usr/local/bin/frpc reload -c /etc/frp/frpc.toml
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF FRPSVC
systemctl daemon-reload && systemctl enable frpc --now
if has_systemd; then
systemctl daemon-reload && systemctl enable frpc --now
fi
cd .. && rm -rf frp_${FRP_VERSION}_linux_${FRP_ARCH} frp.tar.gz cd .. && rm -rf frp_${FRP_VERSION}_linux_${FRP_ARCH} frp.tar.gz
echo -e "${GREEN}frpc 安装并注册成功!配置文件位于 /etc/frp/frpc.toml${PLAIN}" log_info "frpc 安装完成!配置文件位于 /etc/frp/frpc.toml"
fi fi
else else
echo -e "${GREEN}系统已安装 frpc。${PLAIN}" log_info "系统已安装 frpc。"
fi fi
# ================= 配置网关与映射端口 ================= # ================= 配置网关与映射端口 =================
echo -e "\n${YELLOW}[2/5] 配置动态网关参数:${PLAIN}" log_step "2/5" "配置动态网关参数:"
read -p "请输入 Python 动态网关的本地监听端口 (默认 8080): " GATEWAY_PORT read -p "请输入 Python 动态网关的本地监听端口 (默认 8080): " GATEWAY_PORT
GATEWAY_PORT=${GATEWAY_PORT:-8080} GATEWAY_PORT=${GATEWAY_PORT:-8080}
read -p "请输入你想在 frps 公网端暴露的端口 (默认 18000): " REMOTE_PORT read -p "请输入你想在 frps 公网端暴露的端口 (默认 18000): " REMOTE_PORT
REMOTE_PORT=${REMOTE_PORT:-18000} REMOTE_PORT=${REMOTE_PORT:-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 配置文件 ================= # ================= 查找 frpc 配置文件 =================
echo -e "\n${YELLOW}[3/5] 检测 frpc 配置文件位置以追加代理规则:${PLAIN}" log_step "3/5" "检测 frpc 配置文件位置以追加代理规则:"
FRPC_CONFIG="" FRPC_CONFIG=""
if [ -f "/etc/frp/frpc.toml" ]; then FRPC_CONFIG="/etc/frp/frpc.toml"; FRPC_TYPE="toml"; FRPC_TYPE=""
elif [ -f "/etc/frp/frpc.ini" ]; then FRPC_CONFIG="/etc/frp/frpc.ini"; FRPC_TYPE="ini";
else # 按优先级查找配置文件
FRPC_PATH_SEARCH=$(find /etc /usr/local/etc /opt /home -maxdepth 4 -name "frpc.toml" -o -name "frpc.ini" 2>/dev/null | head -n 1) for path in "/etc/frp/frpc.toml" "/etc/frp/frpc.ini" "/usr/local/etc/frp/frpc.toml" "/usr/local/etc/frp/frpc.ini"; do
if [ -f "$path" ]; then
FRPC_CONFIG="$path"
[[ "$path" == *.toml ]] && FRPC_TYPE="toml" || FRPC_TYPE="ini"
break
fi
done
# 如果没找到,尝试搜索(限制深度避免长时间等待)
if [ -z "$FRPC_CONFIG" ]; then
FRPC_PATH_SEARCH=$(find /etc /usr/local/etc /opt /home -maxdepth 3 \( -name "frpc.toml" -o -name "frpc.ini" \) 2>/dev/null | head -n 1)
if [ -n "$FRPC_PATH_SEARCH" ]; then if [ -n "$FRPC_PATH_SEARCH" ]; then
FRPC_CONFIG="$FRPC_PATH_SEARCH" FRPC_CONFIG="$FRPC_PATH_SEARCH"
if [[ "$FRPC_PATH_SEARCH" == *.toml ]]; then FRPC_TYPE="toml"; else FRPC_TYPE="ini"; fi [[ "$FRPC_PATH_SEARCH" == *.toml ]] && FRPC_TYPE="toml" || FRPC_TYPE="ini"
fi fi
fi fi
APPEND_FRP="n"
if [ -n "$FRPC_CONFIG" ]; then if [ -n "$FRPC_CONFIG" ]; then
echo -e "${GREEN}识别到 frpc 配置: $FRPC_CONFIG (${FRPC_TYPE})${PLAIN}" log_info "识别到 frpc 配置: $FRPC_CONFIG (${FRPC_TYPE})"
read -p "是否自动将华为云转发规则追加到该文件? (y/n, 默认 y): " APPEND_FRP read -p "是否自动将华为云转发规则追加到该文件? (y/n, 默认 y): " APPEND_FRP
APPEND_FRP=${APPEND_FRP:-y} APPEND_FRP=${APPEND_FRP:-y}
else else
APPEND_FRP="n" log_warn "未找到 frpc 配置文件,跳过自动追加"
log_warn "请手动在 frpc 配置中添加以下规则:"
echo -e "${YELLOW}--- TOML 格式 ---${PLAIN}"
echo -e "[[proxies]]"
echo -e "name = \"huawei-dynamic-gateway\""
echo -e "type = \"tcp\""
echo -e "local_ip = \"127.0.0.1\""
echo -e "local_port = ${GATEWAY_PORT}"
echo -e "remote_port = ${REMOTE_PORT}"
fi fi
# ================= 安装 Python 依赖 ================= # ================= 安装 Python 依赖 =================
echo -e "\n${YELLOW}[4/5] 部署 Python 运行环境...${PLAIN}" log_step "4/5" "部署 Python 运行环境..."
if ! command -v python3 &> /dev/null; then
if command -v apt &> /dev/null; then apt update -y && apt install -y python3 python3-pip;
elif command -v yum &> /dev/null; then yum install -y python3 python3-pip; fi
fi
python3 -m pip install flask requests --quiet -i https://pypi.tuna.tsinghua.edu.cn/simple
# ================= 创建网关核心代码 (核心优化点) ================= # 检查 python3
echo -e "\n${YELLOW}[5/5] 生成网关代码并启动服务...${PLAIN}" PYTHON_CMD=""
cat << 'EOF' > /usr/local/bin/huawei_gateway.py for cmd in python3 python3.11 python3.10 python3.9 python3.8; do
if command -v "$cmd" &>/dev/null; then
PYTHON_CMD="$cmd"
break
fi
done
if [ -z "$PYTHON_CMD" ]; then
log_warn "未找到 Python3,尝试自动安装..."
if command -v apt &>/dev/null; then
apt-get update -qq && apt-get install -y -qq python3 python3-pip
elif command -v yum &>/dev/null; then
yum install -y -q python3 python3-pip
elif command -v apk &>/dev/null; then
apk add --no-cache python3 py3-pip
else
log_error "无法自动安装 Python,请手动安装 python3 和 pip"
exit 1
fi
PYTHON_CMD="python3"
fi
# 检查 pip 并安装依赖
if ! $PYTHON_CMD -m pip --version &>/dev/null 2>&1; then
log_warn "pip 未安装,尝试安装..."
$PYTHON_CMD -m ensurepip --upgrade 2>/dev/null || {
if command -v apt &>/dev/null; then apt-get install -y -qq python3-pip; fi
}
fi
# 使用虚拟环境避免权限问题(可选)
USE_VENV=false
VENV_PATH="/opt/huawei-gateway/venv"
if [ "$IS_CONTAINER" = true ] || [ "$EUID" -eq 0 ]; then
read -p "是否使用虚拟环境安装 Python 依赖? (推荐, y/n, 默认 y): " USE_VENV_INPUT
USE_VENV_INPUT=${USE_VENV_INPUT:-y}
[[ "$USE_VENV_INPUT" == "y" || "$USE_VENV_INPUT" == "Y" ]] && USE_VENV=true
fi
if [ "$USE_VENV" = true ]; then
mkdir -p /opt/huawei-gateway
$PYTHON_CMD -m venv "$VENV_PATH" 2>/dev/null || {
log_warn "无法创建虚拟环境,将使用系统 Python"
USE_VENV=false
}
fi
PIP_CMD="$PYTHON_CMD -m pip"
if [ "$USE_VENV" = true ]; then
PIP_CMD="$VENV_PATH/bin/pip"
PYTHON_CMD="$VENV_PATH/bin/python"
fi
# 安装依赖(使用国内镜像)
log_info "正在安装 Flask 和 requests..."
$PIP_CMD install --upgrade pip --quiet -i https://pypi.tuna.tsinghua.edu.cn/simple 2>/dev/null
$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
#!/usr/bin/env python3
"""
华为云 Token 动态网关
- 6小时缓存机制
- 支持内存扫描自动刷新
- 401 自动重试
- 兼容生产环境 (Waitress/Gunicorn)
"""
import os import os
import re import re
import sys
import time import time
import logging import logging
import threading import threading
import traceback import traceback
import requests from concurrent.futures import ThreadPoolExecutor, as_completed
from flask import Flask, request, Response from flask import Flask, request, Response
from concurrent.futures import ThreadPoolExecutor
app = Flask(__name__) # 尝试导入 requests,失败则给出明确提示
logging.basicConfig(level=logging.WARNING, try:
format='%(asctime)s [%(levelname)s] %(message)s') import requests
except ImportError:
print("错误:缺少 requests 模块。请运行: pip install requests")
sys.exit(1)
# 内存缓存字典:6小时有效期,这里设置为安全线 5.5 小时 (19800秒) # ================= 配置 =================
cache = {"token": None, "expires_at": 0} CACHE_TTL = 19800 # 5.5 小时(安全线)
cache_lock = threading.Lock() MAX_WORKERS = 8 # 内存扫描线程数
MAX_MEM_SEGMENT = 200 * 1024 * 1024 # 单段最大扫描 200MB
TOKEN_PATTERN = re.compile(b'Bearer ([A-Za-z0-9+/=_-]{100,})')
TARGET_HOST = 'tokenhub.developer.huaweicloud.com'
# ================= 日志 =================
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('huawei-gateway')
# ================= 缓存 =================
class TokenCache:
def __init__(self):
self._token = None
self._expires_at = 0
self._lock = threading.RLock()
self._last_scan = 0
self._scan_interval = 60 # 扫描间隔最小 60 秒
def get(self):
with self._lock:
now = time.time()
if self._token and now < self._expires_at:
return self._token
return None
def set(self, token, ttl=CACHE_TTL):
with self._lock:
self._token = token
self._expires_at = time.time() + ttl
self._last_scan = time.time()
def is_scan_cooldown(self):
with self._lock:
return (time.time() - self._last_scan) < self._scan_interval
def clear(self):
with self._lock:
self._token = None
self._expires_at = 0
cache = TokenCache()
# ================= 内存扫描 =================
def scan_pid_mem(pid): def scan_pid_mem(pid):
"""扫描单个进程的内存寻找 Token"""
maps_path = f'/proc/{pid}/maps'
mem_path = f'/proc/{pid}/mem'
if not os.path.exists(maps_path) or not os.path.exists(mem_path):
return None
try: try:
with open(f'/proc/{pid}/maps', 'r') as f: with open(maps_path, 'r') as f:
for line in f: for line in f:
parts = line.split() parts = line.split()
if len(parts) < 2 or 'r' not in parts[1]: continue if len(parts) < 2:
addrs = parts[0].split('-')
if len(addrs) != 2: continue
start, end = int(addrs[0], 16), int(addrs[1], 16)
if (end - start) > 200 * 1024 * 1024: continue
try:
with open(f'/proc/{pid}/mem', 'rb') as mem:
mem.seek(start)
data = mem.read(end - start)
for m in re.finditer(b'Bearer ([A-Za-z0-9+/=_-]+)', data):
val = m.group(1).decode(errors='replace')
if len(val) > 200:
return val
except Exception:
continue continue
except Exception: perms = parts[1]
if 'r' not in perms or 'w' not in perms: # 只扫描可读可写段(更精确)
continue
addrs = parts[0].split('-')
if len(addrs) != 2:
continue
start = int(addrs[0], 16)
end = int(addrs[1], 16)
size = end - start
# 跳过过大或过小的段
if size > MAX_MEM_SEGMENT or size < 1024:
continue
try:
with open(mem_path, 'rb') as mem:
mem.seek(start)
# 分段读取避免大内存占用
chunk_size = 64 * 1024
remaining = size
while remaining > 0:
to_read = min(chunk_size, remaining)
data = mem.read(to_read)
if not data:
break
for match in TOKEN_PATTERN.finditer(data):
token = match.group(1).decode('ascii', errors='replace')
# 验证 token 格式(华为云 token 通常是 JWT 格式)
if len(token) > 200 and token.count('.') >= 2:
return token
remaining -= len(data)
except (PermissionError, OSError, ValueError):
continue
except (PermissionError, OSError, ProcessLookupError):
pass pass
return None return None
def force_refresh_token():
"""强制重新从内存扫描 Token 并更新缓存时间"""
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
with ThreadPoolExecutor(max_workers=8) as executor:
results = executor.map(scan_pid_mem, pids)
for token in results:
if token:
with cache_lock:
cache["token"] = token
cache["expires_at"] = time.time() + 19800 # 缓存 5.5 小时
return token
return cache["token"]
def get_latest_token(): def find_token_in_memory():
now = time.time() """在所有进程中扫描 Token"""
with cache_lock: if cache.is_scan_cooldown() and cache.get():
# 如果缓存存在且没过期,直接返回缓存(消耗几乎为 0) return cache.get()
if cache["token"] and now < cache["expires_at"]:
return cache["token"]
# 否则,或者过期了,才去扫描内存
return force_refresh_token()
@app.route('/v2/<path:subpath>', methods=['POST', 'GET', 'OPTIONS']) try:
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
except OSError:
logger.error("无法访问 /proc 目录")
return None
# 优先扫描常见进程(python, node, java 等可能持有 token 的进程)
priority_pids = []
other_pids = []
for pid in pids:
try:
exe_path = os.readlink(f'/proc/{pid}/exe')
if any(x in exe_path for x in ['python', 'node', 'java', 'chrome', 'electron']):
priority_pids.append(pid)
else:
other_pids.append(pid)
except (OSError, PermissionError):
other_pids.append(pid)
# 先扫描优先级进程
all_pids = priority_pids + other_pids
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
except Exception:
continue
return cache.get() # 返回可能过期的缓存作为兜底
# ================= Flask 应用 =================
app = Flask(__name__)
@app.route('/health')
def health():
"""健康检查端点"""
token = cache.get()
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
}, 200
@app.route('/v2/<path:subpath>', methods=['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE'])
def proxy(subpath): def proxy(subpath):
if request.method == 'OPTIONS': if request.method == 'OPTIONS':
return Response(), 200 return Response(status=200, headers={
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
})
real_token = get_latest_token() real_token = find_token_in_memory()
if not real_token: if not real_token:
logging.error("未在内存中找到华为云Token") logger.error("未在内存中找到华为云 Token")
return {"error": "未在内存中找到华为云Token"}, 500 return {"error": "未在内存中找到华为云Token,请确保华为云相关应用正在运行"}, 500
# 构建请求头
headers = {}
for k, v in request.headers:
kl = k.lower()
if kl not in ('host', 'content-length', 'connection', 'accept-encoding'):
headers[k] = v
headers = {k: v for k, v in request.headers if k.lower() != 'host'}
headers['Authorization'] = f'Bearer {real_token}' headers['Authorization'] = f'Bearer {real_token}'
headers['Host'] = 'tokenhub.developer.huaweicloud.com' headers['Host'] = TARGET_HOST
target_url = f'https://{TARGET_HOST}/v2/{subpath}'
target_url = f'https://tokenhub.developer.huaweicloud.com/v2/{subpath}'
try: try:
resp = requests.request( resp = requests.request(
method=request.method, url=target_url, headers=headers, method=request.method,
data=request.get_data(), cookies=request.cookies, url=target_url,
allow_redirects=False, timeout=60 headers=headers,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
timeout=60,
stream=False
) )
# 兜底保障:如果华为主端提早失效返回了 401,立即强制重新扫描内存并重试一次请求 # 401 兜底:Token 可能提前过期,强制刷新重试一次
if resp.status_code == 401: if resp.status_code == 401:
new_token = force_refresh_token() logger.warning("收到 401,尝试强制刷新 Token 并重试...")
if new_token: cache.clear()
new_token = find_token_in_memory()
if new_token and new_token != real_token:
headers['Authorization'] = f'Bearer {new_token}' headers['Authorization'] = f'Bearer {new_token}'
resp = requests.request( resp = requests.request(
method=request.method, url=target_url, headers=headers, method=request.method,
data=request.get_data(), cookies=request.cookies, url=target_url,
allow_redirects=False, timeout=60 headers=headers,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
timeout=60
) )
return Response(resp.content, resp.status_code, resp.headers.items()) # 构建响应
response_headers = []
for k, v in resp.headers.items():
if k.lower() not in ('transfer-encoding', 'content-encoding', 'content-length'):
response_headers.append((k, v))
return Response(
resp.content,
status=resp.status_code,
headers=response_headers
)
except requests.exceptions.Timeout:
logger.error("请求华为云 API 超时")
return {"error": "网关超时,请稍后重试"}, 504
except requests.exceptions.ConnectionError:
logger.error("无法连接到华为云 API")
return {"error": "无法连接到华为云服务"}, 502
except Exception as e: except Exception as e:
logging.error(f"网关转发失败: {traceback.format_exc()}") logger.error(f"网关转发失败: {traceback.format_exc()}")
return {"error": f"网关转发失败: {str(e)}"}, 500 return {"error": f"网关转发失败: {str(e)}"}, 500
if __name__ == '__main__':
import sys def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080 port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
app.run(host='127.0.0.1', port=port, debug=False) host = sys.argv[2] if len(sys.argv) > 2 else '127.0.0.1'
EOF # 尝试使用生产级 WSGI 服务器
try:
import waitress
logger.info(f"使用 Waitress 启动网关 ({host}:{port})")
waitress.serve(app, host=host, port=port, threads=16)
except ImportError:
try:
import gunicorn.app.wsgiapp
logger.info(f"使用 Gunicorn 启动网关 ({host}:{port})")
os.execlp('gunicorn', 'gunicorn', '-w', '4', '-b', f'{host}:{port}', '--access-logfile', '-', 'huawei_gateway:app')
except (ImportError, OSError):
logger.warning("未安装 Waitress/Gunicorn,使用 Flask 开发服务器(建议生产环境安装 waitress")
logger.info(f"启动网关 ({host}:{port})")
app.run(host=host, port=port, debug=False, threaded=True)
# 注册网关服务
cat << EOF > /etc/systemd/system/huawei-gateway.service if __name__ == '__main__':
main()
PYEOF
chmod +x /usr/local/bin/huawei_gateway.py
# ================= 注册网关服务 =================
if has_systemd; then
cat << EOF > /etc/systemd/system/huawei-gateway.service
[Unit] [Unit]
Description=Huawei Dynamic Gateway (6H Cache) Description=Huawei Dynamic Gateway (6H Cache)
After=network.target After=network-online.target
Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
User=root User=root
ExecStart=/usr/bin/python3 /usr/local/bin/huawei_gateway.py $GATEWAY_PORT ExecStart=$PYTHON_CMD /usr/local/bin/huawei_gateway.py $GATEWAY_PORT
Restart=always Restart=always
RestartSec=3 RestartSec=3
StartLimitInterval=60s
StartLimitBurst=3
StandardOutput=append:/var/log/huawei-gateway.log
StandardError=append:/var/log/huawei-gateway.log
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
fi
systemctl daemon-reload && systemctl enable huawei-gateway --now &>/dev/null # 启动服务
safe_start_service "huawei-gateway" "$PYTHON_CMD /usr/local/bin/huawei_gateway.py $GATEWAY_PORT" "/etc/systemd/system/huawei-gateway.service"
# ================= 追加 frpc 规则 ================= # ================= 追加 frpc 规则 =================
if [[ "$APPEND_FRP" == "y" || "$APPEND_FRP" == "Y" ]] && [ -f "$FRPC_CONFIG" ]; then if [[ "$APPEND_FRP" == "y" || "$APPEND_FRP" == "Y" ]] && [ -n "$FRPC_CONFIG" ] && [ -f "$FRPC_CONFIG" ]; then
if grep -q "huawei-dynamic-gateway" "$FRPC_CONFIG" 2>/dev/null; then if grep -q "huawei-dynamic-gateway" "$FRPC_CONFIG" 2>/dev/null; then
echo -e "${YELLOW}检测到 frpc 配置中已存在 huawei-dynamic-gateway 规则,跳过追加${PLAIN}" log_warn "frpc 配置中已存在 huawei-dynamic-gateway 规则,跳过追加"
else else
# 备份原配置
cp "$FRPC_CONFIG" "${FRPC_CONFIG}.backup.$(date +%Y%m%d%H%M%S)"
if [ "$FRPC_TYPE" = "toml" ]; then if [ "$FRPC_TYPE" = "toml" ]; then
cat << EOF >> "$FRPC_CONFIG" cat << EOF >> "$FRPC_CONFIG"
@@ -271,18 +652,41 @@ local_port = $GATEWAY_PORT
remote_port = $REMOTE_PORT remote_port = $REMOTE_PORT
EOF EOF
fi fi
echo -e "${GREEN}frpc 代理规则已追加到 $FRPC_CONFIG${PLAIN}" log_info "frpc 代理规则已追加到 $FRPC_CONFIG"
log_info "原配置已备份"
fi
# 重启 frpc
if has_systemd; then
if systemctl list-units --type=service 2>/dev/null | grep -q "frpc"; then
systemctl restart frpc 2>/dev/null && log_info "frpc 服务已重启"
fi
else
restart_service "frpc"
# 尝试直接启动 frpc
if command -v frpc &>/dev/null; then
nohup frpc -c "$FRPC_CONFIG" > /var/log/frpc.log 2>&1 &
log_info "frpc 已通过 nohup 启动"
fi
fi fi
if systemctl list-units --type=service | grep -q "frpc"; then systemctl restart frpc; fi
fi fi
# ================= 部署总结 ================= # ================= 部署总结 =================
echo -e "\n${BLUE}==================================================${PLAIN}" echo -e "\n${BLUE}==================================================${PLAIN}"
echo -e "${GREEN} 🎉 6小时长效缓存网关部署完成 🎉 ${PLAIN}" echo -e "${GREEN} 🎉 6小时长效缓存网关部署完成 🎉 ${PLAIN}"
echo -e "${BLUE}==================================================${PLAIN}" echo -e "${BLUE}==================================================${PLAIN}"
echo -e "1. 动态网关状态:$(systemctl is-active huawei-gateway)" echo -e "1. 动态网关状态:$(get_service_status huawei-gateway)"
echo -e "2. 您的本地一劳永逸调用地址:" echo -e "2. 健康检查: http://127.0.0.1:${GATEWAY_PORT}/health"
echo -e "3. 您的本地一劳永逸调用地址:"
echo -e " - ${GREEN}API Base URL:${PLAIN} http://【你的公网frps_IP】:$REMOTE_PORT/v2" echo -e " - ${GREEN}API Base URL:${PLAIN} http://【你的公网frps_IP】:$REMOTE_PORT/v2"
echo -e " - ${GREEN}API Key:${PLAIN} sk-anything" echo -e " - ${GREEN}API Key:${PLAIN} sk-anything"
echo -e " - ${GREEN}Model:${PLAIN} glm-5.1" echo -e " - ${GREEN}Model:${PLAIN} glm-5.1"
echo -e "${BLUE}==================================================${PLAIN}\n"
if [ "$IS_CONTAINER" = true ]; then
echo -e "\n${YELLOW}[容器环境提示]${PLAIN}"
echo -e " - 服务通过 nohup 运行,日志位于 /var/log/huawei-gateway.log"
echo -e " - 如需停止服务: kill \$(cat /var/run/huawei-gateway.pid)"
echo -e " - 建议安装 waitress 提升性能: pip install waitress"
fi
echo -e "${BLUE}==================================================${PLAIN}\n"