feat(deploy): support macOS arm64 installation with launchd

This commit is contained in:
jocay
2026-10-05 01:36:27 +08:00
parent 3b3492a36d
commit 63d48ec951
2 changed files with 143 additions and 12 deletions
+40 -2
View File
@@ -106,18 +106,56 @@ python3 /tmp/bolt_http_test.py # HTTP 端到端
- Windows(基础功能:`cmd /C`、`taskkill /T` 杀进程树;无信号优雅退出) - Windows(基础功能:`cmd /C`、`taskkill /T` 杀进程树;无信号优雅退出)
## 在其他设备上部署(最快方式) ## 在其他设备上部署(最快方式)
单文件二进制已发布到 Gitea Release,配合一键脚本,目标设备(Linux amd64 + systemd)只需两条命令: 一键脚本支持 Linux(systemd)和 macOS(launchd),自动识别 amd64 / arm64;Apple Silicon 即使在 Rosetta 终端中也优先选择 arm64。安装需要 root 权限。
默认下载 `v0.1.0` 的 `bolt-<平台>-<架构>` 附件,例如 `bolt-linux-amd64`、`bolt-darwin-arm64`。对应附件需要事先发布到 Gitea Release;可通过 `BOLT_VERSION` 指定版本,或通过 `BOLT_DL_URL` / `--binary` 指定其他来源。
```bash ```bash
# 1. 下载并执行安装脚本(私有仓库下载附件需先 export 下载令牌) # 1. 下载并执行安装脚本(私有仓库下载附件需先 export 下载令牌)
export BOLT_DL_TOKEN=你的Gitea令牌 export BOLT_DL_TOKEN=你的Gitea令牌
curl -fsSL https://git.jocay.qzz.io/jocay/bolt/raw/branch/main/deploy/install.sh | sh -s -- --http 0.0.0.0:8080 curl -fsSL -H "Authorization: token $BOLT_DL_TOKEN" https://git.draws.qzz.io/jocay/bolt/raw/branch/main/deploy/install.sh -o install.sh
sudo BOLT_DL_TOKEN="$BOLT_DL_TOKEN" sh install.sh --http 127.0.0.1:8080
# 2. 完成(脚本自动:下载二进制 → 装到 /usr/local/bin → 写 /etc/bolt.env 与 # 2. 完成(脚本自动:下载二进制 → 装到 /usr/local/bin → 写 /etc/bolt.env 与
# systemd 单元 → 开机自启 → 启动服务)。看状态: # systemd 单元 → 开机自启 → 启动服务)。看状态:
systemctl status bolt systemctl status bolt
``` ```
### macOS arm64(Apple Silicon)
在 Mac 上运行上面的安装命令,脚本会下载 `bolt-darwin-arm64`,安装到 `/usr/local/bin/bolt`,并创建以 root 运行的 launchd 系统服务 `io.jocay.bolt`。
`v0.1.0` Release 提供 `bolt-darwin-arm64` 附件。也可以将该二进制下载或拷到 Mac 后安装:
```bash
sudo sh deploy/install.sh --binary ./bolt-darwin-arm64 --http 127.0.0.1:8080 --token 你的令牌
```
配置保存在 `/Library/Application Support/bolt/io.jocay.bolt.plist`,包含监听地址、认证令牌、命令规则和超时。启用开机自启时,脚本还会复制配置到 `/Library/LaunchDaemons/io.jocay.bolt.plist`。两份配置均仅 root 可读写;更改参数建议重新运行安装脚本,以保持一致。
`--no-enable` 只保留手动启动配置,取消本服务的开机配置,仍可立即启动;`--no-start` 本次不加载服务,已运行的本服务也会卸载,下次开机是否启动由 `--no-enable` 决定。两个参数可一起使用。
```bash
# 查看服务 / 重启已加载的服务 / 停止并卸载
sudo launchctl print system/io.jocay.bolt
sudo launchctl kickstart -k system/io.jocay.bolt
sudo launchctl bootout system/io.jocay.bolt
# 停止后或使用 --no-start 安装后,手动加载并启动
sudo launchctl bootstrap system '/Library/Application Support/bolt/io.jocay.bolt.plist'
# 查看日志
sudo tail -f /var/log/bolt/stdout.log /var/log/bolt/stderr.log
```
macOS 服务的工作目录为 `/var/root`,PATH 包含 `/opt/homebrew/bin` 和 `/opt/homebrew/sbin`,不会加载个人 shell 配置。认证下载使用 `BOLT_DL_TOKEN`,HTTP 服务认证使用 `--token`,两者独立。
该附件交叉编译为 `aarch64-apple-darwin`,最低目标系统为 macOS 11.0,未进行 Mac 实机运行验证。SHA-256:
```text
39274c7d1f965ae924ee51d4e9e4d8a260a84d083971cd50dc6ce645245cc4b8 bolt-darwin-arm64
```
常用安装参数(全部可选): 常用安装参数(全部可选):
| 参数 | 含义 | | 参数 | 含义 |
+102 -9
View File
@@ -1,10 +1,10 @@
#!/bin/sh #!/bin/sh
# bolt 一键安装脚本(POSIX sh,需 root + systemd) # bolt 一键安装脚本(POSIX sh,需 root; Linux 使用 systemd,macOS 使用 launchd)
# #
# 用法(在目标设备上): # 用法(在目标设备上):
# 方式 A - 从 Gitea Release 下载二进制并安装: # 方式 A - 从 Gitea Release 下载二进制并安装:
# curl -fsSL https://git.jocay.qzz.io/jocay/bolt/raw/branch/main/deploy/install.sh | sh -s -- --http 0.0.0.0:8080 --token 你的令牌 # curl -fsSL https://git.draws.qzz.io/jocay/bolt/raw/branch/main/deploy/install.sh | sh -s -- --http 0.0.0.0:8080 --token 你的令牌
# (私有仓库下载附件需要认证: 脚本会用 --token 同时作为下载凭据;或先 export BOLT_DL_TOKEN=xxx) # (私有仓库下载附件需要认证:先 export BOLT_DL_TOKEN=xxx,与 --token 分开配置)
# 方式 B - 已把二进制拷到目标设备(如 scp): # 方式 B - 已把二进制拷到目标设备(如 scp):
# ./install.sh --binary ./bolt --http 0.0.0.0:8080 --token xxx --allow "git,docker" # ./install.sh --binary ./bolt --http 0.0.0.0:8080 --token xxx --allow "git,docker"
# #
@@ -17,7 +17,7 @@
# --binary <src> 二进制来源:本地路径 或 http(s) URL(默认从 Gitea Release 下载) # --binary <src> 二进制来源:本地路径 或 http(s) URL(默认从 Gitea Release 下载)
# --no-start 只安装不启动 # --no-start 只安装不启动
# --no-enable 不设置开机自启 # --no-enable 不设置开机自启
set -u set -eu
HTTP_ADDR="${HTTP_ADDR:-127.0.0.1:8080}" HTTP_ADDR="${HTTP_ADDR:-127.0.0.1:8080}"
TOKEN="${BOLT_TOKEN:-}" TOKEN="${BOLT_TOKEN:-}"
@@ -45,13 +45,36 @@ done
BIN_PATH=/usr/local/bin/bolt BIN_PATH=/usr/local/bin/bolt
ENV_FILE=/etc/bolt.env ENV_FILE=/etc/bolt.env
UNIT_FILE=/etc/systemd/system/bolt.service UNIT_FILE=/etc/systemd/system/bolt.service
DL_URL="${BOLT_DL_URL:-https://git.jocay.qzz.io/jocay/bolt/releases/download/v0.1.0/bolt-linux-amd64}"
say() { printf '\033[1;32m[bolt]\033[0m %s\n' "$*"; } say() { printf '\033[1;32m[bolt]\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[bolt] 错误:\033[0m %s\n' "$*"; exit 1; } die() { printf '\033[1;31m[bolt] 错误:\033[0m %s\n' "$*"; exit 1; }
[ "$(id -u)" -eq 0 ] || die "需要 root 权限" [ "$(id -u)" -eq 0 ] || die "需要 root 权限"
command -v systemctl >/dev/null 2>&1 || die "未检测到 systemd" OS=$(uname -s)
ARCH=$(uname -m)
case "$OS" in
Linux)
command -v systemctl >/dev/null 2>&1 || die "未检测到 systemd"
PLATFORM=linux
;;
Darwin)
command -v launchctl >/dev/null 2>&1 || die "未检测到 launchd"
PLATFORM=darwin
# Rosetta 下 uname 可能返回 x86_64,优先识别实际的 Apple Silicon 硬件。
if [ "$(sysctl -n hw.optional.arm64 2>/dev/null || true)" = 1 ]; then
ARCH=arm64
fi
;;
*) die "不支持的操作系统: $OS" ;;
esac
case "$ARCH" in
x86_64|amd64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
*) die "不支持的架构: $ARCH" ;;
esac
DL_URL="${BOLT_DL_URL:-https://git.draws.qzz.io/jocay/bolt/releases/download/${BOLT_VERSION:-v0.1.0}/bolt-$PLATFORM-$ARCH}"
say "目标平台: $PLATFORM/$ARCH"
mkdir -p /usr/local/bin
# ---------- 1. 获取二进制 ---------- # ---------- 1. 获取二进制 ----------
if [ -n "$BINARY_SRC" ]; then if [ -n "$BINARY_SRC" ]; then
@@ -81,9 +104,9 @@ else
rm -f /tmp/bolt-dl.$$ rm -f /tmp/bolt-dl.$$
if command -v curl >/dev/null 2>&1; then if command -v curl >/dev/null 2>&1; then
if [ -n "${BOLT_DL_TOKEN:-}" ]; then if [ -n "${BOLT_DL_TOKEN:-}" ]; then
curl -fsSL --retry 3 -H "Authorization:token ${BOLT_DL_TOKEN:-}" -o /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败(私有仓库需 export BOLT_DL_TOKEN 或 --token)" curl -fsSL --retry 3 -H "Authorization:token ${BOLT_DL_TOKEN:-}" -o /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败,请确认 Release 附件存在且 BOLT_DL_TOKEN 有效"
else else
curl -fsSL --retry 3 -o /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败(私有仓库需 export BOLT_DL_TOKEN 或 --token)" curl -fsSL --retry 3 -o /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败,请确认 Release 附件存在;私有仓库需设置 BOLT_DL_TOKEN"
fi fi
elif command -v wget >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
wget -qO /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败" wget -qO /tmp/bolt-dl.$$ "$DL_URL" || die "下载失败"
@@ -95,9 +118,79 @@ fi
"$BIN_PATH" --help >/dev/null 2>&1 || die "二进制不可执行: $BIN_PATH" "$BIN_PATH" --help >/dev/null 2>&1 || die "二进制不可执行: $BIN_PATH"
say "二进制就绪: $BIN_PATH ($("$BIN_PATH" --help | head -1))" say "二进制就绪: $BIN_PATH ($("$BIN_PATH" --help | head -1))"
# macOS 将环境配置直接写入 plist,不通过 shell 解释令牌或命令规则。
if [ "$OS" = Darwin ]; then
LABEL=io.jocay.bolt
PLIST_DIR="/Library/Application Support/bolt"
PLIST_FILE="$PLIST_DIR/$LABEL.plist"
AUTO_PLIST="/Library/LaunchDaemons/$LABEL.plist"
xml_escape() {
printf '%s' "$1" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g'
}
mkdir -p "$PLIST_DIR" /Library/LaunchDaemons /var/log/bolt
chmod 700 "$PLIST_DIR" /var/log/bolt
# 令牌所在配置仅供 root 读取。
umask 077
say "写入 launchd 配置: $PLIST_FILE"
cat > "$PLIST_FILE" <<PLISTEOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$BIN_PATH</string>
<string>--http</string><string>$(xml_escape "$HTTP_ADDR")</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>BOLT_TOKEN</key><string>$(xml_escape "$TOKEN")</string>
<key>BOLT_ALLOW</key><string>$(xml_escape "$ALLOW")</string>
<key>BOLT_DENY</key><string>$(xml_escape "$DENY")</string>
<key>BOLT_TIMEOUT_MS</key><string>$(xml_escape "$TIMEOUT_MS")</string>
</dict>
<key>WorkingDirectory</key><string>/var/root</string>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
<key>ThrottleInterval</key><integer>2</integer>
<key>ExitTimeOut</key><integer>10</integer>
<key>StandardOutPath</key><string>/var/log/bolt/stdout.log</string>
<key>StandardErrorPath</key><string>/var/log/bolt/stderr.log</string>
</dict>
</plist>
PLISTEOF
chown root:wheel "$PLIST_FILE"
chmod 600 "$PLIST_FILE"
if [ "$NO_ENABLE" -eq 0 ]; then
cp "$PLIST_FILE" "$AUTO_PLIST"
chown root:wheel "$AUTO_PLIST"
chmod 600 "$AUTO_PLIST"
say "已设置开机自启"
else
# 仅移除本脚本管理的开机配置,保留手动启动配置。
rm -f "$AUTO_PLIST"
fi
launchctl enable "system/$LABEL"
if launchctl print "system/$LABEL" >/dev/null 2>&1; then
launchctl bootout "system/$LABEL"
fi
if [ "$NO_START" -eq 0 ]; then
launchctl bootstrap system "$PLIST_FILE" || die "服务注册失败"
say "已提交服务启动: http://$HTTP_ADDR/mcp"
else
say "已安装(未启动),执行: sudo launchctl bootstrap system '$PLIST_FILE'"
fi
say "管理: sudo launchctl print system/$LABEL | sudo launchctl kickstart -k system/$LABEL"
say "日志: /var/log/bolt/stdout.log 与 /var/log/bolt/stderr.log"
exit 0
fi
# ---------- 2. 写入环境配置 ---------- # ---------- 2. 写入环境配置 ----------
say "写入配置: $ENV_FILE" say "写入配置: $ENV_FILE"
printf '# bolt 服务配置,修改后 systemctl restart bolt\n' > "$ENV_FILE" printf '# bolt 服务配置,修改后 systemctl restart bolt\n' > "$ENV_FILE"
printf 'BOLT_TIMEOUT_MS=%s\n' "$TIMEOUT_MS" >> "$ENV_FILE"
if [ -n "$TOKEN" ]; then if [ -n "$TOKEN" ]; then
printf 'BOLT_TOKEN=%s\n' "$TOKEN" >> "$ENV_FILE" printf 'BOLT_TOKEN=%s\n' "$TOKEN" >> "$ENV_FILE"
fi fi
@@ -144,7 +237,7 @@ if [ "$NO_START" -eq 0 ]; then
sleep 1 sleep 1
systemctl is-active bolt >/dev/null 2>&1 || { systemctl status bolt --no-pager -l | tail -15; die "服务启动失败"; } systemctl is-active bolt >/dev/null 2>&1 || { systemctl status bolt --no-pager -l | tail -15; die "服务启动失败"; }
say "服务运行中: http://$HTTP_ADDR/mcp $(systemctl show -p MainPID --value bolt)" say "服务运行中: http://$HTTP_ADDR/mcp $(systemctl show -p MainPID --value bolt)"
[ -n "$TOKEN" ] && say "认证: Authorization: Bearer $TOKEN" [ -z "$TOKEN" ] || say "已启用 Bearer 认证"
else else
say "已安装(未启动),执行 systemctl start bolt 启动" say "已安装(未启动),执行 systemctl start bolt 启动"
fi fi