#!/bin/sh # ============================================================================== # bolt 一键安装脚本 (支持 stdio / Streamable HTTP) # # 快速执行 (交互式向导 - 推荐): # curl -fsSL bolt.jocay.qzz.io | bash # # 非交互直接安装 (静默模式): # curl -fsSL bolt.jocay.qzz.io | bash -s -- -k 你的密钥 -p 8080 -y # 或通过环境变量: # KEY=你的密钥 PORT=8080 curl -fsSL bolt.jocay.qzz.io | bash -s -- -y # # 选项: # -k, --key, --token 设置 HTTP Bearer 认证令牌 # -p, --port 监听端口 (默认: 8080) # --http 自定义完整监听地址 (如 0.0.0.0:8080) # --allow 允许执行的命令白名单 (逗号分隔, 留空允许全部) # --deny 禁止执行的命令黑名单 (逗号分隔) # --timeout-ms 单条命令执行超时时间 (毫秒, 默认 30000) # -i, --interactive 强制进入交互式配置向导 # -y, --yes 非交互模式 (跳过确认与交互提示,直接使用默认或指定参数) # --binary 指定二进制来源 (本地文件路径 或 自定义下载 URL) # --no-start 仅安装配置,不立即启动服务 # --no-enable 不注册开机自启 # -h, --help 显示帮助信息 # ============================================================================== set -u # 配色输出函数 say() { printf '\033[1;32m[bolt]\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[bolt 提示]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[bolt 错误]\033[0m %s\n' "$*"; exit 1; } # 基础环境变量与默认值 TOKEN="${KEY:-${BOLT_KEY:-${TOKEN:-${BOLT_TOKEN:-}}}}" PORT="${PORT:-${BOLT_PORT:-8080}}" HTTP_ADDR="${HTTP_ADDR:-}" ALLOW="${ALLOW:-${BOLT_ALLOW:-}}" DENY="${DENY:-${BOLT_DENY:-}}" TIMEOUT_MS="${TIMEOUT_MS:-${BOLT_TIMEOUT_MS:-30000}}" BINARY_SRC="${BINARY_SRC:-${BOLT_BINARY_SRC:-}}" FORCE_INTERACTIVE=0 NON_INTERACTIVE=0 NO_START=0 NO_ENABLE=0 CLI_PARAM_GIVEN=0 # 参数解析 while [ $# -gt 0 ]; do case "$1" in -k|--key|--token) [ $# -ge 2 ] || die "$1 选项需要参数" TOKEN="$2"; CLI_PARAM_GIVEN=1; shift 2 ;; -p|--port) [ $# -ge 2 ] || die "$1 选项需要参数" PORT="$2"; CLI_PARAM_GIVEN=1; shift 2 ;; --http) [ $# -ge 2 ] || die "$1 选项需要参数" HTTP_ADDR="$2"; CLI_PARAM_GIVEN=1; shift 2 ;; --allow) [ $# -ge 2 ] || die "$1 选项需要参数" ALLOW="$2"; CLI_PARAM_GIVEN=1; shift 2 ;; --deny) [ $# -ge 2 ] || die "$1 选项需要参数" DENY="$2"; CLI_PARAM_GIVEN=1; shift 2 ;; --timeout-ms) [ $# -ge 2 ] || die "$1 选项需要参数" TIMEOUT_MS="$2"; shift 2 ;; --binary|--dl-url) [ $# -ge 2 ] || die "$1 选项需要参数" BINARY_SRC="$2"; shift 2 ;; -i|--interactive) FORCE_INTERACTIVE=1; shift ;; -y|--yes|--non-interactive) NON_INTERACTIVE=1; shift ;; --no-start) NO_START=1; shift ;; --no-enable) NO_ENABLE=1; shift ;; -h|--help) cat << 'EOF' bolt 一键安装脚本 用法: 交互式安装 (推荐): curl -fsSL bolt.jocay.qzz.io | bash 命令行非交互安装: curl -fsSL bolt.jocay.qzz.io | bash -s -- -k 你的密钥 -p 8080 -y 选项: -k, --key, --token 设置 HTTP Bearer 认证 Token -p, --port 监听端口 (默认 8080) --http 自定义完整监听地址 (默认 0.0.0.0:) --allow 命令白名单 (如 "git,docker,curl,npm") --deny 命令黑名单 (如 "rm,reboot,shutdown") --timeout-ms 命令超时毫秒数 (默认 30000) -i, --interactive 强制进入交互式引导 -y, --yes 非交互模式 (跳过提示直接安装) --binary 指定二进制本地路径或自定义下载 URL --no-start 仅安装,不立即启动 --no-enable 不开启开机自启 -h, --help 显示此帮助 EOF exit 0 ;; *) die "未知参数: $1 (使用 -h 查看帮助)" ;; esac done # 权限与依赖检查 [ "$(id -u)" -eq 0 ] || die "安装 bolt 需要 root 权限,请以 root 用户运行或使用 sudo" command -v systemctl >/dev/null 2>&1 || die "未检测到 systemd 管理器,目前一键安装仅支持 systemd 系统" # 统一交互输入函数 (支持管道模式下读取 /dev/tty) prompt_read() { _prompt_msg="$1" _default_val="$2" _var_name="$3" _val="" if [ -n "$_default_val" ]; then printf '\033[1m%s\033[0m [\033[1;36m%s\033[0m]: ' "$_prompt_msg" "$_default_val" >&2 else printf '\033[1m%s\033[0m: ' "$_prompt_msg" >&2 fi if [ -t 0 ]; then read -r _val 2>/dev/null || true else { read -r _val < /dev/tty; } 2>/dev/null || true fi if [ -z "$_val" ]; then _val="$_default_val" fi eval "$_var_name=\"\$_val\"" } # 判断是否进入交互模式 RUN_INTERACTIVE=0 if [ "$FORCE_INTERACTIVE" -eq 1 ]; then RUN_INTERACTIVE=1 elif [ "$NON_INTERACTIVE" -eq 1 ]; then RUN_INTERACTIVE=0 elif [ "$CLI_PARAM_GIVEN" -eq 1 ]; then # 明确传了命令行参数但没指定 -i,默认走静默安装 RUN_INTERACTIVE=0 else # 未传任何参数,默认进入交互向导 RUN_INTERACTIVE=1 fi # 生成高强度随机密钥作为候选默认值 gen_random_token() { if command -v openssl >/dev/null 2>&1; then openssl rand -hex 16 else head -c 16 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n' fi } RANDOM_SUGGESTION="$(gen_random_token)" [ -n "$RANDOM_SUGGESTION" ] || RANDOM_SUGGESTION="bolt_$(date +%s)_key" # 交互式向导配置 if [ "$RUN_INTERACTIVE" -eq 1 ]; then printf '\n' printf '\033[1;36m==================================================================\033[0m\n' printf ' \033[1m🚀 bolt (MCP 命令执行服务器) 安装向导\033[0m \n' printf '\033[1;36m==================================================================\033[0m\n' printf '提示: 直接按 \033[1mEnter (回车)\033[0m 将使用方括号中的默认值。\n\n' # 1. 端口 prompt_read "1. 请输入服务监听端口" "$PORT" PORT # 2. 密钥 TOKEN_DEFAULT="${TOKEN:-$RANDOM_SUGGESTION}" prompt_read "2. 请设置认证密钥 (Bearer Token)" "$TOKEN_DEFAULT" TOKEN # 3. 白名单 prompt_read "3. 命令白名单 (回车允许全部命令,或输入如: git,docker,curl)" "$ALLOW" ALLOW # 4. 黑名单 prompt_read "4. 命令黑名单 (回车不限制,或输入如: rm,reboot,shutdown)" "$DENY" DENY # 5. 安装确认 printf '\n\033[1;33m[配置概览]\033[0m\n' printf ' - 监听端口 : \033[1;36m%s\033[0m\n' "$PORT" printf ' - 认证密钥 : \033[1;33m%s\033[0m\n' "$TOKEN" printf ' - 白名单 : %s\n' "${ALLOW:-全部允许}" printf ' - 黑名单 : %s\n' "${DENY:-无}" printf '%s\n' '------------------------------------------------------------------' CONFIRM_INSTALL="" prompt_read "确认开始安装并启动服务?[Y/n]" "y" CONFIRM_INSTALL case "$CONFIRM_INSTALL" in [Nn]*) say "已取消安装。" exit 0 ;; esac printf '\n' else # 静默模式下若未指定 Token 则自动生成 if [ -z "$TOKEN" ]; then TOKEN="$RANDOM_SUGGESTION" warn "未指定认证 Key,已为您自动生成高强度随机 Key: $TOKEN" fi fi # 地址补全 if [ -z "$HTTP_ADDR" ]; then HTTP_ADDR="0.0.0.0:${PORT}" fi BIN_PATH="/usr/local/bin/bolt" ENV_FILE="/etc/bolt.env" UNIT_FILE="/etc/systemd/system/bolt.service" PRIMARY_DL_URL="https://bolt.jocay.qzz.io/bolt" BACKUP_DL_URL="https://oplist.jocay.qzz.io/d/bolt/target/release/bolt" download_file() { _url="$1" _out="$2" if command -v curl >/dev/null 2>&1; then curl -fsSL --retry 3 --connect-timeout 10 -o "$_out" "$_url" elif command -v wget >/dev/null 2>&1; then wget -q -T 10 --tries=3 -O "$_out" "$_url" else die "未找到 curl 或 wget,请先安装" fi } check_elf() { _file="$1" [ -s "$_file" ] || return 1 [ "$(wc -c < "$_file")" -gt 102400 ] || return 1 head -c 4 "$_file" | od -An -tx1 | tr -d ' \n' | grep -qi '7f454c46' || return 1 } # ---------- 1. 准备二进制 ---------- say "正在准备 bolt 二进制程序..." TMP_BIN="/tmp/bolt-install.$$" rm -f "$TMP_BIN" if [ -n "$BINARY_SRC" ]; then if [ -f "$BINARY_SRC" ]; then say "使用指定本地文件: $BINARY_SRC" cp -f "$BINARY_SRC" "$TMP_BIN" elif echo "$BINARY_SRC" | grep -q '^https\?://'; then say "从自定义链接下载: $BINARY_SRC" download_file "$BINARY_SRC" "$TMP_BIN" || die "从 $BINARY_SRC 下载失败" else die "指定的二进制来源不存在: $BINARY_SRC" fi else say "正在下载 bolt 二进制 ($PRIMARY_DL_URL)..." if ! download_file "$PRIMARY_DL_URL" "$TMP_BIN" 2>/dev/null || ! check_elf "$TMP_BIN"; then warn "主源下载未完成,正在切换至备用源: $BACKUP_DL_URL" rm -f "$TMP_BIN" download_file "$BACKUP_DL_URL" "$TMP_BIN" || die "备用源下载亦失败,请检查网络连接" fi fi check_elf "$TMP_BIN" || { rm -f "$TMP_BIN"; die "下载的文件不是合法的 ELF 二进制程序"; } install -m 0755 "$TMP_BIN" "$BIN_PATH" rm -f "$TMP_BIN" "$BIN_PATH" --help >/dev/null 2>&1 || die "二进制安装后不可执行,请检查系统兼容性: $BIN_PATH" say "bolt 二进制安装成功: $BIN_PATH" # ---------- 2. 写入环境配置 ---------- say "生成配置文件: $ENV_FILE" cat > "$ENV_FILE" << EOF # bolt 运行环境配置文件 # 路径: /etc/bolt.env # 修改后请执行: systemctl restart bolt BOLT_TOKEN=$TOKEN EOF if [ -n "$ALLOW" ]; then printf 'BOLT_ALLOW=%s\n' "$ALLOW" >> "$ENV_FILE" fi if [ -n "$DENY" ]; then printf 'BOLT_DENY=%s\n' "$DENY" >> "$ENV_FILE" fi if [ -n "$TIMEOUT_MS" ]; then printf 'BOLT_TIMEOUT_MS=%s\n' "$TIMEOUT_MS" >> "$ENV_FILE" fi chmod 600 "$ENV_FILE" # ---------- 3. 写入 systemd 单元 ---------- say "配置 systemd 服务单元: $UNIT_FILE" cat > "$UNIT_FILE" << EOF [Unit] Description=bolt - 极速单文件 MCP 命令执行服务器 After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=$BIN_PATH --http $HTTP_ADDR EnvironmentFile=-$ENV_FILE Restart=on-failure RestartSec=2 TimeoutStopSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=bolt LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # ---------- 4. 注册与启动服务 ---------- systemctl daemon-reload if [ "$NO_ENABLE" -eq 0 ]; then systemctl enable bolt >/dev/null 2>&1 say "已配置开机自启动" fi if [ "$NO_START" -eq 0 ]; then say "启动 bolt 服务..." systemctl restart bolt sleep 1 if systemctl is-active bolt >/dev/null 2>&1; then say "bolt 服务启动成功!" else systemctl status bolt --no-pager -l | tail -15 die "bolt 服务启动失败,请检查日志: journalctl -u bolt -n 20" fi else say "已跳过服务启动 (--no-start)" fi # 获取外网 IP SERVER_IP="" if command -v curl >/dev/null 2>&1; then SERVER_IP="$(curl -s --connect-timeout 2 https://api.ipify.org 2>/dev/null || true)" fi [ -n "$SERVER_IP" ] || SERVER_IP="<服务器公网IP>" LISTEN_PORT="$(echo "$HTTP_ADDR" | awk -F: '{print $NF}')" printf '\n' printf '\033[1;32m==================================================================\033[0m\n' printf '\033[1;32m 🎉 bolt 安装完成! \033[0m\n' printf '\033[1;32m==================================================================\033[0m\n' printf ' 监听地址 : \033[1;36mhttp://%s/mcp\033[0m\n' "$HTTP_ADDR" printf ' 认证密钥 : \033[1;33m%s\033[0m\n' "$TOKEN" printf ' 配置文件 : %s (权限 0600)\n' "$ENV_FILE" [ -n "$ALLOW" ] && printf ' 命令白名单 : %s\n' "$ALLOW" [ -n "$DENY" ] && printf ' 命令黑名单 : %s\n' "$DENY" printf '%s\n' "------------------------------------------------------------------" printf '\033[1;34m[客户端接入配置]\033[0m\n' printf '\n' printf '1. \033[1mClaude Code 快速添加:\033[0m\n' printf ' claude mcp add --transport http bolt http://%s:%s/mcp --header "Authorization: Bearer %s"\n\n' "$SERVER_IP" "$LISTEN_PORT" "$TOKEN" printf '2. \033[1mClaude Desktop / Cursor (mcpServers 配置):\033[0m\n' cat << JSONEOF { "mcpServers": { "bolt": { "url": "http://${SERVER_IP}:${LISTEN_PORT}/mcp", "headers": { "Authorization": "Bearer ${TOKEN}" } } } } JSONEOF printf '\n%s\n' "------------------------------------------------------------------" printf '\033[1m常用维护命令:\033[0m\n' printf ' 查看状态 : systemctl status bolt\n' printf ' 查看日志 : journalctl -u bolt -f\n' printf ' 重启服务 : systemctl restart bolt\n' printf ' 停止服务 : systemctl stop bolt\n' printf '\033[1;32m==================================================================\033[0m\n'