#!/usr/bin/env bash # scripts/deploy-mgr1.sh — browser-server 传输/部署 mgr1(192.168.1.51)。 # # 🟡🟡🟡 高危操作声明(devops-role-boundaries 分级)🟡🟡🟡 # 本脚本面向 **primary 生产节点 mgr1**(非 dev-swarm),属 🟡 级高危操作: # - mgr1 在跑 PG / MinIO / WSG-prod :8562 / ASW 等生产服务; # - 执行前必须经用户「确认执行」明确授权(含糊的「好」「修吧」不算确认); # - 默认 dry-run:不带 --apply 参数时只做只读探测与打印,不写任何远端状态。 # # 🔴 红线(Contract §9):本轮(run-20260901-browser-impl)绝不 SSH mgr1、 # 绝不 stack deploy 生产、绝不 git push。本脚本只交付不执行; # 部署轮须另走 🟡 前置确认(free -g / ss -tln / 影响面 / 回滚)。 # # 回滚:docker stack rm browser-server(命名卷 browser-server-data 保留,数据不丢)。 # # 前置密钥:仓库根 deploy.env(gitignore,不入 git)需含: # BROWSER_SERVER_KEYS_SEED=... # BROWSER_SERVER_ADMIN_TOKEN=... # PROXY_SUB_URLS=... # SEARXNG_SECRET=... # 现网凭据交接纪律:明文仅 gitignore 文件 + 线下传递(service-secret-protocol §4)。 set -euo pipefail # ---------- 变量区 ---------- MGR1="${MGR1:-swarm-mgr1}" # primary mgr1(~/.ssh/config 别名:ubuntu + id_ed25519_onesvm_daas) STACK_NAME="browser-server" STACK_FILE="stacks/browser-server.yml" DEPLOY_ENV="deploy.env" SELF_IMG="onesvm/browser-server:dev" TRAF_IMG="onesvm/trafilatura-http:dev" # 第三方镜像清单(repo:tag@amd64-manifest-digest;Mac 拉取 → save/load, # 生产节点禁直连 Docker Hub)。注意必须钉 amd64 manifest digest 而非 index # digest:--resolve-image never 下 index digest 与载入镜像不匹配会导致容器无法启动。 THIRD_PARTY_IMAGES=( "searxng/searxng:2026.8.29-d226b78bc@sha256:61ad36fcf71eb300f7d64216a6a04e010754755c7f5d33a526bd52e597e78c69" "metacubex/mihomo:v1.19.24@sha256:9e37208fae8afa4c8b83d14ff2e9771b99178ebaf65a2c4fb388bc67ecefe4dc" "lightpanda/browser:0.3.7@sha256:447598e4c103b70b0fb610813e72795da44341c564e1ec872c3a490c1bc6aebe" "chromedp/headless-shell:151.0.7922.109@sha256:5f877a2a559dea1a99fb750da695d28a020cdd49db660aead6c78a46e3c7dd50" "alpine:3.20@sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e" ) APPLY=0 [ "${1:-}" = "--apply" ] && APPLY=1 # ---------- 步骤 ①:前置只读探测(free -g / ss -tln;dry-run 也会做,只读无副作用) ---------- log() { printf '[deploy-mgr1] %s\n' "$*"; } log "== 前置探测(只读): mgr1 内存余量 / 8640 端口占用 ==" ssh "$MGR1" 'free -g | head -2; echo ---; ss -tln | grep -E ":8640\b" || echo "端口 8640 空闲"' log "(🟡 请将上述输出发给用户确认内存余量与端口空闲后再 --apply)" if [ "$APPLY" -ne 1 ]; then log "DRY-RUN 结束(未做任何变更)。确认执行请运行:$0 --apply" exit 0 fi # ---------- 密钥加载(fail-closed:deploy.env 缺失即拒绝) ---------- if [ ! -f "$DEPLOY_ENV" ]; then log "错误:缺 ${DEPLOY_ENV}(gitignore;模板见 server/.env.example)——fail-closed 退出" exit 1 fi # shellcheck disable=SC1090 set -a; . "$DEPLOY_ENV"; set +a for var in BROWSER_SERVER_KEYS_SEED BROWSER_SERVER_ADMIN_TOKEN PROXY_SUB_URLS SEARXNG_SECRET; do if [ -z "${!var:-}" ]; then log "错误:$var 未设置(${DEPLOY_ENV})——fail-closed 退出" exit 1 fi done # ---------- 步骤 ②:Mac 构建 amd64 镜像(自研两枚) ---------- log "== 构建 $SELF_IMG / ${TRAF_IMG}(linux/amd64)==" make image PLATFORM=linux/amd64 make image-trafilatura PLATFORM=linux/amd64 # ---------- 步骤 ③:自研镜像 save | load 到 mgr1 ---------- log "== save|load 自研镜像 → mgr1 ==" docker save "$SELF_IMG" "$TRAF_IMG" | ssh "$MGR1" 'docker load' # ---------- 步骤 ④:第三方镜像逐枚 save | load ---------- # (searxng / mihomo / lightpanda / headless-shell / alpine;swarm 多节点时每节点都要 load) for ref in "${THIRD_PARTY_IMAGES[@]}"; do img="${ref%@*}" # repo:tag(save/load 与 stack 引用统一走此 tag) log "== pull(amd64) + save|load 第三方镜像 $img → mgr1 ==" docker pull --platform linux/amd64 "$ref" docker tag "$ref" "$img" # 钉 amd64 变体到 tag(Mac 本地 tag 可能仍是 arm64 变体) docker save "$img" | ssh "$MGR1" 'docker load' done # ---------- 步骤 ⑤:stack deploy(--resolve-image never,env 由 deploy.env 注入) ---------- # 同步 stack 引用的挂载文件(searxng 两份 settings)到 mgr1(stack 相对路径按执行目录解析)。 log "== 同步 $STACK_FILE 与挂载配置 → mgr1:/tmp/${STACK_NAME} ==" ssh "$MGR1" "mkdir -p /tmp/${STACK_NAME}" scp -r stacks "$MGR1:/tmp/${STACK_NAME}/" scp "$DEPLOY_ENV" "$MGR1:/tmp/${STACK_NAME}/" log "== stack deploy(🟡 生产写入动作,前面已获 --apply 授权)==" ssh "$MGR1" "cd /tmp/${STACK_NAME} && set -a && . ./deploy.env && set +a && \ docker stack deploy -c stacks/browser-server.yml --resolve-image never ${STACK_NAME}" # ---------- 步骤 ⑥:尾部清理(仅 dangling;安全,不删命名镜像) ---------- log "== 本机 dangling 清理(docker image prune -f)==" docker image prune -f log "== 部署完成。验证建议:ssh $MGR1 'docker stack ps ${STACK_NAME}' / curl :8640/healthz ==" log "回滚:ssh $MGR1 'docker stack rm ${STACK_NAME}'(命名卷 browser-server-data 保留)"