deploy: 站点探测不再强制 IPv4/IPv6 并加超时

git.jocayn.qzz.io 在 Cloudflare 后面同时有 IPv4/IPv6 地址,部分网络(如 nas)IPv4 不通、
IPv6 可用;原来用 -4 强制 IPv4 且没有连接超时,会重试到卡死。改为由 curl 自动选择协议族,
探测 8 秒连接超时、15 秒总时长且不重试,下载加 15 秒连接超时。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jocay
2026-10-18 10:57:56 +08:00
parent 76f6fe32f5
commit a8d6dd4b87
4 changed files with 37 additions and 32 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ cargo check --release --all-targets --target x86_64-pc-windows-gnu # 在 Linux
- `install.sh`:首次安装。自动识别 Linux(systemd:`/usr/local/bin/bolt`、`/etc/bolt.env`、`bolt.service`)或 macOS(launchd 标签 `io.jocay.bolt`,plist 放在 `/Library/Application Support/bolt/`,启用开机自启时复制到 `/Library/LaunchDaemons/`)。已安装时拒绝执行,除非加 `--reinstall`,因为它会按本次参数重写配置。 - `install.sh`:首次安装。自动识别 Linux(systemd:`/usr/local/bin/bolt`、`/etc/bolt.env`、`bolt.service`)或 macOS(launchd 标签 `io.jocay.bolt`,plist 放在 `/Library/Application Support/bolt/`,启用开机自启时复制到 `/Library/LaunchDaemons/`)。已安装时拒绝执行,除非加 `--reinstall`,因为它会按本次参数重写配置。
- `update.sh`:只替换二进制,保留配置。重启和健康检查交给独立进程执行(Linux 用 `systemd-run`,macOS 用 `(trap '' HUP; sh … &)`;macOS 上 `nohup … &` 启动的进程会在调用返回后消失),失败时从 `/usr/local/bin/bolt.bak` 回滚。 - `update.sh`:只替换二进制,保留配置。重启和健康检查交给独立进程执行(Linux 用 `systemd-run`,macOS 用 `(trap '' HUP; sh … &)`;macOS 上 `nohup … &` 启动的进程会在调用返回后消失),失败时从 `/usr/local/bin/bolt.bak` 回滚。
- `uninstall.sh`:先删文件,最后才停服务,因为通过 bolt 自身执行时,停服务会结束脚本本身。 - `uninstall.sh`:先删文件,最后才停服务,因为通过 bolt 自身执行时,停服务会结束脚本本身。
- 下载站点:IPv4 用 `https://git.jocayn.qzz.io`,IPv6 用 `https://git.draws.qzz.io`(同一个 Gitea,仓库 `jocay/bolt` 已公开)。脚本先用 `-4` 试 IPv4 站点,再用 `-6` 试 IPv6 站点;最新版本通过 `/api/v1/repos/jocay/bolt/releases/latest` 获取(这个 Gitea 不支持 `releases/latest/download/` 直链),附件命名为 `bolt-<linux|darwin>-<amd64|arm64>`,可选附带 `SHA256SUMS`。`BOLT_GIT_HOST`、`BOLT_VERSION`、`BOLT_DL_URL`、`--binary` 可以覆盖;`BOLT_DL_TOKEN`(下载认证)与 `--token`(服务器的 Bearer 令牌)相互独立。 - 下载站点:IPv4 用 `https://git.jocayn.qzz.io`,IPv6 用 `https://git.draws.qzz.io`(同一个 Gitea,仓库 `jocay/bolt` 已公开)。脚本先试 IPv4 站点再试 IPv6 站点(不强制 `-4`/`-6`:`git.jocayn.qzz.io` 在 Cloudflare 后面,同时有 IPv4 和 IPv6 地址,有的网络只有其中一种能通;探测带 8 秒连接超时、不重试);最新版本通过 `/api/v1/repos/jocay/bolt/releases/latest` 获取(这个 Gitea 不支持 `releases/latest/download/` 直链),附件命名为 `bolt-<linux|darwin>-<amd64|arm64>`,可选附带 `SHA256SUMS`。`BOLT_GIT_HOST`、`BOLT_VERSION`、`BOLT_DL_URL`、`--binary` 可以覆盖;`BOLT_DL_TOKEN`(下载认证)与 `--token`(服务器的 Bearer 令牌)相互独立。
- 发布用的二进制用 cargo-zigbuild 编译:`x86_64-unknown-linux-gnu.2.28`(普通 `cargo build` 产物依赖构建机的 glibc 版本,较老的系统上跑不了)和 `aarch64-apple-darwin`。 - 发布用的二进制用 cargo-zigbuild 编译:`x86_64-unknown-linux-gnu.2.28`(普通 `cargo build` 产物依赖构建机的 glibc 版本,较老的系统上跑不了)和 `aarch64-apple-darwin`。
- `bolt.service` 和 `bolt.env.example` 用于手动 systemd 部署。监听地址写在 unit 文件的 `ExecStart` 里,其余配置来自 `/etc/bolt.env` 中的 `BOLT_*` 变量。 - `bolt.service` 和 `bolt.env.example` 用于手动 systemd 部署。监听地址写在 unit 文件的 `ExecStart` 里,其余配置来自 `/etc/bolt.env` 中的 `BOLT_*` 变量。
- 修改 CLI 参数时,需同步更新 `parse_args`/`print_help`、`install.sh`(参数解析及其生成的 env/plist)、`bolt.env.example` 和 README 中的参数表。 - 修改 CLI 参数时,需同步更新 `parse_args`/`print_help`、`install.sh`(参数解析及其生成的 env/plist)、`bolt.env.example` 和 README 中的参数表。
+1 -1
View File
@@ -142,7 +142,7 @@ python3 docs/bolt_http_test.py # HTTP 端到端(含 Mcp-Session-Id、/files
## 在其他设备上部署(一键脚本) ## 在其他设备上部署(一键脚本)
脚本支持 Linux(systemd)和 macOS(launchd),自动识别 amd64 / arm64(Apple Silicon 即使在 Rosetta 终端中也优先选择 arm64),需要 root 权限。仓库公开,无需令牌;IPv4 网络用 `git.jocayn.qzz.io`,IPv6 网络用 `git.draws.qzz.io`(脚本下载二进制时也会自动先试 IPv4 站点、再试 IPv6 站点)。 脚本支持 Linux(systemd)和 macOS(launchd),自动识别 amd64 / arm64(Apple Silicon 即使在 Rosetta 终端中也优先选择 arm64),需要 root 权限。仓库公开,无需令牌;IPv4 网络用 `git.jocayn.qzz.io`,IPv6 网络用 `git.draws.qzz.io`(脚本下载二进制时先试 `git.jocayn.qzz.io`、不通再试 `git.draws.qzz.io`,协议族由 curl 自动选择,探测超时 15 秒)。
| 操作 | IPv4 | IPv6 | | 操作 | IPv4 | IPv6 |
|---|---|---| |---|---|---|
+18 -14
View File
@@ -101,19 +101,24 @@ if [ "$REINSTALL" -eq 0 ] && [ -x "$BIN_PATH" ] && { [ -e "$ENV_FILE" ] || [ -e
die "检测到已安装的 bolt。升级请用 update.sh(保留配置);确实要按本次参数重写配置请加 --reinstall" die "检测到已安装的 bolt。升级请用 update.sh(保留配置);确实要按本次参数重写配置请加 --reinstall"
fi fi
# fetch <url> <输出文件> [-4|-6] # fetch <url> <输出文件> [probe]:probe 用于探测站点,连接最多等 8 秒、总共 15 秒且不重试
fetch() { fetch() {
if [ "${3:-}" = probe ]; then
c_opt="--connect-timeout 8 -m 15"; w_opt="-T 15 -t 1"
else
c_opt="--connect-timeout 15 --retry 3"; w_opt="-T 60 -t 3"
fi
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 ${3:-} -H "Authorization: token $BOLT_DL_TOKEN" -o "$2" "$1" curl -fsSL $c_opt -H "Authorization: token $BOLT_DL_TOKEN" -o "$2" "$1"
else else
curl -fsSL --retry 3 ${3:-} -o "$2" "$1" curl -fsSL $c_opt -o "$2" "$1"
fi fi
elif command -v wget >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
if [ -n "${BOLT_DL_TOKEN:-}" ]; then if [ -n "${BOLT_DL_TOKEN:-}" ]; then
wget -q ${3:-} --header="Authorization: token $BOLT_DL_TOKEN" -O "$2" "$1" wget -q $w_opt --header="Authorization: token $BOLT_DL_TOKEN" -O "$2" "$1"
else else
wget -q ${3:-} -O "$2" "$1" wget -q $w_opt -O "$2" "$1"
fi fi
else else
die "需要 curl 或 wget" die "需要 curl 或 wget"
@@ -141,30 +146,29 @@ elif [ -n "${BOLT_DL_URL:-}" ]; then
else else
# 选择站点:BOLT_GIT_HOST 优先,否则先试 IPv4 站点再试 IPv6 站点 # 选择站点:BOLT_GIT_HOST 优先,否则先试 IPv4 站点再试 IPv6 站点
HOST="" HOST=""
IPFLAG=""
if [ -n "${BOLT_GIT_HOST:-}" ]; then if [ -n "${BOLT_GIT_HOST:-}" ]; then
HOST="${BOLT_GIT_HOST%/}" HOST="${BOLT_GIT_HOST%/}"
else else
for cand in "$HOST4 -4" "$HOST6 -6"; do # 不强制协议族,由 curl 自动选择可用的 IPv4 / IPv6 地址
if fetch "${cand% *}/api/v1/version" "$TMP/probe" "${cand#* }" 2>/dev/null; then for h in "$HOST4" "$HOST6"; do
HOST=${cand% *} if fetch "$h/api/v1/version" "$TMP/probe" probe 2>/dev/null; then
IPFLAG=${cand#* } HOST=$h
break break
fi fi
done done
[ -n "$HOST" ] || die "无法连接 $HOST4(IPv4)或 $HOST6(IPv6),可用 BOLT_GIT_HOST 指定站点" [ -n "$HOST" ] || die "无法连接 $HOST4 或 $HOST6,可用 BOLT_GIT_HOST 指定站点"
fi fi
say "下载站点: $HOST" say "下载站点: $HOST"
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
fetch "$HOST/api/v1/repos/$REPO/releases/latest" "$TMP/latest.json" "$IPFLAG" || die "查询最新版本失败" fetch "$HOST/api/v1/repos/$REPO/releases/latest" "$TMP/latest.json" || die "查询最新版本失败"
VERSION=$(sed -n 's/.*"tag_name":"\([^"]*\)".*/\1/p' "$TMP/latest.json") VERSION=$(sed -n 's/.*"tag_name":"\([^"]*\)".*/\1/p' "$TMP/latest.json")
[ -n "$VERSION" ] || die "无法解析最新版本号" [ -n "$VERSION" ] || die "无法解析最新版本号"
fi fi
BASE="$HOST/$REPO/releases/download/$VERSION" BASE="$HOST/$REPO/releases/download/$VERSION"
say "从 Release 下载: $BASE/bolt-$PLATFORM-$ARCH" say "从 Release 下载: $BASE/bolt-$PLATFORM-$ARCH"
fetch "$BASE/bolt-$PLATFORM-$ARCH" "$TMP/bolt" "$IPFLAG" || die "下载失败,请确认 Release $VERSION 有 bolt-$PLATFORM-$ARCH 附件" fetch "$BASE/bolt-$PLATFORM-$ARCH" "$TMP/bolt" || die "下载失败,请确认 Release $VERSION 有 bolt-$PLATFORM-$ARCH 附件"
# 有 SHA256SUMS 附件时校验 # 有 SHA256SUMS 附件时校验
if fetch "$BASE/SHA256SUMS" "$TMP/SHA256SUMS" "$IPFLAG" 2>/dev/null; then if fetch "$BASE/SHA256SUMS" "$TMP/SHA256SUMS" 2>/dev/null; then
want=$(awk -v f="bolt-$PLATFORM-$ARCH" '$2 == f || $2 == "*" f {print $1}' "$TMP/SHA256SUMS") want=$(awk -v f="bolt-$PLATFORM-$ARCH" '$2 == f || $2 == "*" f {print $1}' "$TMP/SHA256SUMS")
if command -v sha256sum >/dev/null 2>&1; then if command -v sha256sum >/dev/null 2>&1; then
got=$(sha256sum "$TMP/bolt" | awk '{print $1}') got=$(sha256sum "$TMP/bolt" | awk '{print $1}')
+17 -16
View File
@@ -66,42 +66,43 @@ case "$ARCH" in
esac esac
# ---------- 下载工具 ---------- # ---------- 下载工具 ----------
# fetch <url> <输出文件> [-4|-6] # fetch <url> <输出文件> [probe]:probe 用于探测站点,连接最多等 8 秒、总共 15 秒且不重试
fetch() { fetch() {
if [ "${3:-}" = probe ]; then
c_opt="--connect-timeout 8 -m 15"; w_opt="-T 15 -t 1"
else
c_opt="--connect-timeout 15 --retry 3"; w_opt="-T 60 -t 3"
fi
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 ${3:-} -H "Authorization: token $BOLT_DL_TOKEN" -o "$2" "$1" curl -fsSL $c_opt -H "Authorization: token $BOLT_DL_TOKEN" -o "$2" "$1"
else else
curl -fsSL --retry 3 ${3:-} -o "$2" "$1" curl -fsSL $c_opt -o "$2" "$1"
fi fi
elif command -v wget >/dev/null 2>&1; then elif command -v wget >/dev/null 2>&1; then
if [ -n "${BOLT_DL_TOKEN:-}" ]; then if [ -n "${BOLT_DL_TOKEN:-}" ]; then
wget -q ${3:-} --header="Authorization: token $BOLT_DL_TOKEN" -O "$2" "$1" wget -q $w_opt --header="Authorization: token $BOLT_DL_TOKEN" -O "$2" "$1"
else else
wget -q ${3:-} -O "$2" "$1" wget -q $w_opt -O "$2" "$1"
fi fi
else else
die "需要 curl 或 wget" die "需要 curl 或 wget"
fi fi
} }
# 探测可用站点:先 IPv4 站点,再 IPv6 站点 # 探测可用站点:先 IPv4 站点再 IPv6 站点(不强制协议族,由 curl 自动选择可用的 IPv4 / IPv6 地址)
IPFLAG=""
pick_host() { pick_host() {
if [ -n "${BOLT_GIT_HOST:-}" ]; then if [ -n "${BOLT_GIT_HOST:-}" ]; then
HOST="${BOLT_GIT_HOST%/}" HOST="${BOLT_GIT_HOST%/}"
return return
fi fi
for cand in "$HOST4 -4" "$HOST6 -6"; do for h in "$HOST4" "$HOST6"; do
h=${cand% *} if fetch "$h/api/v1/version" "$TMP/probe" probe 2>/dev/null; then
f=${cand#* }
if fetch "$h/api/v1/version" "$TMP/probe" "$f" 2>/dev/null; then
HOST=$h HOST=$h
IPFLAG=$f
return return
fi fi
done done
die "无法连接 $HOST4(IPv4)或 $HOST6(IPv6),可用 BOLT_GIT_HOST 指定站点" die "无法连接 $HOST4 或 $HOST6,可用 BOLT_GIT_HOST 指定站点"
} }
# ver_cmp a b:输出 1 / 0 / -1 # ver_cmp a b:输出 1 / 0 / -1
@@ -140,7 +141,7 @@ else
pick_host pick_host
say "下载站点: $HOST" say "下载站点: $HOST"
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
fetch "$HOST/api/v1/repos/$REPO/releases/latest" "$TMP/latest.json" "$IPFLAG" || die "查询最新版本失败" fetch "$HOST/api/v1/repos/$REPO/releases/latest" "$TMP/latest.json" || die "查询最新版本失败"
VERSION=$(sed -n 's/.*"tag_name":"\([^"]*\)".*/\1/p' "$TMP/latest.json") VERSION=$(sed -n 's/.*"tag_name":"\([^"]*\)".*/\1/p' "$TMP/latest.json")
[ -n "$VERSION" ] || die "无法解析最新版本号" [ -n "$VERSION" ] || die "无法解析最新版本号"
EXPLICIT=0 EXPLICIT=0
@@ -161,9 +162,9 @@ else
fi fi
BASE="$HOST/$REPO/releases/download/$VERSION" BASE="$HOST/$REPO/releases/download/$VERSION"
say "下载: $BASE/bolt-$PLATFORM-$ARCH" say "下载: $BASE/bolt-$PLATFORM-$ARCH"
fetch "$BASE/bolt-$PLATFORM-$ARCH" "$NEW_BIN" "$IPFLAG" || die "下载失败,请确认 Release $VERSION 有 bolt-$PLATFORM-$ARCH 附件" fetch "$BASE/bolt-$PLATFORM-$ARCH" "$NEW_BIN" || die "下载失败,请确认 Release $VERSION 有 bolt-$PLATFORM-$ARCH 附件"
# 有 SHA256SUMS 附件时校验 # 有 SHA256SUMS 附件时校验
if fetch "$BASE/SHA256SUMS" "$TMP/SHA256SUMS" "$IPFLAG" 2>/dev/null; then if fetch "$BASE/SHA256SUMS" "$TMP/SHA256SUMS" 2>/dev/null; then
want=$(awk -v f="bolt-$PLATFORM-$ARCH" '$2 == f || $2 == "*" f {print $1}' "$TMP/SHA256SUMS") want=$(awk -v f="bolt-$PLATFORM-$ARCH" '$2 == f || $2 == "*" f {print $1}' "$TMP/SHA256SUMS")
if command -v sha256sum >/dev/null 2>&1; then if command -v sha256sum >/dev/null 2>&1; then
got=$(sha256sum "$NEW_BIN" | awk '{print $1}') got=$(sha256sum "$NEW_BIN" | awk '{print $1}')