Files
knowledge-governance/scripts/deploy-api-web.sh
T
姚章浩 90db243a68
build / build (push) Successful in 56s
fix(web): 旧 index.html 的哈希资源回退到「上一版构建」,页面不再空白/裸奔
现象(主人反馈:页面**偶尔**变成"只有文字没样式"):旧入口 index.html 引用的是上一版的
`index-CddbQIqQ.js` / `index-DLg1Y9tN.css`,而新镜像里只剩当次构建的两个文件
⇒ JS 404 白屏、CSS 404 只有文字没样式。触发条件是"短时间内多次重建 web + CDN/浏览器还拿着旧入口"。

修复(三层,各管一段):

1. **入口不缓存**(上一笔已做):`index.html` → `no-cache, must-revalidate`,CDN/浏览器每次回源校验;
2. **旧哈希资源仍可取**(本笔):`/assets/` 未命中时先回退到只读挂载的
   `deploy/legacy-assets/assets`(**上一版构建**的资源),再 404 ⇒ 还持有旧入口的客户端
   不会因为一个文件 404 而白屏/裸奔;该目录由 `scripts/deploy-api-web.sh` 在**每次重建前**
   从"正在运行的那个镜像"里落盘(`LEGACY_ASSETS=` 会打在部署日志里),并加进 .gitignore;
3. **未命中不再回退成 HTML**:避免浏览器把 index.html 当 JS 执行报一堆语法错误。

验证:旧入口的两个引用 `assets/index-CddbQIqQ.js`、`assets/index-DLg1Y9tN.css` 现在都是 200
(旧客户端可用);当前入口的 `assets/index-Dbnd-DW-.js`、`assets/index-Bgkt4A1i.css` 也是 200;
不存在的文件名仍明确 404;`nginx -t` 通过;本机 8088 与公网入口 kg.zheromiku.top 一致;make verify 15/15。
2026-10-14 10:58:01 +08:00

