OpenClaw多节点一键部署脚本(Ubuntu)

该脚本在Ubuntu中部署,支持Windows访问web控制台!

亲测好用,免去很多配置步骤,只需要填写一些基本的参数即可,但是没有Skill的安装步骤,需要自己去安装一下

在 OpenClaw 的日常运维与集群部署中,随着节点数量的增加,手动维护 JSON 配置文件、管理端口映射以及执行多节点启停等操作变得相对繁琐。为提升部署效率并规范化节点管理,本文分享一个基于 Bash 开发的 OpenClaw 多节点自动化管理脚本(OpenClaw Manager,简称 OCM)。

该脚本提供了交互式的终端菜单,集成了环境初始化、多模型厂商配置、智能端口分配及节点生命周期管理等功能,且通过CMD隧道,实现Windows访问

核心功能概述

  1. 自动化环境准备 脚本启动时将自动检测 jqNode.jsOpenClawpsmisc 等底层依赖。若检测到组件缺失,将自动配置国内镜像源并静默完成安装。

  2. 配置状态持久化 内置输入缓存机制,可自动记录历史使用的 API Key 与模型标识。用户在进行二次部署或新增节点时,可直接通过数字键快速复用历史配置,提高录入效率。

  3. 多厂商协议自动适配 内置硅基流动、Claude、OpenAI、Gemini、DeepSeek 等 12 家主流模型 API 供应商。用户选择对应的服务商后,系统会自动映射并配置对应的底层通信协议。

  4. 智能端口防冲突机制 采用全局端口分配池与实时端口探测技术。在批量拉起多个节点时,能够自动规避已被占用的端口,有序分配连续的可用端口,解决并发启动导致的端口冲突问题。

  5. 节点生命周期闭环管理 支持通过控制台对已部署的节点进行独立管理,包含:节点参数修改(端口、厂商、模型等)、热重启、单节点启停控制以及目录级安全销毁。

通过系统给出的命令进行CMD隧道建立

源码与部署指引

请在基于 Debian/Ubuntu 的终端中执行以下命令,生成并运行控制台:

复制代码
cat << 'EOF' > ~/openclaw_deploy.sh
#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════╗
# ║  OpenClaw Manager (OCM) v2.8 · 完美并发端口分配与全大厂阵列版    ║
# ╚══════════════════════════════════════════════════════════════════╝

set -uo pipefail

# ─── 颜色与变量 ──────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'
BASE_DIR="$HOME/.openclaw_nodes"
KEYS_FILE="$HOME/.oc_keys.txt"
MODELS_FILE="$HOME/.oc_models.txt"
ALLOCATED_PORTS=()

# 🌟 多厂商API (自动匹配专属协议)
TMPL_NAMES=(
    "硅基流动 SiliconFlow"
    "Anthropic Claude 官方"
    "OpenAI GPT 官方"
    "Google Gemini 官方"
    "DeepSeek 官方"
    "阿里云百炼 DashScope"
    "智谱清言 Zhipu"
    "月之暗面 Moonshot"
    "字节豆包 Ark"
    "xAI Grok"
    "Groq 高速"
    "自定义 URL"
)
TMPL_URLS=(
    "https://api.siliconflow.cn/v1"
    "https://api.anthropic.com/v1"
    "https://api.openai.com/v1"
    "https://generativelanguage.googleapis.com/v1beta"
    "https://api.deepseek.com/v1"
    "https://dashscope.aliyuncs.com/compatible-mode/v1"
    "https://open.bigmodel.cn/api/paas/v4"
    "https://api.moonshot.cn/v1"
    "https://ark.cn-beijing.volces.com/api/v3"
    "https://api.x.ai/v1"
    "https://api.groq.com/openai/v1"
    "custom"
)

