name: Deploy Web to ECS # 数字我前端(纯成人版) on: workflow_dispatch: {} # ★ 允许在 Actions 页面手动触发部署(老板要求 2026-07-28) push: # ⚠ 只监听 master。原来 [master, main] 是一把上膛的枪: # main 是落后 169 个提交的僵尸分支,往它推任何一行代码, # 都会把旧代码盖到生产上。(已发生过一次。main 现已删除。) branches: [master] paths: - 'digital-me-web/**' - '.github/workflows/deploy-adult-web.yml' # ★ 并发锁:同一时刻只跑一个前端部署,多次快连推【串行、不重叠】。 # 2026-07-20 事故:连推三次(80ed3f8/379b671/ed91b14)三个部署乱序上传 chunk, # 服务器一度新旧混装,懒加载的天下几章刷到「新 index.html 配已删旧 chunk」→ 打不开。 # cancel-in-progress: false ⇒ 不中途掐(scp/rsync 掐一半更糟),排队跑完; # GitHub 会把中间那个 pending 顶掉,最终落定的永远是最新一次 push 的构建。 concurrency: group: deploy-adult-web cancel-in-progress: false jobs: deploy: runs-on: ubuntu-latest defaults: run: working-directory: digital-me-web steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm cache-dependency-path: digital-me-web/package-lock.json - run: npm ci - run: npm run build:fast env: VITE_API_BASE_URL: https://digitalme-api.invoratec.cn - name: Upload dist to staging uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.ECS_HOST }} username: ${{ secrets.ECS_USER }} password: ${{ secrets.ECS_PASSWORD }} source: 'digital-me-web/dist/*' target: /tmp/dm-web-dist strip_components: 2 overwrite: true - name: Publish to nginx root + reload uses: appleboy/ssh-action@v1 with: host: ${{ secrets.ECS_HOST }} username: ${{ secrets.ECS_USER }} password: ${{ secrets.ECS_PASSWORD }} script: | # ★★ 2026-07-28:写死本站 SPA 目录,【绝不】扫描 nginx root、【绝不】按 index.html 内容猜目录。 # 起因:同源同品牌(数字我家族)多站同服务器时,按 "index.html 含 数字我" 猜目录,会把隔壁 # superdigitme("超级数字我")的前端一起覆盖(2026-07-28 事故)。数字我(成人)前端唯一落点就是这里。 # 要改路径,只改这一行 ROOT。 ROOT=/var/www/digitalme-spa RESOLVE="--resolve digitalme.invoratec.cn:443:127.0.0.1" NEW=$(grep -oE 'index-[A-Za-z0-9_-]+\.js' /tmp/dm-web-dist/index.html 2>/dev/null | head -1) if [ -z "$NEW" ]; then echo "::error::上传的 dist 里没有 index.html——上传步骤有问题,中止,不写盘。"; exit 1; fi mkdir -p "$ROOT" cp -rf /tmp/dm-web-dist/* "$ROOT"/ WROTE="$ROOT" echo "✓ 已写入固定目录 $ROOT (指纹 $NEW)" rm -rf /tmp/dm-web-dist # 清理上一次误传进 acme 目录的 SPA 文件(不动真正的 challenge 文件)。 rm -rf /var/www/acme-challenge/assets /var/www/acme-challenge/index.html 2>/dev/null || true # ── 缓存头(幂等【替换】,不再跳过): index.html 改 no-store —— 浏览器永不缓存首页,每次强制拿最新, # 根治「老用户/旧标签页一直加载旧 bundle、看不到新版」(no-cache 会被 bfcache 绕过,no-store 不会)。 # hash 资源仍长缓存(immutable)。带 nginx -t 校验, 失败即回滚。 ── CONF=/etc/nginx/conf.d/digitalme.invoratec.cn.conf if [ -f "$CONF" ]; then cp "$CONF" "$CONF.dmbak" sed -i '/# dm-cache-headers/d' "$CONF" # 先删旧头(如有), 再注入新的 sed -i '/index index\.html;/a location = /index.html { add_header Cache-Control "no-store, no-cache, must-revalidate"; } location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; } # dm-cache-headers' "$CONF" if nginx -t 2>/dev/null; then echo "✓ nginx 缓存头已更新(index.html=no-store)"; else echo "❌ nginx -t 失败, 已回滚缓存头"; cp "$CONF.dmbak" "$CONF"; fi else echo "✓ conf 缺失, 跳过缓存头" fi nginx -s reload 2>/dev/null || true sleep 2 # ★★ 发布后自检:以【公网真实 URL】(老板看到的那个)为准;不一致就把整张诊断表写进红色 ::error:: 标注 # ——GitHub 会把它显示在运行页顶部(就是老板一直能看到、能贴回来的那种),这样病根直接暴露、不用再猜。 LOCAL=$(curl -sk $RESOLVE https://digitalme.invoratec.cn/ 2>/dev/null | grep -oE 'index-[A-Za-z0-9_-]+\.js' | head -1) PUB=$(curl -s "https://digitalme.invoratec.cn/?cc=$$" 2>/dev/null | grep -oE 'index-[A-Za-z0-9_-]+\.js' | head -1) echo "=== 发布后自检 === 应发布=$NEW 本机served=$LOCAL 公网served=$PUB 写入目录=[$WROTE]" if [ "$NEW" != "$PUB" ]; then echo "::error::已写入 $WROTE 并 reload,但公网仍非最新。应发=$NEW 本机=$LOCAL 公网=$PUB。请确认 digitalme.invoratec.cn 的 nginx root 是否为 $WROTE。" exit 1 fi echo "✓✓ 自检通过:公网已是最新 $NEW"