Files
HomeAgent/deploy-sdk-site.sh
JianFeeeee e773f156fb feat(deploy): 站点部署流水线(SDK 文档站 + introduce)
`deploy-sdk-site.sh` 覆盖两个静态站,与 homed / waiter 是独立部署单元:

- SDK 文档站:本地 `third_party/homeagent-sdk/site_build/` → 106 的
  `/vol1/docker/navi-data/sites/sdk`
- introduce:本地 `site/`(零构建,源即产物)→ `sites/introduce`

用法:`--check`(只核对差异)/ 默认(构建+部署+验证)/ `--rollback <备份名>`。
`--check` 逐字节比对,2026-09-27 核实两站线上与本地产物**完全一致**。

## 更新了一处会误导的注释

脚本原注释写「设备网关白名单只放行 ls/stat/find/cat,打不了包」。
那是 waiter 白名单**硬编码 18 条**时的状况;2026-09-27 部署 7193446 后
106 已扩到 **22 条**(含 find/grep/sed/sort/tr/wc/head/tail/stat/file)。

**结论(打不了包)不变,但理由已变** —— 22 条里**没有 `tar`**,
有 `sed` 也不能打包。照旧文字理解会以为白名单只有 4 条。

## docs/zh/deploy-runbook.md 补 §5 站点章节

- 链路:`.60` nginx stream 按 ssl_preread SNI → 106:3080 → navi 容器内 nginx
  (**不是** portal-nginx,那个已 Exited 两周)
- 坑①:构建**必须**走 `tools/apidoc/build.sh`。裸跑 `mkdocs build` 会丢掉
  整个 `api/*.md` 和 `llms.txt` —— 而 `llms.txt` 正是给 agent 直读的入口。
  正确产物 106 个文件,裸跑只有 78 个
- 坑②:打包不能走设备网关(无 `tar`),必须 SSH 直连
- ★ **新增文档后必须更新 `mkdocs.yml` 的 nav** —— 没登记会被 mkdocs 明确
  警告 `not included in the nav configuration`,等于写完了但站点里不可达
- 验证要打**线上**而不是只看本地产物
- 回滚点与失败版本保留策略

写文档时我一度把 introduce 的域名写成「另见 §5.2」,但 §5.2 只讲了 SDK 的
SNI 链路 —— 已改为只写"同台同目录",并补上脚本里有依据的"故意不带 README.md"。
2026-09-27 23:00:04 +08:00