log_info() { echo -e "${GREEN}[INFO]${RESET} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
log_error() { echo -e "${RED}[ERROR] $*${RESET}"; }
log_step() { echo -e "\n${BOLD}${MAGENTA}━━━ $* ━━━${RESET}"; }
log_success() { echo -e "${GREEN}${BOLD}✅  $*${RESET}"; }
pause() { read -n 1 -s -r -p $'\n\033[1;33m按任意键继续...\033[0m'; echo ""; }

safe_run() { local desc="$1"; shift; if ! "$@"; then log_error "$desc 失败"; exit 1; fi }
has_cmd() { command -v "$1" &>/dev/null; }
read_lines() { local f="$1"; local -n arr="$2"; arr=(); if [[ -f "$f" ]]; then while read -r line; do [[ -n "$line" ]] && arr+=("$line"); done < "$f"; fi; }
save_line() { 
    local f="$1"; local val="$2"
    grep -Fxq "$val" "$f" 2>/dev/null || echo "$val" >> "$f"
    tail -n 8 "$f" > "$f.tmp" && mv "$f.tmp" "$f"
}
menu_select() {
    local prompt="$1"; shift; local options=("$@") choice
    echo -e "${CYAN}  $prompt${RESET}" >&2
    for i in "${!options[@]}"; do
        if [[ $((i+1)) -lt 10 ]]; then echo -e "     ${BOLD}$((i+1))${RESET}) ${options[$i]}" >&2
        else echo -e "    ${BOLD}$((i+1))${RESET}) ${options[$i]}" >&2; fi
    done
    while true; do
        echo -ne "  ${CYAN}请输入数字并回车 (1-${#options[@]}): ${RESET}" >&2
        read -r choice
        if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#options[@]} )); then 
            echo "$choice"; return
        fi
    done
}
read_default() {
    local prompt="$1" default="$2" varname="$3" _input
    echo -ne "${CYAN}  ${prompt}${RESET} [默认: ${YELLOW}${default}${RESET}]: " >&2
    read -r _input
    printf -v "$varname" '%s' "${_input:-$default}"
}
# 🌟 核心修复 1:纯计算端口,不在子 Shell 中污染全局数组
get_avail_port() {
    local p=18810
    while ss -tuln | grep -q ":$p " || [[ " ${ALLOCATED_PORTS[*]:-} " =~ " ${p} " ]]; do ((p+=10)); done
    echo "$p"
}
kill_gateways() {
    ps aux | grep "[o]penclaw gateway" | awk '{print $2}' | xargs -r kill -9 2>/dev/null || true
}
check_and_install_env() {
    log_step "基础环境智能自检"
    local need_install=false
    if ! has_cmd jq || ! has_cmd node || ! has_cmd openclaw || ! has_cmd fuser; then need_install=true; fi
if $need_install; then
        log_warn "检测到缺少必要组件,开始自动安装部署环境..."
        safe_run "apt update" sudo apt-get update -q
        safe_run "安装基础依赖" sudo apt-get install -y -q curl git jq openssh-server ca-certificates python3 psmisc
        
        if ! has_cmd node; then 
            log_info "正在安装 Node.js..."
            curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs
        fi
        if ! has_cmd openclaw; then 
            log_info "正在安装 OpenClaw 引擎..."
            npm config set registry https://registry.npmmirror.com/ 
            safe_run "安装 OpenClaw" sudo npm install -g openclaw@latest
        fi
        log_success "环境安装完毕!"
    else
        log_success "环境检测通过,所有底层组件均已就绪。"
    fi
}
show_status() {
    clear; echo -e "${BOLD}${MAGENTA}╔════════════════ 当前节点运行状态 ════════════════╗${RESET}"
    local count=0
    if has_cmd jq; then
        for nd in "$BASE_DIR"/*; do
            if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
                ((count++))
                local cfg="$nd/.openclaw/openclaw.json"
                local name=$(basename "$nd")
                local port=$(jq -r '.gateway.port // "未知"' "$cfg" 2>/dev/null)
                local prov=$(jq -r '.models.providers | keys[0] // "未知"' "$cfg" 2>/dev/null)
                local model=$(jq -r ".models.providers[\"$prov\"].models[0].name // \"未知\"" "$cfg" 2>/dev/null)
                
                local status="${RED}已停止 ⭕${RESET}"
                if ss -tuln | grep -q ":$port "; then status="${GREEN}运行中 🟢${RESET}"; fi
                echo -e " ${BOLD}$name${RESET} | 端口: ${CYAN}$port${RESET} | 厂商: ${YELLOW}$prov${RESET} | 模型: $model | 状态: $status"
            fi
        done
    fi
    [[ $count -eq 0 ]] && echo -e "  ${DIM}暂无已部署的节点。${RESET}"
    echo -e "${BOLD}${MAGENTA}╚══════════════════════════════════════════════════╝${RESET}\n"
}
print_all_tunnels() {
    local LAN_IP=$(hostname -I | awk '{print $1}')
    local target_ports=(); local node_names=(); local node_links=()
for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
            local port=$(jq -r '.gateway.port' "$nd/.openclaw/openclaw.json" 2>/dev/null)
            local tok=$(jq -r '.gateway.auth.token' "$nd/.openclaw/openclaw.json" 2>/dev/null)
            local name=$(basename "$nd")
            if [[ -n "$port" ]]; then
                target_ports+=("$port"); node_names+=("$name")
                node_links+=("  $name → ${GREEN}http://127.0.0.1:$port/?token=$tok${RESET}")
            fi
        fi
    done
[[ ${#target_ports[@]} -eq 0 ]] && return
echo -e "\n${BOLD}${GREEN}╔══════════════════════════════════════════════════════╗${RESET}"
    echo -e "${BOLD}${GREEN}║              🏁  节点访问与隧道指引                  ║${RESET}"
    echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════╝${RESET}"
echo -e "\n${BOLD}${YELLOW}【1】Windows CMD 隧道命令 (两种方式任选其一):${RESET}"
    echo -e "  ${RED}⚠️ 注意:输入密码连通后,请将 CMD 窗口最小化,切勿随意关闭!${RESET}"
    
    echo -e "\n  ${CYAN}▶ 方式 A:单窗口全开 (推荐,只需开 1 个 CMD)${RESET}"
    local all_cmd="ssh"
    for p in "${target_ports[@]}"; do all_cmd+=" -L $p:127.0.0.1:$p"; done
    echo -e "    ${BOLD}${all_cmd} root@$LAN_IP${RESET}"
echo -e "\n  ${CYAN}▶ 方式 B:多窗口独立 (共 ${#target_ports[@]} 个节点,可分别在不同的 CMD 中运行)${RESET}"
    for ((i=0; i<${#target_ports[@]}; i++)); do
        echo -e "    ${node_names[$i]}: ${BOLD}ssh -L ${target_ports[$i]}:127.0.0.1:${target_ports[$i]} root@$LAN_IP${RESET}"
    done
echo -e "\n${BOLD}${YELLOW}【2】浏览器免密直达链接:${RESET}"
    for link in "${node_links[@]}"; do echo -e "$link"; done
    echo -e "${BOLD}${GREEN}══════════════════════════════════════════════════════${RESET}\n"
}
menu_view_info() {
    clear; echo -e "${BOLD}${MAGENTA}╔════════════════ 节点详细信息检视 ════════════════╗${RESET}"
    local count=0
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
            ((count++))
            local cfg="$nd/.openclaw/openclaw.json"
            local name=$(basename "$nd")
            local port=$(jq -r '.gateway.port // "未知"' "$cfg" 2>/dev/null)
            local tok=$(jq -r '.gateway.auth.token // "未生成"' "$cfg" 2>/dev/null)
            local prov=$(jq -r '.models.providers | keys[0] // "未知"' "$cfg" 2>/dev/null)
            local model=$(jq -r ".models.providers[\"$prov\"].models[0].name // \"未知\"" "$cfg" 2>/dev/null)
            local key=$(jq -r ".models.providers[\"$prov\"].apiKey // \"未知\"" "$cfg" 2>/dev/null)
            local display_key="未知"
            if [[ "$key" != "未知" && ${#key} -gt 10 ]]; then display_key="${key:0:5}******${key: -4}"; elif [[ "$key" != "未知" ]]; then display_key="******"; fi
echo -e "\n${BOLD}${BLUE}▶ $name${RESET}"
            echo -e "  - 端口: ${CYAN}$port${RESET} | 厂商: ${YELLOW}$prov${RESET} | 模型: $model"
            echo -e "  - 密钥: ${DIM}$display_key${RESET}"
            echo -e "  - Token: ${YELLOW}$tok${RESET}"
        fi
    done
    [[ $count -eq 0 ]] && echo -e "  ${DIM}暂无已部署的节点。${RESET}"
    echo -e "\n${BOLD}${MAGENTA}╚══════════════════════════════════════════════════╝${RESET}"
    print_all_tunnels
    pause
}
generate_provider_json() {
    local p_url="$1" p_key="$2" p_model="$3"
    local p_id="provider"; local p_api="openai-completions"
    
    if [[ "$p_url" == *"siliconflow"* ]]; then p_id="siliconflow"
    elif [[ "$p_url" == *"anthropic"* ]]; then p_id="anthropic"; p_api="anthropic-messages"
    elif [[ "$p_url" == *"openai"* ]]; then p_id="openai"
    elif [[ "$p_url" == *"googleapis"* ]]; then p_id="google"; p_api="google-generative-ai"
    elif [[ "$p_url" == *"dashscope"* ]]; then p_id="dashscope"
    elif [[ "$p_url" == *"deepseek"* ]]; then p_id="deepseek"
    elif [[ "$p_url" == *"bigmodel"* ]]; then p_id="zhipu"
    elif [[ "$p_url" == *"moonshot"* ]]; then p_id="moonshot"
    elif [[ "$p_url" == *"volces"* ]]; then p_id="ark"
    elif [[ "$p_url" == *"x.ai"* ]]; then p_id="xai"
    elif [[ "$p_url" == *"groq"* ]]; then p_id="groq"
    fi
echo $(jq -n --arg pid "$p_id" --arg api "$p_api" --arg apiKey "$p_key" --arg baseUrl "$p_url" \
        --arg mid "$p_model" '{($pid): {api:$api, apiKey:$apiKey, auth:"api-key", baseUrl:$baseUrl, models:[{id:$mid, name:$mid, api:$api, maxTokens:8192, input:["text"]}]}}')
}
collect_multi_nodes() {
    local total=$1
    MULTI_CONFIGS=()
    local p_urls=(); local p_keys=(); local p_models=()
log_step "步骤 1/3:配置【厂商 Base URL】"
    for (( i=1; i<=total; i++ )); do
        echo -e "\n${BOLD}${BLUE}▶ 正在为 Node_$i 选择厂商${RESET}"
        local u_idx=$(menu_select "选择厂商" "${TMPL_NAMES[@]}")
        local prov_url="${TMPL_URLS[$((u_idx-1))]}"
        if [[ "$prov_url" == "custom" ]]; then echo -ne "  输入自定义 URL: "; read -r prov_url; fi
        p_urls+=("$prov_url")
    done
log_step "步骤 2/3:配置【API Key】"
    read_lines "$KEYS_FILE" my_keys
    local k_opts=("🆕 手动输入新 API Key")
    for k in "${my_keys[@]}"; do if [[ ${#k} -gt 10 ]]; then k_opts+=("🗂️ 历史: ${k:0:5}******${k: -4}"); else k_opts+=("🗂️ 历史: ******"); fi; done
for (( i=1; i<=total; i++ )); do
        echo -e "\n${BOLD}${BLUE}▶ 正在为 Node_$i 配置 API Key${RESET}"
        local k_idx=$(menu_select "选择或输入 API Key" "${k_opts[@]}")
        local prov_key=""
        if [[ $k_idx -eq 1 ]]; then
            echo -ne "  请输入新 API Key (不回显): "; read -rs prov_key; echo ""
            save_line "$KEYS_FILE" "$prov_key"
            k_opts+=("🗂️ 历史: ${prov_key:0:5}******${prov_key: -4}")
            my_keys+=("$prov_key")
        else
            prov_key="${my_keys[$((k_idx-2))]}"
        fi
        p_keys+=("$prov_key")
    done
log_step "步骤 3/3:配置【模型 ID】"
    read_lines "$MODELS_FILE" my_models
    local m_opts=("🆕 手动输入新 Model ID")
    for m in "${my_models[@]}"; do m_opts+=("🗂️ 历史: $m"); done
for (( i=1; i<=total; i++ )); do
        echo -e "\n${BOLD}${BLUE}▶ 正在为 Node_$i 配置模型 ID${RESET}"
        local m_idx=$(menu_select "选择或输入模型 ID" "${m_opts[@]}")
        local prov_model=""
        if [[ $m_idx -eq 1 ]]; then
            echo -ne "  请输入模型 ID: "; read -r prov_model
            save_line "$MODELS_FILE" "$prov_model"
            m_opts+=("🗂️ 历史: $prov_model")
            my_models+=("$prov_model")
        else
            prov_model="${my_models[$((m_idx-2))]}"
        fi
        p_models+=("$prov_model")
    done
for (( i=1; i<=total; i++ )); do
        MULTI_CONFIGS+=("$(generate_provider_json "${p_urls[$((i-1))]}" "${p_keys[$((i-1))]}" "${p_models[$((i-1))]}")")
    done
}
start_and_mount() {
    local nd="$1" port="$2" prov_json="$3"
    log_info "正在启动 $(basename $nd) (绑定端口 $port)..."
    export OPENCLAW_HOME="$nd"
    openclaw config set gateway.port "$port" >/dev/null 2>&1
    openclaw config set gateway.mode local >/dev/null 2>&1
    openclaw config set gateway.auth false >/dev/null 2>&1
    nohup openclaw gateway > "$nd/node.log" 2>&1 &
    
    local waited=0 cfg="$nd/.openclaw/openclaw.json"
    while [[ ! -f "$cfg" ]] || [[ -z "$(jq -r '.gateway.auth.token // empty' "$cfg" 2>/dev/null)" ]]; do
        sleep 1; ((waited++)); echo -n "."; [[ $waited -gt 25 ]] && { log_error "\n$(basename $nd) Token 生成超时,请查看日志排错!"; return; }
    done
    # 🌟 核心修复 2:进度条完成后换行,解决日志挤压问题
    echo ""
    
    local tmp="${cfg}.tmp"
    jq --argjson p "$prov_json" '.gateway.controlUi.allowedOrigins = ["*"] | .models.providers = $p' "$cfg" > "$tmp" 2>/dev/null && mv "$tmp" "$cfg"
    
    fuser -k -9 "$port/tcp" >/dev/null 2>&1 || true; sleep 1
    nohup openclaw gateway >> "$nd/node.log" 2>&1 &
}
# ─── 核心功能:新环境部署 ─────────────────────────
menu_new_deploy() {
    local existing_nodes=0
    for nd in "$BASE_DIR"/*; do [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]] && ((existing_nodes++)); done
    
    if [[ $existing_nodes -gt 0 ]]; then
        log_warn "检测到当前已存在 $existing_nodes 个部署好的节点环境。"
        echo -ne "  确认要进行【新环境部署】吗?(y/N): "
        read -n 1 -s ans1; echo "$ans1"
        [[ ! "$ans1" =~ ^[Yy]$ ]] && return
        
        echo -ne "  是否需要【彻底清空】全部旧环境数据及节点?(y/N): "
        read -n 1 -s ans2; echo "$ans2"
        
        kill_gateways
        if [[ "$ans2" =~ ^[Yy]$ ]]; then
            log_info "正在彻底抹除旧环境目录..."
            rm -rf "$BASE_DIR"
        else
            log_info "保留旧环境目录,将在原有基础上进行覆盖/新增..."
        fi
    else
        echo -ne "  确认开始【新环境部署】吗?(y/N): "
        read -n 1 -s ans1; echo "$ans1"
        [[ ! "$ans1" =~ ^[Yy]$ ]] && return
        kill_gateways
    fi
    
    check_and_install_env
    
    echo -ne "\n  请输入本次需要部署的节点总数 (直接键入数字并回车): "
    read -r TOTAL
    if [[ ! "$TOTAL" =~ ^[1-9][0-9]*$ ]]; then log_error "输入无效"; pause; return; fi
ALLOCATED_PORTS=()
    collect_multi_nodes "$TOTAL"
log_step "步骤 4/4:点火与配置注入"
    local ports=()
    for (( i=1; i<=TOTAL; i++ )); do
        local p=$(get_avail_port)
        # 🌟 核心修复 3:在外层(主 Shell)显式记录已分配端口
        ALLOCATED_PORTS+=("$p")
        ports+=("$p")
        local nd_name="node_$i"; mkdir -p "$BASE_DIR/$nd_name"
        start_and_mount "$BASE_DIR/$nd_name" "$p" "${MULTI_CONFIGS[$((i-1))]}"
    done
    print_all_tunnels; pause
}
# ─── 其他节点管理功能 ───────────────────────────────────────────
menu_add_node() {
    check_and_install_env
    local next_idx=1
    while [[ -d "$BASE_DIR/node_$next_idx" ]]; do ((next_idx++)); done
    local nd_name="node_$next_idx"
    ALLOCATED_PORTS=()
    local port=$(get_avail_port)
    ALLOCATED_PORTS+=("$port")
    
    echo -e "\n${BOLD}${BLUE}┌─ 新增节点 ──────────────────────────────────────┐${RESET}"
    read_default "自定义节点名称" "$nd_name" final_name
    collect_multi_nodes 1
    echo -e "${BOLD}${BLUE}└─────────────────────────────────────────────────┘${RESET}"
    
    mkdir -p "$BASE_DIR/$final_name"
    start_and_mount "$BASE_DIR/$final_name" "$port" "${MULTI_CONFIGS[0]}"
    log_success "节点 $final_name 启动成功!"
    print_all_tunnels; pause
}
menu_edit_node() {
    local nodes=(); local opts=()
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then nodes+=("$nd"); opts+=("修改 $(basename "$nd")"); fi
    done
    if [[ ${#nodes[@]} -eq 0 ]]; then log_warn "没有可编辑的节点。"; pause; return; fi
    opts+=("返回上一级")
local choice=$(menu_select "选择要编辑的节点" "${opts[@]}")
    if [[ $choice -le ${#nodes[@]} ]]; then
        local target_dir="${nodes[$((choice-1))]}"
        local old_name=$(basename "$target_dir")
        local old_port=$(jq -r '.gateway.port // ""' "$target_dir/.openclaw/openclaw.json" 2>/dev/null)
log_info "正在停止 $old_name 以便修改配置..."
        fuser -k -9 "$old_port/tcp" >/dev/null 2>&1 || true
echo -e "\n${BOLD}${BLUE}┌─ 编辑节点参数 ──────────────────────────────────────┐${RESET}"
        read_default "节点名称 (目录名)" "$old_name" new_name
        local new_dir="$BASE_DIR/$new_name"
        if [[ "$new_name" != "$old_name" ]]; then
            if [[ -d "$new_dir" ]]; then log_error "目标名称 $new_name 已存在!取消重命名。"; new_name="$old_name"; new_dir="$target_dir"
            else mv "$target_dir" "$new_dir"; fi
        fi
ALLOCATED_PORTS=()
        collect_multi_nodes 1
        echo -e "${BOLD}${BLUE}└─────────────────────────────────────────────────────┘${RESET}"
start_and_mount "$new_dir" "$old_port" "${MULTI_CONFIGS[0]}"
        log_success "节点 $old_name 已成功更新!面板及命令已自动刷新。"
        print_all_tunnels; pause
    fi
}
menu_toggle_node() {
    local dirs=(); local ports=(); local opts=(); local statuses=()
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
            local port=$(jq -r '.gateway.port' "$nd/.openclaw/openclaw.json" 2>/dev/null)
            local name=$(basename "$nd")
            dirs+=("$nd"); ports+=("$port")
            if ss -tuln | grep -q ":$port "; then
                opts+=("$name (端口 $port) - 🟢 运行中 -> ${RED}点击关闭${RESET}")
                statuses+=("running")
            else
                opts+=("$name (端口 $port) - ⭕ 已停止 -> ${GREEN}点击启动${RESET}")
                statuses+=("stopped")
            fi
        fi
    done
if [[ ${#dirs[@]} -eq 0 ]]; then log_warn "当前没有已配置的节点。"; pause; return; fi
    opts+=("返回上一级")
local choice=$(menu_select "选择要操作的节点" "${opts[@]}")
    if [[ $choice -le ${#dirs[@]} ]]; then
        local idx=$((choice-1)); local t_dir="${dirs[$idx]}"; local t_port="${ports[$idx]}"; local t_name=$(basename "$t_dir"); local t_status="${statuses[$idx]}"
        if [[ "$t_status" == "running" ]]; then
            log_info "正在停止 $t_name (强杀端口 $t_port)..."
            fuser -k -9 "$t_port/tcp" >/dev/null 2>&1 || true
            log_success "$t_name 已关闭。"
        else
            log_info "正在唤醒 $t_name (绑定端口 $t_port)..."
            export OPENCLAW_HOME="$t_dir"
            nohup openclaw gateway >> "$t_dir/node.log" 2>&1 &
            sleep 2
            if ss -tuln | grep -q ":$t_port "; then log_success "$t_name 已成功启动!"
            else log_error "$t_name 启动失败,请查看日志。"
            fi
        fi
        pause
    fi
}
menu_delete_node() {
    local dirs=(); local opts=()
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
            dirs+=("$nd"); opts+=("销毁 $(basename "$nd")")
        fi
    done
    if [[ ${#dirs[@]} -eq 0 ]]; then log_warn "当前没有可删除的节点。"; pause; return; fi
    opts+=("返回上一级")
local choice=$(menu_select "选择要彻底删除的节点" "${opts[@]}")
    if [[ $choice -le ${#dirs[@]} ]]; then
        local t_dir="${dirs[$((choice-1))]}"; local t_name=$(basename "$t_dir")
        local t_port=$(jq -r '.gateway.port' "$t_dir/.openclaw/openclaw.json" 2>/dev/null)
        
        echo -ne "${RED}⚠️  警告:确定要彻底删除 $t_name 及所有配置吗?(y/N): ${RESET}"
        read -n 1 -s ans; echo "$ans"
        if [[ "$ans" =~ ^[Yy]$ ]]; then
            log_info "正在清理进程与目录..."
            fuser -k -9 "$t_port/tcp" >/dev/null 2>&1 || true
            rm -rf "$t_dir"
            log_success "$t_name 已被彻底删除!"
        else
            log_info "已取消删除。"
        fi
        pause
    fi
}
menu_start_existing() {
    log_info "正在唤醒所有已停止的节点..."
    local started=0
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" && -f "$nd/.openclaw/openclaw.json" ]]; then
            local port=$(jq -r '.gateway.port' "$nd/.openclaw/openclaw.json" 2>/dev/null)
            if ! ss -tuln | grep -q ":$port "; then
                export OPENCLAW_HOME="$nd"
                nohup openclaw gateway >> "$nd/node.log" 2>&1 &
                ((started++))
            fi
        fi
    done
    [[ $started -eq 0 ]] && log_info "所有节点都在运行中,无需唤醒。" || log_success "共唤醒 $started 个节点。"
    pause
}
menu_view_logs() {
    local dirs=(); local opts=()
    for nd in "$BASE_DIR"/*; do
        if [[ -d "$nd" ]]; then dirs+=("$nd"); opts+=("查看 $(basename "$nd") 实时日志"); fi
    done
    if [[ ${#dirs[@]} -eq 0 ]]; then log_warn "当前没有节点。"; pause; return; fi
    opts+=("返回上一级")
local choice=$(menu_select "选择要查看日志的节点" "${opts[@]}")
    if [[ $choice -le ${#dirs[@]} ]]; then
        local target_log="${dirs[$((choice-1))]}/node.log"
        clear
        echo -e "${BOLD}${CYAN}实时日志流 ( $(basename "${dirs[$((choice-1))]}") ) ${RESET}"
        echo -e "${DIM}提示:按 Ctrl+C 退出日志查看,不会影响节点运行。${RESET}"
        echo -e "──────────────────────────────────────────────────────────"
        
        trap '' INT
        tail -f -n 30 "$target_log" || echo "日志文件为空或不存在。"
        trap - INT
        
        echo -e "\n${GREEN}已退出日志面板。${RESET}"
        pause
    fi
}
# ─── 主路由 ──────────────────────────────────────────────────────
while true; do
    show_status
    echo -e "${CYAN}请直接按数字键选择操作:${RESET}"
    echo "  1) 🆕 新环境部署 (智能环境检测/批量建站)"
    echo "  2) 🚀 唤醒所有 (一键启动所有已停止节点)"
    echo "  3) ⚙️  节点管理 (新增 / 修改 / 启停 / 删除)"
    echo "  4) 📄 查看详细信息与链接"
    echo "  5) 📜 实时查看节点日志"
    echo "  0) ❌ 退出"
    read -n 1 -s main_choice; echo ""
case "$main_choice" in
        1) menu_new_deploy ;;
        2) menu_start_existing ;;
        3) 
            while true; do
                show_status
                echo -e "${CYAN}节点管理菜单 (直接按数字键):${RESET}"
                echo "  1) ➕ 新增一个节点"
                echo "  2) ✏️  修改已有节点"
                echo "  3) 🟢/🛑 启停指定节点"
                echo "  4) 🗑️  彻底删除节点"
                echo "  0) ↩️  返回主菜单"
                read -n 1 -s sub_choice; echo ""
                case "$sub_choice" in
                    1) menu_add_node ;;
                    2) menu_edit_node ;;
                    3) menu_toggle_node ;;
                    4) menu_delete_node ;;
                    0) break ;;
                esac
            done
            ;;
        4) menu_view_info ;;
        5) menu_view_logs ;;
        0) echo "再见!"; exit 0 ;;
    esac
done
EOF

调整权限并启动

复制代码
chmod +x ~/openclaw_deploy.sh
~/openclaw_deploy.sh

直接复制代码到命令行中进行回车再添加权限启动即可

相关推荐
landuochong2002 小时前
OpenClaw 架构文档
人工智能·架构·openclaw
cxr8282 小时前
OpenClaw与NetLogo之间的调用与数据交互机制
人工智能·交互·netlogo·openclaw
码克疯v12 小时前
OpenClaw 安装与入门:从零到跑通 Gateway(详细可操作)
gateway·openclaw·龙虾
七夜zippoe3 小时前
OpenClaw 接入 Discord:从零开始
大数据·人工智能·microsoft·discord·openclaw
GISer_Jing3 小时前
Agent开发学习进展总结
ai·前端框架·aigc
阆遤4 小时前
利用TRAE对nanobot进行安全分析并优化
python·安全·ai·trae·nanobot
SimonLiu0095 小时前
【OpenClaw】Openclaw开放局域网访问
局域网·openclaw
熊猫钓鱼>_>6 小时前
使用腾讯云 ClawPro 助手打造南京旅游攻略应用实践
人工智能·云计算·腾讯云·skills·adp·openclaw·claw
木斯佳6 小时前
前端八股文面经大全:腾讯前端暑期AI面(2026-03-26)·面经深度解析
前端·人工智能·ai·智能体·暑期实习