Files
script/ai/hwaishell.sh
T
chaos 209d7eb6e8 增加 read_required 函数:管道模式下必填参数必须通过环境变量提供
- 新增 read_required(): 交互模式下反复提示直到输入非空值
- 管道模式下必填参数未环境变量提供时,报错并引导下载后交互执行
- FRPS_ADDR 改为必填,其余端口保持默认值
- 避免 curl | bash 静默跳过导致配置不完整
2026-07-02 15:13:18 +08:00

785 lines
26 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ================= 颜色定义 =================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;36m'
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}"; }
# 交互式读取,必须提供非空值;管道模式下要求环境变量
read_required() {
local prompt_msg="$1"
local var_name="$2"
if [ -t 0 ]; then
local val
while true; do
read -p "$prompt_msg" val
if [ -n "$val" ]; then
echo "$val"
return 0
fi
echo -e "${RED}此项为必填,请输入!${PLAIN}"
done
else
# 管道模式:从环境变量取,没有则报错提示改为交互式执行
local env_val="${!var_name}"
if [ -n "$env_val" ]; then
echo "$env_val"
else
log_error "管道模式下参数 $var_name 未通过环境变量提供"
log_error "请改为交互式执行: curl -sSL -o hwaishell.sh <url> && bash hwaishell.sh"
exit 1
fi
fi
}
# 交互式读取,有默认值可选
read_or_default() {
local prompt_msg="$1"
local default_value="$2"
if [ -t 0 ]; then
read -p "$prompt_msg" input_val
echo "${input_val:-$default_value}"
else
echo "$default_value"
fi
}
# 交互式确认(管道模式下默认返回 true)
confirm_or_default() {
local prompt_msg="$1"
local default="$2"
if [ -t 0 ]; then
read -p "$prompt_msg" answer
answer="${answer:-$default}"
[[ "$answer" == "y" || "$answer" == "Y" ]]
else
[[ "$default" == "y" || "$default" == "Y" ]]
fi
}
# 检查是否有 systemdpid=1 是 systemd 才算真正有 systemd
has_systemd() {
[ -f /proc/1/comm ] && grep -qx "systemd" /proc/1/comm 2>/dev/null
}
# 安全的进程启动(支持 systemd 和 nohup 两种模式)
# 返回 0 表示启动成功
safe_start_service() {
local name=$1
local cmd=$2
if has_systemd; then
systemctl daemon-reload 2>/dev/null
if systemctl enable "$name" --now 2>/dev/null; then
log_info "$name 已通过 systemd 启动"
return 0
fi
# systemd 启动失败,回退到 nohup
log_warn "systemd 启动失败,回退到 nohup 模式"
fi
# 容器/无 systemd 环境:使用 nohup + 简单的 PID 文件管理
local pid_file="/var/run/${name}.pid"
local log_file="/var/log/${name}.log"
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 > "$log_file" 2>&1 &
echo $! > "$pid_file"
sleep 1
if kill -0 "$!" 2>/dev/null; then
log_info "$name 已通过 nohup 启动 (PID: $!)"
return 0
else
log_error "$name 启动失败,查看日志: $log_file"
return 1
fi
}
# 检查服务状态
get_service_status() {
local name=$1
if has_systemd; then
systemctl is-active "$name" 2>/dev/null || echo "inactive"
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"
if [ -f "$pid_file" ]; then
kill "$(cat "$pid_file")" 2>/dev/null
sleep 1
fi
fi
}
echo -e "${BLUE}==================================================${PLAIN}"
echo -e "${GREEN} 华为云 Token 动态网关 (6小时高能效缓存版) 部署 ${PLAIN}"
echo -e "${BLUE}==================================================${PLAIN}"
# 1. 检查权限
if [ "$EUID" -ne 0 ]; then
log_error "请使用 root 用户运行此脚本!"
exit 1
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 =================
log_step "1/5" "正在检测 frpc 环境..."
if ! command -v frpc &>/dev/null && [ ! -f "/usr/local/bin/frpc" ] && [ ! -f "/usr/bin/frpc" ]; then
log_warn "未检测到 frpc"
if confirm_or_default "是否需要为您自动下载并安装最新版 frpc (使用国内加速镜像)? (y/n, 默认 y): " "y"; then
ARCH=$(uname -m)
case "$ARCH" in
x86_64) FRP_ARCH="amd64" ;;
aarch64) FRP_ARCH="arm64" ;;
armv7l) FRP_ARCH="arm" ;;
i386|i686) FRP_ARCH="386" ;;
*)
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
fi
tar -zxf frp.tar.gz > /dev/null 2>&1 || { log_error "解压失败"; exit 1; }
cd frp_${FRP_VERSION}_linux_${FRP_ARCH} || { log_error "无法进入解压目录"; exit 1; }
cp frpc /usr/local/bin/ && chmod +x /usr/local/bin/frpc
mkdir -p /etc/frp
if [ ! -f "/etc/frp/frpc.toml" ]; then
cat << 'FRPCFG' > /etc/frp/frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
auth.method = "token"
auth.token = "your_token_here"
FRPCFG
log_warn "已生成 frpc 配置模板,请编辑 /etc/frp/frpc.toml 填入正确的服务器信息"
fi
# 生成 systemd 服务文件(即使当前无 systemd,也保留以便后续使用)
cat << 'FRPSVC' > /etc/systemd/system/frpc.service
[Unit]
Description=Frp Client Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Restart=on-failure
RestartSec=5s
StartLimitInterval=60s
StartLimitBurst=3
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
ExecReload=/usr/local/bin/frpc reload -c /etc/frp/frpc.toml
[Install]
WantedBy=multi-user.target
FRPSVC
if has_systemd; then
systemctl daemon-reload && systemctl enable frpc --now
fi
cd .. && rm -rf frp_${FRP_VERSION}_linux_${FRP_ARCH} frp.tar.gz
log_info "frpc 安装完成!配置文件位于 /etc/frp/frpc.toml"
fi
else
log_info "系统已安装 frpc。"
fi
# ================= 配置网关与 frpc 代理参数 =================
log_step "2/5" "配置 frpc 连接与网关端口参数:"
# frps 服务器连接信息(必填)
FRPS_ADDR="${FRPS_ADDR:-$(read_required "请输入 frps 服务器地址 (例: 1.2.3.4): " "FRPS_ADDR")}"
FRPS_PORT="${FRPS_PORT:-$(read_or_default "请输入 frps 服务器端口 (默认 7000): " "7000")}"
FRPS_TOKEN="${FRPS_TOKEN:-$(read_or_default "请输入 frps 认证 token (默认留空): " "")}"
# 代理规则
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_CONFIG=""
FRPC_TYPE="toml"
# 按优先级查找现有配置文件
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" == *.ini ]] && 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
FRPC_CONFIG="$FRPC_PATH_SEARCH"
[[ "$FRPC_PATH_SEARCH" == *.ini ]] && FRPC_TYPE="ini"
fi
fi
# 构建代理规则块
build_proxy_block() {
local type=$1 # toml | ini
if [ "$type" = "toml" ]; then
cat << BLK
[[proxies]]
name = "${PROXY_NAME}"
type = "tcp"
localIP = "127.0.0.1"
localPort = ${GATEWAY_PORT}
remotePort = ${REMOTE_PORT}
BLK
else
cat << BLK
[${PROXY_NAME}]
type = tcp
local_ip = 127.0.0.1
local_port = ${GATEWAY_PORT}
remote_port = ${REMOTE_PORT}
BLK
fi
}
if [ -n "$FRPC_CONFIG" ]; 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} 规则,跳过追加"
else
cp "$FRPC_CONFIG" "${FRPC_CONFIG}.backup.$(date +%Y%m%d%H%M%S)"
build_proxy_block "$FRPC_TYPE" >> "$FRPC_CONFIG"
log_info "代理规则已追加到 $FRPC_CONFIG (原配置已备份)"
fi
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
# 生成主配置
cat << BLK > "$FRPC_CONFIG"
serverAddr = "${FRPS_ADDR}"
serverPort = ${FRPS_PORT}
auth.method = "token"
auth.token = "${FRPS_TOKEN}"
BLK
# 追加代理规则
build_proxy_block "toml" >> "$FRPC_CONFIG"
log_info "已创建 frpc 配置文件: $FRPC_CONFIG"
fi
fi
# ================= 安装 Python 依赖 =================
log_step "4/5" "部署 Python 运行环境..."
# 检查 python3
PYTHON_CMD=""
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
if confirm_or_default "是否使用虚拟环境安装 Python 依赖? (推荐, y/n, 默认 y): " "y"; then
USE_VENV=true
fi
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 re
import sys
import time
import logging
import threading
import traceback
from concurrent.futures import ThreadPoolExecutor, as_completed
from flask import Flask, request, Response
# 尝试导入 requests,失败则给出明确提示
try:
import requests
except ImportError:
print("错误:缺少 requests 模块。请运行: pip install requests")
sys.exit(1)
# ================= 配置 =================
CACHE_TTL = 19800 # 5.5 小时(安全线)
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):
"""扫描单个进程的内存寻找 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:
with open(maps_path, 'r') as f:
for line in f:
parts = line.split()
if len(parts) < 2:
continue
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
return None
def find_token_in_memory():
"""在所有进程中扫描 Token"""
if cache.is_scan_cooldown() and cache.get():
return cache.get()
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):
if request.method == 'OPTIONS':
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 = find_token_in_memory()
if not real_token:
logger.error("未在内存中找到华为云 Token")
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['Authorization'] = f'Bearer {real_token}'
headers['Host'] = TARGET_HOST
target_url = f'https://{TARGET_HOST}/v2/{subpath}'
try:
resp = requests.request(
method=request.method,
url=target_url,
headers=headers,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
timeout=60,
stream=False
)
# 401 兜底:Token 可能提前过期,强制刷新重试一次
if resp.status_code == 401:
logger.warning("收到 401,尝试强制刷新 Token 并重试...")
cache.clear()
new_token = find_token_in_memory()
if new_token and new_token != real_token:
headers['Authorization'] = f'Bearer {new_token}'
resp = requests.request(
method=request.method,
url=target_url,
headers=headers,
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False,
timeout=60
)
# 构建响应
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:
logger.error(f"网关转发失败: {traceback.format_exc()}")
return {"error": f"网关转发失败: {str(e)}"}, 500
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
host = sys.argv[2] if len(sys.argv) > 2 else '127.0.0.1'
# 尝试使用生产级 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)
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]
Description=Huawei Dynamic Gateway (6H Cache)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=$PYTHON_CMD /usr/local/bin/huawei_gateway.py $GATEWAY_PORT
Restart=always
RestartSec=3
StartLimitInterval=60s
StartLimitBurst=3
StandardOutput=append:/var/log/huawei-gateway.log
StandardError=append:/var/log/huawei-gateway.log
[Install]
WantedBy=multi-user.target
EOF
fi
# 启动服务
safe_start_service "huawei-gateway" "$PYTHON_CMD /usr/local/bin/huawei_gateway.py $GATEWAY_PORT"
# ================= 启动 frpc =================
if [ -n "$FRPC_CONFIG" ] && [ -f "$FRPC_CONFIG" ]; then
if command -v frpc &>/dev/null; then
# 重启已运行的 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 服务已重启"
else
systemctl enable frpc --now 2>/dev/null && log_info "frpc 服务已通过 systemd 启动"
fi
else
restart_service "frpc"
nohup frpc -c "$FRPC_CONFIG" > /var/log/frpc.log 2>&1 &
echo $! > /var/run/frpc.pid
sleep 1
if kill -0 "$(cat /var/run/frpc.pid)" 2>/dev/null; then
log_info "frpc 已通过 nohup 启动 (PID: $(cat /var/run/frpc.pid))"
else
log_warn "frpc 启动失败,查看日志: /var/log/frpc.log"
fi
fi
else
log_warn "frpc 未安装,跳过启动。配置文件已生成: $FRPC_CONFIG"
fi
fi
# ================= 部署总结 =================
echo -e "\n${BLUE}==================================================${PLAIN}"
echo -e "${GREEN} 🎉 6小时长效缓存网关部署完成 🎉 ${PLAIN}"
echo -e "${BLUE}==================================================${PLAIN}"
echo -e "1. 动态网关状态:$(get_service_status huawei-gateway)"
echo -e "2. frpc 配置: $FRPC_CONFIG"
echo -e "3. 健康检查: http://127.0.0.1:${GATEWAY_PORT}/health"
echo -e "4. 您的本地一劳永逸调用地址:"
echo -e " - ${GREEN}API Base URL:${PLAIN} http://${FRPS_ADDR}:${REMOTE_PORT}/v2"
echo -e " - ${GREEN}API Key:${PLAIN} sk-anything"
echo -e " - ${GREEN}Model:${PLAIN} glm-5.1"
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"