222 lines
8.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# SDK 文档站一键构建 + 部署(sdk.homeagent.jianfgit.xyz)
#
# 链路(全部实测确认,写在这里免得下次再摸一遍):
# 本机 192.168.2.60
# └─ nginx stream 按 ssl_preread SNI 把 443 透传 → 192.168.2.106:3080
# └─ navi 容器内的 nginx(不是 portal-nginx,那个已 Exited 两周)
# └─ server_name sdk.homeagent.jianfgit.xyz → root /app/data/sites/sdk
# └─ 宿主对应 /vol1/docker/navi-data/sites/sdk
#
# 两个容易踩的坑:
# 1. 构建**必须**走 tools/apidoc/build.sh,不能裸跑 mkdocs build。
# 裸跑会少掉整个 api/*.md 和 llms.txt —— 而 llms.txt 正是给 agent 用的入口。
# 正确产物 106 个文件,裸跑只有 78 个。
# 2. 登录 .106 只能用 admin@ + sudo -n,root@ 会被拒。
# 设备网关白名单(waiter.yaml 的 device_cmd_allowlist)现有 22 条:
# ls pwd cat du df free ps ip uname uptime date hostname
# find grep sed sort tr wc head tail stat file
# —— 里面**没有 tar**(有 sed 也不能打包),所以走 SSH 直连。
# ★ 早先这里写的是「只放行 ls/stat/find/cat」,那是 waiter 白名单
# 硬编码 18 条时的状况;2026-09-27 部署 7193446 后扩到 22 条,
# 结论(打不了包)不变但**理由已变**,别照旧文字理解。
#
# 用法:
# ./deploy-sdk-site.sh # 构建 + 部署 + 验证
# ./deploy-sdk-site.sh --check # 只核对线上与本地产物差异,不动线上
# ./deploy-sdk-site.sh --rollback <备份目录名> # 回滚
set -euo pipefail
SDK_DIR=/home/program/TrueAgent/third_party/homeagent-sdk
BUILD_DIR="$SDK_DIR/site_build"
HOST=192.168.2.106
SSH="ssh -n -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no admin@$HOST"
SITES=/vol1/docker/navi-data/sites
TS=$(date +%Y%m%d-%H%M%S)
PKG_NAME=sdk-site-build-$TS.tar.gz
PKG=/tmp/$PKG_NAME
say() { printf '\n\033[1m%s\033[0m\n' "$*"; }
info() { printf ' %s\n' "$*"; }
# ── introduce 站 ──
#
# 零构建:/home/program/TrueAgent/site/ 里就是成品(site/README.md 自称
# 「单文件、零构建、零依赖」),改完直接是产物,所以这条只需要同步。
#
# 部署时**故意不带上 README.md**:那是仓库里的说明文档,不是站点资源。
# 线上现有 2 个文件(index.html + assets/logo.svg),带 README 会多出一个
# 线上原本没有的文件,反而让 diff 比对永远不为零。
INTRO_SRC=/home/program/TrueAgent/site
INTRO_PKG_NAME=intro-site-$TS.tar.gz
INTRO_PKG=/tmp/$INTRO_PKG_NAME
intro_local_md5() {
(cd "$INTRO_SRC" && find index.html assets -type f -print0 | sort -z | xargs -0 md5sum) | norm_md5
}
intro_remote_md5() {
$SSH "cd $SITES/introduce && sudo -n find . -type f -print0 | sort -z | sudo -n xargs -0 md5sum" 2>/dev/null | norm_md5
}
do_check_introduce() {
say "检查 introduce 站"
local n files
n=$(intro_local_md5 | wc -l)
info "本机源: $n 个文件(已排除 README.md)"
files=$($SSH "sudo -n find $SITES/introduce -type f 2>/dev/null | wc -l" 2>/dev/null)
info "线上站: $files 个文件"
if diff -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
info "✓ introduce 线上与本机源逐字节一致"
return 0
fi
info "✗ introduce 有差异:"
diff <(intro_local_md5) <(intro_remote_md5) | head -20 || true
return 1
}
do_deploy_introduce() {
say "introduce:打包上传"
tar -czf "$INTRO_PKG" -C "$INTRO_SRC" index.html assets
local sz
sz=$(du -h "$INTRO_PKG" | cut -f1)
scp -q -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$INTRO_PKG" "admin@$HOST:/tmp/" || {
info "✗ 上传失败"; rm -f "$INTRO_PKG"; return 1; }
rm -f "$INTRO_PKG"
info "已上传 $sz"
$SSH "
set -e
sudo -n cp -a $SITES/introduce $SITES/.intro-bak-$TS
sudo -n mkdir -p $SITES/.intro-new-$TS
sudo -n tar -xzf /tmp/$INTRO_PKG_NAME -C $SITES/.intro-new-$TS
sudo -n chown -R root:root $SITES/.intro-new-$TS
sudo -n rm -rf $SITES/.intro-old-$TS
sudo -n mv $SITES/introduce $SITES/.intro-old-$TS
sudo -n mv $SITES/.intro-new-$TS $SITES/introduce
sudo -n rm -rf $SITES/.intro-old-$TS
rm -f /tmp/$INTRO_PKG_NAME
echo -n '现役站文件数: '; sudo -n find $SITES/introduce -type f | wc -l
" 2>&1 | tail -3
info "回滚点: $SITES/.intro-bak-$TS"
local code
code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' -k https://introduce.homeagent.jianfgit.xyz/)
info "首页 http=$code(introduce 配了 try_files 回落,404 才是异常)"
if diff -q <(intro_local_md5) <(intro_remote_md5) >/dev/null 2>&1; then
info "✓ introduce 线上与本机源逐字节一致"
else
info "✗ introduce 仍有差异:"; diff <(intro_local_md5) <(intro_remote_md5) | head -20; return 1
fi
}
# md5 清单统一去掉路径前缀 ./,否则本地与线上排序后无法直接 diff。
# 归一化放在本机做:远端 sed 若嵌在 ssh 的双引号里,转义会被外层 shell 先吃掉。
norm_md5() { sed 's| \./| |'; }
# 本地产物 → md5 清单
local_md5() {
(cd "$BUILD_DIR" && find . -type f -print0 | sort -z | xargs -0 md5sum) | norm_md5
}
# 线上产物 → md5 清单
remote_md5() {
$SSH "cd $SITES/sdk && sudo -n find . -type f -print0 | sort -z | sudo -n xargs -0 md5sum" 2>/dev/null | norm_md5
}
do_check() {
say "检查 $HOST 上的 sdk 站"
local n files
n=$(local_md5 | wc -l)
info "本地产物: $n 个文件"
files=$($SSH "sudo -n find $SITES/sdk -type f 2>/dev/null | wc -l" 2>/dev/null)
info "线上站: $files 个文件"
if diff -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
info "✓ 线上与本地产物逐字节一致,无需部署"
else
info "✗ sdk 有差异,需部署。差异文件:"
diff <(local_md5) <(remote_md5) | head -20 || true
fi
# introduce 始终检查:两个站是独立的,sdk 一致不代表 introduce 也一致
do_check_introduce || true
}
do_deploy() {
say "1/4 构建($TS)"
(cd "$SDK_DIR" && tools/apidoc/build.sh) >/tmp/sdk-build-$TS.log 2>&1 || {
info "✗ 构建失败,日志:/tmp/sdk-build-$TS.log"; tail -20 /tmp/sdk-build-$TS.log; exit 1; }
local n
n=$(find "$BUILD_DIR" -type f | wc -l)
info "产物 $n 个文件"
[ "$n" -ge 100 ] || { info "✗ 产物只有 $n 个,八成是裸跑了 mkdocs(应调 build.sh)"; exit 1; }
say "2/4 打包上传"
tar -czf "$PKG" -C "$BUILD_DIR" .
scp -q -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$PKG" "admin@$HOST:/tmp/" || {
info "✗ 上传失败"; exit 1; }
info "已上传 $(du -h "$PKG" | cut -f1)"
rm -f "$PKG"
say "3/4 原子替换(先备份再换)"
$SSH "
set -e
sudo -n cp -a $SITES/sdk $SITES/.sdk-bak-$TS
sudo -n mkdir -p $SITES/.sdk-new-$TS
sudo -n tar -xzf /tmp/$PKG_NAME -C $SITES/.sdk-new-$TS
sudo -n chown -R root:root $SITES/.sdk-new-$TS
sudo -n mv $SITES/sdk $SITES/.sdk-old-$TS
sudo -n mv $SITES/.sdk-new-$TS $SITES/sdk
echo -n '现役站文件数: '; sudo -n find $SITES/sdk -type f | wc -l
# .sdk-old 与刚建的 .sdk-bak 内容必然相同(同一份旧站复制两次),
# 留一份就够,白占 11M。
if sudo -n diff -r -q $SITES/.sdk-bak-$TS $SITES/.sdk-old-$TS >/dev/null 2>&1; then
sudo -n rm -rf $SITES/.sdk-old-$TS
echo '已删重复的 .sdk-old(与 .sdk-bak 内容相同)'
else
echo '★ .sdk-old 与 .sdk-bak 内容不同,两份都保留,请人工确认'
fi
rm -f /tmp/$PKG_NAME
" 2>&1 | tail -5
info "回滚点: $SITES/.sdk-bak-$TS"
say "4/4 验证"
# 静态文件换完 nginx 直接吃新文件,不需要 reload。
local code
code=$(curl -s -o /dev/null -m 10 -w '%{http_code}' -k https://sdk.homeagent.jianfgit.xyz/)
info "首页 http=$code"
for p in guide/scene-memory/ guide/parallel-tool-declaration/ llms.txt llms-full.txt; do
printf ' %-34s ' "$p"
curl -s -o /dev/null -m 10 -w 'http=%{http_code}\n' -k "https://sdk.homeagent.jianfgit.xyz/$p"
done
if diff -q <(local_md5) <(remote_md5) >/dev/null 2>&1; then
info "✓ 线上与本地产物逐字节一致"
else
info "✗ 仍有差异:"; diff <(local_md5) <(remote_md5) | head -20; return 1
fi
do_deploy_introduce
}
do_rollback() {
local bak=${1:?用法: --rollback <备份目录名,如 .sdk-bak-20260927-223242>}
say "回滚到 $bak"
$SSH "
set -e
[ -d '$SITES/$bak' ] || { echo '找不到备份 $SITES/$bak'; exit 1; }
sudo -n cp -a $SITES/sdk $SITES/.sdk-failed-$TS
sudo -n rm -rf $SITES/sdk
sudo -n cp -a $SITES/$bak $SITES/sdk
echo -n '回滚后文件数: '; sudo -n find $SITES/sdk -type f | wc -l
" 2>&1 | tail -3
info "已回滚,失败版本留在 $SITES/.sdk-failed-$TS"
}
case "${1:-}" in
--check) do_check ;;
--rollback) do_rollback "${2:?用法: --rollback <备份目录名>}" ;;
"") do_deploy ;;
*) echo "用法: $0 [--check | --rollback <备份目录名>]"; exit 2 ;;
esac