95 lines
6.2 KiB
Bash
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
# 重建并上线 governance api + web(本机 docker build 需要宿主代理 + --network host)
set -u
cd /home/zheromiku/knowledge-governance || exit 1
TS=$(date +%Y%m%d-%H%M%S)
PROXY_ARGS=(--build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890 --build-arg NO_PROXY=localhost,127.0.0.1 --build-arg GOPROXY=https://goproxy.cn,direct)
echo "=== 1. 打回滚标签 ($TS)"
docker tag knowledge-governance-p1-api:latest "knowledge-governance-p1-api:rollback-$TS" || exit 1
docker tag knowledge-governance-p1-web:latest "knowledge-governance-p1-web:rollback-$TS" || exit 1
echo "=== 1b. 构建身份(构建期注入:页面「当前运行」只认产物里的完整 SHA 精确匹配)"
# 唯一来源是 scripts/version.py --identity(与 Makefile、CI 同一份规则):不注入 ⇒ 页面只能显示"未知"。
IDENTITY_ARGS=()
while IFS= read -r line; do IDENTITY_ARGS+=("$line"); done < <(python3 scripts/version.py --identity 2>/dev/null | python3 -c '
import json, sys
try:
d = json.load(sys.stdin)
except Exception:
sys.exit(0)
release = d.get("release_version") or ""
count = d.get("commits_since_release")
print("--build-arg=VERSION=" + (release or "dev"))
print("--build-arg=RELEASE_VERSION=" + release)
print("--build-arg=BUILD_KIND=" + (d.get("build_kind") or "development"))
print("--build-arg=COMMITS_SINCE_RELEASE=" + ("" if count is None else str(count)))
print("--build-arg=COMMIT_SHA=" + (d.get("commit_sha") or "unknown"))
print("--build-arg=BUILD_TIME=" + (d.get("built_at") or "unknown"))
')
if [ "${#IDENTITY_ARGS[@]}" -lt 6 ]; then
echo "IDENTITY_UNAVAILABLE"
echo " 取不到构建身份(scripts/version.py --identity 失败)⇒ 拒绝继续:"
echo " 否则构建出来的产物无法被「版本记录」页识别成「当前运行」,只会显示未知。"
exit 1
fi
printf ' %s\n' "${IDENTITY_ARGS[@]}"
echo "=== 1c. 保留上一版构建的静态资源(CDN/浏览器可能还持有旧 index.html)"
# 只保留**当前正在跑的那一版**:旧 index.html 的旧哈希引用仍能取到文件,
# 页面不会因为某个 JS/CSS 404 而空白或"裸奔"。目录只读挂载进 web 容器。
LEGACY_DIR="deploy/legacy-assets"
PREV_WEB_IMAGE=$(docker inspect -f '{{.Image}}' knowledge-governance-p1-web-1 2>/dev/null || true)
mkdir -p "$LEGACY_DIR/assets" && chmod 777 "$LEGACY_DIR" "$LEGACY_DIR/assets"
if [ -n "$PREV_WEB_IMAGE" ]; then
docker run --rm --user 0 --entrypoint sh -v "$PWD/$LEGACY_DIR:/out" "$PREV_WEB_IMAGE" \
-c 'cp -a /usr/share/nginx/html/assets/. /out/assets/ 2>/dev/null || true' >/dev/null 2>&1 || true
echo " LEGACY_ASSETS=$(ls -1 "$LEGACY_DIR/assets" 2>/dev/null | tr '\n' ' ')"
fi
echo "=== 2. 构建 api"
docker build --network host "${PROXY_ARGS[@]}" "${IDENTITY_ARGS[@]}" -f Dockerfile -t knowledge-governance-p1-api:latest . || { echo "API_BUILD_FAILED"; exit 1; }
echo "=== 3. 构建 web"
docker build --network host "${PROXY_ARGS[@]}" -f Dockerfile.web -t knowledge-governance-p1-web:latest . || { echo "WEB_BUILD_FAILED"; exit 1; }
echo "=== 4. 上线 api + web(不动依赖)"
# COMPOSE_FILES 让调用方叠加发布接线 / 门禁 override,默认只有基础 compose.yaml。
# 例:COMPOSE_FILES="-f compose.yaml -f compose.release-sim.yaml" bash scripts/deploy-api-web.sh
#
# 注意(2026-10-10 实测踩到):重建 api 时若不叠加 compose.release-gates.yaml,两道生产门禁会被
# **静默关掉**(PRODUCTION_RELEASE_REQUESTS_ENABLED/BACKUP_RESTORE_VERIFIED=false),页面只在点击
# 「授权发布」时回一句 "production release requests are disabled" —— 很容易误判成功能坏了。
# 因此下面做前置保护:正在运行的 api 若已开闸、而本次没叠加门禁文件,直接拒绝(确要关闸用 ALLOW_GATE_OFF=1)。
COMPOSE_FILES_EFFECTIVE="${COMPOSE_FILES:--f compose.yaml}"
GATES_PREV=$(docker exec knowledge-governance-p1-api-1 sh -lc 'printf %s "${PRODUCTION_RELEASE_REQUESTS_ENABLED:-}"' 2>/dev/null || true)
if [ "${GATES_PREV}" = "true" ] && [[ "${COMPOSE_FILES_EFFECTIVE}" != *compose.release-gates.yaml* ]] && [ "${ALLOW_GATE_OFF:-0}" != "1" ]; then
echo "REFUSED_WOULD_CLOSE_PRODUCTION_GATES"
echo " 当前 api 已开闸(PRODUCTION_RELEASE_REQUESTS_ENABLED=true),而本次 COMPOSE_FILES 不含 compose.release-gates.yaml,"
echo " 继续会用基础 compose 重建 api → 生产授权接口被静默关闭。"
echo " 保留门禁:COMPOSE_FILES=\"-f compose.yaml -f compose.release-sim.yaml -f compose.release-gates.yaml\" bash scripts/deploy-api-web.sh"
echo " 确实要关闸:ALLOW_GATE_OFF=1 bash scripts/deploy-api-web.sh"
exit 1
fi
docker compose ${COMPOSE_FILES_EFFECTIVE} up -d --no-build --no-deps api web || { echo "UP_FAILED"; exit 1; }
echo "=== 5. 状态"
docker ps --filter name=knowledge-governance-p1 --format '{{.Names}} {{.Status}} {{.Image}}'
echo "api image=$(docker inspect -f '{{.Image}}' knowledge-governance-p1-api-1)"
echo "web image=$(docker inspect -f '{{.Image}}' knowledge-governance-p1-web-1)"
echo "=== 6. 健康"
echo "health=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8088/health)"
echo "=== 7. 生产端点环境变量(只报名字与非空)"
docker exec knowledge-governance-p1-api-1 sh -lc 'for v in PRODUCTION_WEKNORA_BASE_URL PRODUCTION_WEKNORA_API_KEY_FILE PRODUCTION_WEKNORA_ALLOW_DOCKER_HTTP; do eval "val=\$$v"; if [ -n "$val" ]; then echo "$v=<set>"; else echo "$v=<empty>"; fi; done'
echo "=== 7b. 生产门禁状态(重建后必须与预期一致)"
docker exec knowledge-governance-p1-api-1 sh -lc 'for v in PRODUCTION_RELEASE_REQUESTS_ENABLED PRODUCTION_BACKUP_RESTORE_VERIFIED; do eval "val=\$$v"; echo "$v=${val:-<empty>}"; done'
echo "=== 8. 新包"
docker exec knowledge-governance-p1-web-1 sh -lc 'grep -o "index-[A-Za-z0-9_-]*\.js" /usr/share/nginx/html/index.html | head -1'
echo "=== 9. 构建身份是否真的进了产物(页面据此判「当前运行」)"
INJECTED=$(printf '%s\n' "${IDENTITY_ARGS[@]}" | sed -n 's/^--build-arg=COMMIT_SHA=//p')
if docker exec knowledge-governance-p1-api-1 sh -lc "strings /app/api | grep -q '$INJECTED'"; then
echo "INJECTED_COMMIT_SHA=$INJECTED(已进 api 产物)"
else
echo "INJECTION_MISSING(产物里没有 $INJECTED —— 页面会显示当前运行未知)"
fi
echo "DONE"