Download scripts/ccmr-wrapper.sh from SZFSPC/page: direct link, hf CLI and curl.
- Browser
- Download file 8.03 kB
-
https://huggingface.co/spaces/SZFSPC/page/resolve/main/scripts/ccmr-wrapper.sh
- Command line
-
hf download hf://spaces/SZFSPC/page/scripts/ccmr-wrapper.sh
-
curl -L -o ccmr-wrapper.sh https://huggingface.co/spaces/SZFSPC/page/resolve/main/scripts/ccmr-wrapper.sh
8.03 kB
| # ============================================================ | |
| # CCMR (Claude Code Model Router) Supervisor 启动包装脚本 | |
| # | |
| # 功能说明: | |
| # 作为 Supervisor 子进程管理 CCMR 网关的完整生命周期 | |
| # 支持文件热加载和进程崩溃自动恢复 | |
| # | |
| # 工作流程: | |
| # 1. 启动时读取 /root/.env.d/ccmr.env(如果存在) | |
| # 2. 检查 CCMR_ENABLED 是否为 true | |
| # - false: exit 0,Supervisor 视作正常退出不再拉起 | |
| # - true: 进入内部重启循环 | |
| # 3. 每次循环迭代: | |
| # a) 重新 source ccmr.env(热加载最新配置) | |
| # b) 调用 ccmr-setup.sh 重新生成 .env 和 models.yaml | |
| # c) 启动 ccmr start --port $CCMR_PORT & | |
| # d) 后台启动 inotifywait 监听 ccmr.env 文件变化 | |
| # e) 轮询检测 CCMR 进程存活(每 $CCMR_RELOAD_INTERVAL 秒) | |
| # f) 检测到文件变化或进程崩溃 -> 杀死旧进程 -> goto (a) | |
| # | |
| # 文件热加载机制: | |
| # - inotify 模式: 后台 inotifywait -m 持续监听 ccmr.env | |
| # 当文件被 modify/attrib/move/delete 时,写入 /tmp/.ccmr_config_changed 标记 | |
| # 主循环在下次轮询时检测到标记,立即触发重启 | |
| # - 轮询模式: 没有 inotify 时每 5 秒检查文件 mtime | |
| # - 两种模式都确保: 编辑保存 ccmr.env 后 <= 5 秒生效 | |
| # | |
| # Supervisor 行为: | |
| # - 未启用时 exit 0(Supervisor 不再重启) | |
| # - 启用后 while true 内部循环,永不退出 | |
| # - 如果 wrapper 脚本自身崩溃(set -e 触发),Supervisor 重新拉起 | |
| # | |
| # 所有支持的环境变量(共 10 个 API Key + 3 个系统设置): | |
| # | |
| # 系统设置: | |
| # CCMR_ENABLED - 是否启用 CCMR (true/false, 默认 false) | |
| # CCMR_PORT - 网关监听端口 (默认 8080) | |
| # CCMR_CONFIG_DIR - 配置存放目录 (默认 ~/.ccmr) | |
| # CCMR_CONFIG_FILE - 配置文件路径 (默认 /root/.env.d/ccmr.env) | |
| # CCMR_RELOAD_INTERVAL - 文件变化轮询间隔秒数 (默认 5) | |
| # | |
| # API Key 列表(按平台分组): | |
| # 1) DeepSeek: CCMR_DEEPSEEK_API_KEY=sk-xxx | |
| # 2) 通义千问 / Qwen: CCMR_QWEN_API_KEY=sk-xxx | |
| # 3) Kimi / Moonshot: CCMR_KIMI_API_KEY=sk-xxx | |
| # 4) 智谱 GLM: CCMR_GLM_API_KEY=xxx | |
| # 5) MiniMax CN: CCMR_MINIMAX_API_KEY=xxx | |
| # 6) MiniMax Global: CCMR_MINIMAX_GLOBAL_API_KEY=xxx | |
| # 7) MiMo SGP: CCMR_MIMO_API_KEY=tp-xxx | |
| # 8) MiMo CN: CCMR_MIMO_TOKEN_CN_API_KEY=tp-xxx | |
| # 9) MiMo AMS: CCMR_MIMO_TOKEN_AMS_API_KEY=tp-xxx | |
| # 10) MiMo Pay-as-you-go: CCMR_MIMO_PAYG_API_KEY=sk-xxx | |
| # | |
| # ============================================================ | |
| set -euo pipefail | |
| # ============================================================ | |
| # 从文件加载 CCMR 环境变量(优先级高于 Docker 环境变量) | |
| # ============================================================ | |
| CCMR_CONFIG_FILE="${CCMR_CONFIG_FILE:-/root/.env.d/ccmr.env}" | |
| if [[ -f "$CCMR_CONFIG_FILE" ]]; then | |
| # shellcheck source=/dev/null | |
| source "$CCMR_CONFIG_FILE" | |
| fi | |
| CCMR_ENABLED="${CCMR_ENABLED:-false}" | |
| CCMR_PORT="${CCMR_PORT:-8080}" | |
| CCMR_CONFIG_DIR="${CCMR_CONFIG_DIR:-$HOME/.ccmr}" | |
| CCMR_RELOAD_INTERVAL="${CCMR_RELOAD_INTERVAL:-5}" | |
| # 未启用时进入空闲等待(保持进程存在,避免 supervisord 认为启动失败 → FATAL | |
| if [[ "$CCMR_ENABLED" != "true" ]]; then | |
| echo "[CCMR-WRAPPER] CCMR is disabled (CCMR_ENABLED != true). Entering idle loop to keep supervisord happy." | |
| echo "[CCMR-WRAPPER] To enable CCMR: set CCMR_ENABLED=true in ${CCMR_CONFIG_FILE} and restart this program." | |
| while true; do | |
| sleep 3600 | |
| done | |
| fi | |
| echo "[CCMR-WRAPPER] CCMR is enabled, entering restart loop..." | |
| echo "[CCMR-WRAPPER] Config file: ${CCMR_CONFIG_FILE}" | |
| echo "[CCMR-WRAPPER] Watching for changes every ${CCMR_RELOAD_INTERVAL}s" | |
| # 检查 inotifywait 是否可用 | |
| USE_INOTIFY=false | |
| if command -v inotifywait &>/dev/null; then | |
| USE_INOTIFY=true | |
| echo "[CCMR-WRAPPER] Using inotifywait for efficient file monitoring" | |
| else | |
| echo "[CCMR-WRAPPER] inotifywait not available, using polling mode" | |
| fi | |
| # ============================================================ | |
| # 内部重启循环 | |
| # ============================================================ | |
| restart_count=0 | |
| while true; do | |
| restart_count=$((restart_count + 1)) | |
| echo "[CCMR-WRAPPER] === Restart #${restart_count} ===" | |
| # 重新加载配置文件(避免超长运行后环境变量过期) | |
| if [[ -f "$CCMR_CONFIG_FILE" ]]; then | |
| # shellcheck source=/dev/null | |
| source "$CCMR_CONFIG_FILE" | |
| CCMR_PORT="${CCMR_PORT:-8080}" | |
| fi | |
| # 重新生成 CCMR 配置(.env + models.yaml) | |
| /usr/local/bin/ccmr-setup.sh | |
| # 检查 API Key 是否已配置 | |
| if ! grep -q '=' "$CCMR_CONFIG_DIR/.env" 2>/dev/null || \ | |
| ! grep -v '^#' "$CCMR_CONFIG_DIR/.env" | grep -q '=' 2>/dev/null; then | |
| echo "[CCMR-WRAPPER] WARNING: No API keys configured yet." | |
| echo "[CCMR-WRAPPER] CCMR will start but may be non-functional." | |
| echo "[CCMR-WRAPPER] Create ${CCMR_CONFIG_FILE} with CCMR_*_API_KEY entries." | |
| fi | |
| echo "[CCMR-WRAPPER] Starting CCMR gateway on port ${CCMR_PORT}..." | |
| echo "[CCMR-WRAPPER] Config dir: ${CCMR_CONFIG_DIR}" | |
| echo "[CCMR-WRAPPER] Command: ccmr start --port ${CCMR_PORT}" | |
| # 启动 CCMR 网关(后台运行) | |
| cd "$CCMR_CONFIG_DIR" | |
| export NODE_ENV=production | |
| ccmr start --port "$CCMR_PORT" & | |
| CCMR_PID=$! | |
| echo "[CCMR-WRAPPER] CCMR gateway started (PID: ${CCMR_PID})" | |
| # ========================================================== | |
| # 双路监控:同时监听配置文件变化 和 CCMR 进程状态 | |
| # ========================================================== | |
| restart_reason="" | |
| CHANGE_FLAG="/tmp/.ccmr_config_changed" | |
| if [[ "$USE_INOTIFY" == "true" ]]; then | |
| # inotify 模式:后台 watcher 监听文件变化 | |
| # 检测到变化后写入标记文件,主循环轮询检测 | |
| rm -f "$CHANGE_FLAG" | |
| ( | |
| mkdir -p "$(dirname "$CCMR_CONFIG_FILE")" | |
| inotifywait -q -m --format '%f' \ | |
| -e modify,attrib,move,delete \ | |
| "$(dirname "$CCMR_CONFIG_FILE")" 2>/dev/null \ | |
| | while read -r fname; do | |
| if [[ "$fname" == "ccmr.env" ]]; then | |
| touch "$CHANGE_FLAG" | |
| exit 0 | |
| fi | |
| done | |
| ) & | |
| WATCHER_PID=$! | |
| fi | |
| # 轮询 loop:检测 CCMR 进程存活 和 文件变化标记 | |
| # 如果 inotify 检测到变化,标记文件会在下一次轮询前被写入 | |
| while kill -0 "$CCMR_PID" 2>/dev/null; do | |
| sleep "$CCMR_RELOAD_INTERVAL" | |
| if [[ -f "$CHANGE_FLAG" ]]; then | |
| restart_reason="config_file_changed" | |
| break | |
| fi | |
| done | |
| # 如果 CCMR 已退出且不是文件变化导致,标记为进程崩溃 | |
| if [[ -z "$restart_reason" ]] && ! kill -0 "$CCMR_PID" 2>/dev/null; then | |
| restart_reason="process_crashed" | |
| fi | |
| # 清理:停止 inotify watcher 和标记文件 | |
| if [[ -n "${WATCHER_PID:-}" ]]; then | |
| kill "$WATCHER_PID" 2>/dev/null || true | |
| wait "$WATCHER_PID" 2>/dev/null || true | |
| fi | |
| rm -f "$CHANGE_FLAG" | |
| # 输出重启原因 | |
| case "$restart_reason" in | |
| config_file_changed) | |
| echo "[CCMR-WRAPPER] Config file changed. Restarting gateway..." | |
| ;; | |
| process_crashed) | |
| echo "[CCMR-WRAPPER] CCMR process exited unexpectedly (PID: ${CCMR_PID}). Restarting..." | |
| ;; | |
| *) | |
| echo "[CCMR-WRAPPER] Monitoring loop exited (unknown reason). Restarting..." | |
| ;; | |
| esac | |
| # 确保旧进程已停止 | |
| if kill -0 "$CCMR_PID" 2>/dev/null; then | |
| echo "[CCMR-WRAPPER] Stopping CCMR (PID: ${CCMR_PID})..." | |
| kill "$CCMR_PID" 2>/dev/null || true | |
| sleep 1 | |
| if kill -0 "$CCMR_PID" 2>/dev/null; then | |
| kill -9 "$CCMR_PID" 2>/dev/null || true | |
| fi | |
| wait "$CCMR_PID" 2>/dev/null || true | |
| echo "[CCMR-WRAPPER] CCMR stopped" | |
| fi | |
| # 文件变化时立即重启,否则等一小段时间 | |
| if [[ "$restart_reason" != "config_file_changed" ]]; then | |
| echo "[CCMR-WRAPPER] Restarting in ${CCMR_RELOAD_INTERVAL}s..." | |
| sleep "$CCMR_RELOAD_INTERVAL" | |
| fi | |
| done | |