From 512effa1adad2b497192dba57c90fbca52a22067 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 27 Sep 2026 23:00:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E7=AB=99=E7=82=B9=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=B5=81=E6=B0=B4=E7=BA=BF=EF=BC=88SDK=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E7=AB=99=20+=20introduce=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 部署 1911575 后 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"。 --- deploy-sdk-site.sh | 221 ++++++++++++++++++++++++++++++++++++++ docs/zh/deploy-runbook.md | 89 +++++++++++++++ 2 files changed, 310 insertions(+) create mode 100755 deploy-sdk-site.sh diff --git a/deploy-sdk-site.sh b/deploy-sdk-site.sh new file mode 100755 index 0000000..c734459 --- /dev/null +++ b/deploy-sdk-site.sh @@ -0,0 +1,221 @@ +#!/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 diff --git a/docs/zh/deploy-runbook.md b/docs/zh/deploy-runbook.md index 40540d2..133de6d 100644 --- a/docs/zh/deploy-runbook.md +++ b/docs/zh/deploy-runbook.md @@ -269,3 +269,92 @@ stage handler 入口自动加锁 + 锁不可重入所致,**插件侧问题** | 白名单不看参数 | 功能限制 | `find -delete`/`sed -i` 能逃,用户已知悉并选择先下发 | | `重复申请 stage 锁` | 插件缺陷 | 需改 SDK 模板并重编 9月14日的 `plugins/qq/plugin.bin` | | 群聊必须 @ 才触发 | 设计 | 放宽会在群里引发 unwanted 触发 | + +--- + +## 5. 部署站点(SDK 文档站 / introduce) + +`deploy-sdk-site.sh` 覆盖两个静态站,**与 homed / waiter 是独立部署单元**。 + +| 站 | 本地源 | 线上位置 | 构建 | +| --- | --- | --- | --- | +| SDK 文档站 | `third_party/homeagent-sdk/site_build/` | `/vol1/docker/navi-data/sites/sdk` | 需 `tools/apidoc/build.sh`(§5.3 坑①) | +| introduce | `site/`(排除 `README.md`) | `/vol1/docker/navi-data/sites/introduce` | 零构建,源即产物 | + +两站部署在**同一台** 106 的同一 `sites/` 目录下。 + +introduce 是**零构建**的 —— `site/` 里就是成品,脚本部署时**故意不带 +`README.md`**(那是仓库说明文档,不是站点资源)。 + +### 5.1 用法 + +```bash +bash deploy-sdk-site.sh --check # 只核对差异,不动线上 +bash deploy-sdk-site.sh # 构建 + 部署 + 验证 +bash deploy-sdk-site.sh --rollback .sdk-bak-<时间戳> # 回滚 +``` + +`--check` 逐字节比对本地与线上,两个站都一致时可直接跳过部署。 + +### 5.2 链路(实测确认) + +``` +本机 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 +``` + +登录 106 只能用 `admin@` + `sudo -n`,`root@` 会被拒。 + +### 5.3 ★ 两个坑 + +**① 构建必须走 `tools/apidoc/build.sh`,不能裸跑 `mkdocs build`** + +裸跑会丢掉整个 `api/*.md` 和 `llms.txt` —— 而 `llms.txt` 正是**给 agent 直读的入口**。 +正确产物 **106 个文件**,裸跑只有 **78 个**。 + +**② 打包不能走设备网关** + +106 的 waiter 白名单(`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` 也不能打包)⇒ `device_ctl_cmdrun` 打不了包, +必须走 SSH 直连。 + +> 早先这里记的是「只放行 `ls`/`stat`/`find`/`cat`」,那是 waiter 白名单 +> 硬编码 18 条时的状况。2026-09-27 部署 `7193446` 后扩到 22 条 —— +> 结论(打不了包)不变但**理由已变**,别照旧文字理解。 + +### 5.4 新增文档后要记得更新 nav + +`mkdocs.yml` 的 nav 没登记的页面,mkdocs 会明确警告 +`not included in the nav configuration` —— 等于**文档写完了但在站点里不可达**。 + +改完 SDK 文档的完整流程: + +```bash +cd third_party/homeagent-sdk +# 1) 在 docs/guide/ 写文档 +# 2) 在 mkdocs.yml 的 nav 里登记 ← 漏了这步站点里找不到 +# 3) 重新生成(含 docs/api/* 与 llms.txt 同步) +tools/apidoc/build.sh +# 4) 部署并验证 +bash ../../deploy-sdk-site.sh +``` + +验证线上是否真的可访问(**别只看本地产物**): + +```bash +curl -s -o /dev/null -m 10 -w '%{http_code}\n' -k https://sdk.homeagent.jianfgit.xyz/guide/<新文档>/ +``` + +### 5.5 回滚点 + +每次部署留 `.sdk-bak-<时间戳>`(同 `sites/` 下)。`--rollback` 会先把当前站 +`cp -a` 成 `.sdk-failed-<时间戳>` 再恢复,失败版本不丢。