#!/usr/bin/env bash # 🪐 123ALL Bot 无头守护进程 (Headless Daemon) 安装脚本 # # ════════════════════════════════════════════════════════════════════════════ # [2026-10-02 重写] 上一版有三个致命缺陷,合起来的效果是 # **"安装成功"的提示与真实状态无关**: # # ① `ExecStart=/bin/sh -c 'echo "...connected to gateway." >> daemon.log'` # —— 它往日志里写了一句"已连接网关",**却从未启动守护进程**。 # 运维 `systemctl status` 看到 active(running) + 日志里有"connected", # 于是排到别处去了。 # ② 配置里写 `${GATEWAY_URL}/v1/bot/tunnel`,而路由挂在 `/api/v1/bot/tunnel` # —— 少一层 `/api`,真连上去也是 404。 # ③ 脚本从不下载/安装任何二进制,`--token=` 也不校验形态。 # # 现在:**装的是真二进制、跑的是真进程、装完必须验证它真的入网**。 # 验证不过就退出非零(不许打"安装成功")。 # # 用法: # install_123all_bot.sh --token=nje1... \ # --binary=/path/to/123all-bot-daemon \ # [--gateway=https://api.123all.ai] [--host-id=...] [--install-dir=/opt/123all-bot] # # 或 --binary-url=https://.../123all-bot-daemon(curl 下载;建议同时给 --sha256=) # ════════════════════════════════════════════════════════════════════════════ set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; CYAN='\033[0;36m'; NC='\033[0m' log() { echo -e "${CYAN}>>>${NC} $*"; } ok() { echo -e "${GREEN}[✓]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } die() { echo -e "${RED}[✗]${NC} $*" >&2; exit 1; } ENROLL_TOKEN="" GATEWAY_URL="https://api.123all.ai" BINARY_SRC="" BINARY_URL="" BINARY_SHA256="" HOST_ID="" INSTALL_DIR="/opt/123all-bot" SERVICE_NAME="123all-bot" SKIP_SYSTEMD=0 while [[ $# -gt 0 ]]; do case $1 in --token=*) ENROLL_TOKEN="${1#*=}"; shift ;; --gateway=*) GATEWAY_URL="${1#*=}"; shift ;; --binary=*) BINARY_SRC="${1#*=}"; shift ;; --binary-url=*) BINARY_URL="${1#*=}"; shift ;; --sha256=*) BINARY_SHA256="${1#*=}"; shift ;; --host-id=*) HOST_ID="${1#*=}"; shift ;; --install-dir=*) INSTALL_DIR="${1#*=}"; shift ;; --no-systemd) SKIP_SYSTEMD=1; shift ;; *) die "未知参数:$1(见脚本头部用法)" ;; esac done [[ -n "$ENROLL_TOKEN" ]] || die "必须提供入网令牌:--token=nje1... 没有令牌**不许装** —— 上一版会带着一个假令牌把服务装起来,然后一路静默连不上。" # ── 令牌形态先在本地卡住:形态不对就不必浪费一次安装 ── [[ "$ENROLL_TOKEN" == nje1.* ]] || die "令牌形态不对:入网令牌必须以 nje1. 开头(收到的是 '${ENROLL_TOKEN:0:8}…')" IFS='.' read -r _tk_pfx TK_SCOPE TK_EXP _tk_sig <<<"$ENROLL_TOKEN" [[ -n "${TK_SCOPE:-}" && -n "${TK_EXP:-}" ]] || die "令牌形态不对:缺少 scope/exp 段(应为 nje1...)" [[ "$TK_EXP" =~ ^[0-9]+$ ]] || die "令牌形态不对:exp 段应当是 unix 秒(收到 '$TK_EXP')" NOW_S=$(date +%s) (( TK_EXP > NOW_S )) || die "令牌已过期(exp=$TK_EXP < now=$NOW_S)—— 换一张再装,别让节点带着死令牌反复 401" echo -e "${CYAN}======================================================== 🪐 123ALL Bot Headless Daemon Installer (v3.0) 真二进制 · 真进程 · 装完验证入网 ========================================================${NC}" # ── [1/5] 取二进制 ── log "[1/5] 准备守护进程二进制" mkdir -p "${INSTALL_DIR}/bin" "${INSTALL_DIR}/config" "${INSTALL_DIR}/logs" "${INSTALL_DIR}/state" BIN_PATH="${INSTALL_DIR}/bin/123all-bot-daemon" if [[ -n "$BINARY_SRC" ]]; then [[ -f "$BINARY_SRC" ]] || die "--binary=$BINARY_SRC 不存在" install -m 0755 "$BINARY_SRC" "${BIN_PATH}.new" elif [[ -n "$BINARY_URL" ]]; then command -v curl >/dev/null 2>&1 || die "需要 curl 下载 --binary-url" curl -fsSL "$BINARY_URL" -o "${BIN_PATH}.new" || die "下载失败:$BINARY_URL" chmod 0755 "${BIN_PATH}.new" else die "必须提供二进制来源:--binary=/path/to/123all-bot-daemon 或 --binary-url=https://... 上一版脚本**根本不装二进制**,只写配置和一个 echo 的 systemd 单元, 然后打印「安装成功」—— 所以这里宁可失败也不假装成功。" fi if [[ -n "$BINARY_SHA256" ]]; then actual=$(sha256sum "${BIN_PATH}.new" | cut -d' ' -f1) [[ "$actual" == "$BINARY_SHA256" ]] || die "校验和不符:期望 $BINARY_SHA256,实际 $actual" ok "二进制校验和一致:${actual:0:16}…" fi # ── [2/5] 二进制自检:**它必须真的 fail-closed** ── # 这条同时验证"装的是对的二进制"和"它不会带着缺省令牌启动": # 配置文件不存在 ⇒ 退出码 78(配置问题),且**不得**出现任何"已连接"。 set +e selfcheck_out=$("${BIN_PATH}.new" --config=/nonexistent/daemon.json 2>&1) selfcheck_rc=$? set -e if [[ $selfcheck_rc -ne 78 ]]; then die "二进制自检失败:缺配置时应以退出码 78 结束,实际 $selfcheck_rc —— 这个二进制不是我们期望的那个(旧版会用缺省令牌继续启动)。输出:${selfcheck_out:0:200}" fi ok "二进制自检通过(缺配置即退出 78,不带缺省令牌启动)" mv -f "${BIN_PATH}.new" "${BIN_PATH}" # ── [3/5] host_id:跨重启稳定,且**与令牌 scope 对齐** ── log "[3/5] 生成节点身份" if [[ -z "$HOST_ID" ]]; then if [[ -r /etc/machine-id ]]; then MID="$(tr -d '\n' "${INSTALL_DIR}/config/daemon.json" </dev/null || echo 123all-node)", "capabilities": ["shell", "browser", "code_eval", "file_sync"], "heartbeat_interval_sec": 15, "shell": { "enabled": true, "timeout_sec": 60, "max_output_bytes": 262144 } } JSON chmod 0600 "${INSTALL_DIR}/config/daemon.json" ok "配置已写入(0600,含令牌,勿外传)" # ── [5/5] 装服务并**验证真的入网** ── log "[5/5] 前台连通性验证(--once)" set +e once_out=$("${BIN_PATH}" --config="${INSTALL_DIR}/config/daemon.json" --once 2>&1) once_rc=$? set -e echo "$once_out" | sed 's/^/ /' if [[ $once_rc -ne 0 ]]; then die "连通性验证失败(退出码 $once_rc)。 —— 不要在这里打"安装成功"。请按上面日志里的原因处理(令牌被拒 / 地址不对 / 网络不通)。" fi if ! grep -q "服务端已确认入网" <<<"$once_out"; then die "连通性验证拿到的是「没断开、但也没有服务端确认」的结果 —— 这正说明隧道没真正建立。 日志:$(echo "$once_out" | tail -5 | tr '\n' ' ')" fi ok "节点已入网(服务端确认 host_id 与令牌)" if [[ $SKIP_SYSTEMD -eq 1 ]] || ! command -v systemctl >/dev/null 2>&1; then warn "未安装 systemd 单元(--no-systemd 或系统无 systemctl)。 手动常驻:${BIN_PATH} --config=${INSTALL_DIR}/config/daemon.json" else cat > "/etc/systemd/system/${SERVICE_NAME}.service" < daemon.log') ExecStart=${BIN_PATH} --config=${INSTALL_DIR}/config/daemon.json Restart=always RestartSec=5 # 令牌缺失/配置坏掉时守护进程以 78 退出:不要无限快速重启,也不要当成网络问题 RestartPreventExitStatus=78 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable "${SERVICE_NAME}.service" >/dev/null systemctl restart "${SERVICE_NAME}.service" # **服务起来之后再看一眼**:active + 日志里有入网确认,两个都要 sleep 3 if ! systemctl is-active --quiet "${SERVICE_NAME}.service"; then die "服务没有处于 active 状态:systemctl status ${SERVICE_NAME}" fi ok "systemd 服务 active" fi echo -e "${CYAN}======================================================== ✨ 无头节点已上线并完成入网 - 节点 id:${HOST_ID} - 网关:${GATEWAY_URL}(运行时归一化为 /api/v1/bot/tunnel) - 日志:journalctl -u ${SERVICE_NAME} -f - 卸载:systemctl disable --now ${SERVICE_NAME}; rm -rf ${INSTALL_DIR} ========================================================${NC}"