@@ -0,0 +1,201 @@
---
name : slark-cicd
description : Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、staging 真机预验、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark( qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。
---
# Slark CI/CD runbook
驱动 Slark 从改代码到上线的端到端流程。权威流程与完整细节见仓库文档
`docs/cicd-staging-production.md` (本技能命令与之一致);本文件是可直接照做的分阶段清单。
仓库:`qiudl/qiu-slark` (本机克隆通常在 `/Users/donglinlai/coding/slark` )。所有命令在**仓库根**执行。
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
## 铁律(先记死)
- GitHub `origin/main` 是唯一代码权威、合并入口与生产发布来源;`internal` 只用于 CI/镜像,永不发布。
- 生产发布只接受**最新 `origin/main` 的精确 40 位 SHA**; `HEAD != origin/main` 会被 `deploy.sh` 拒绝。
- 生产发布必须在**普通 clone** 中执行,且 `.git` 必须是目录;`release-prod.sh` / `deploy.sh` 会拒绝
`.git` 为指针文件的 git worktree。不要污染现有 checkout:主目录脏或被占用时,新建干净的临时 clone。
- 审批式部署,永不自动:亲手敲的 `sudo` /`rm -rf` /`dd` /`curl|sh` /包安装是 `destructive` ( NEVER_AUTO),
**每次都要人审 ** ;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
( Name=`slark-staging` , 2026-08-18 核验公网 IP `15.135.112.181` )。公网 IP 自动分配,发布前必须按
实例 ID 重查并确认 Name/状态;不得凭历史 IP 判断目标。
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
- staging 发布若影响 `packages/daemon` 、`packages/runtime` 、Server/daemon 公共协议或 Desktop 公共运行链,
必须把 `server-staging` 与 `daemon-staging` 更新到同一精确 SHA;两者版本不一致不得宣告 staging 成功。
- 只修 PR 范围内的 CI 失败;不为过检去改 CI/workflow 或做无关改动。
## 选择链路
- 「发布/上线/部署生产」→ 生产发布链:阶段 1→2→4→5。
- 「Desktop/桌面版打包、发布、上传 OSS」→ 阶段 1→2 后走 `docs/desktop-oss-release.md` 的 m5max 双平台链路。
- 「盯 CI/合并 PR」→ 阶段 1→2(用 babysit 心态循环到 green + mergeable + 评论收口)。
- 「复现/验证运行时行为、别碰生产」→ staging 预验链(阶段 3,可独立随时跑)。
## 阶段 1: PR 与内网 gate( `ci/internal-gate`)
真实门禁是内网 CI( GitHub Actions 已停用)。检查与自查:
``` bash
gh pr checks <PR> # 应见 ci/internal-gate
gh pr view <PR> --json state,mergeable,mergeStateStatus,isDraft,reviewDecision
```
- 基础门(全 PR):release-contract / member-gate / authz-matrix / controlled-content-exits /
onconflict-predicate / migration-idempotency / wsl-path-translation / typecheck / test(全量)。
- 条件外部门(按改动路径):改 server DB/迁移/schema → `postgresql-security` ;改 web → `web-e2e` ;
android / daemon+desktop 各有门。
- 本地复跑基础门可跑部分:`pnpm -r typecheck && pnpm test` 。
红了:只修本 PR 范围内的失败。疑似无关的合并阻塞→先 merge 最新 `main` (可能别的 PR 已修)。
`BLOCKED: <label> requires <VAR>` = runner 环境未配外部门命令,属运维问题,不是 PR 代码问题。
## 阶段 2:合并到 `origin/main`
合并前确认:`mergeable=MERGEABLE` 、`mergeStateStatus=CLEAN` 、非 draft、`ci/internal-gate` 绿、评论收口。
``` bash
gh pr merge <PR> --merge # 仓库风格:merge commit
```
若本机 `main` 被其他 worktree 占用导致 `--delete-branch` 本地 checkout 失败(属本地副作用):
``` bash
gh pr view <PR> --json state,mergedAt,mergeCommit # 确认 GitHub 侧 MERGED
git push origin --delete <feature-branch> # 删远端
git branch -D <feature-branch> # 删本地(先切走)
```
## 阶段 3:staging 真机预验(可选,随时)
### 3.1 当前权威路径:Slark 独立 staging
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e` ( Name=`slark-staging` )。它使用自动分配
公网 IP,因此每次部署前先查询当前 IP;2026-08-18 核验值为 `15.135.112.181` :
``` bash
aws ec2 describe-instances --region ap-southeast-2 \
--instance-ids i-013e5ca3fdfddc13e \
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
--output table
export STAGING_SSH_KEY = ~/.ssh/cloud-server-syd.pem
export STAGING_REMOTE = ubuntu@15.135.112.181
export STAGING_REMOTE_IP = 15.135.112.181
export STAGING_BASE = http://15.135.112.181
scripts/deploy-staging.sh --migrate-dry-run
scripts/deploy-staging.sh
STAGING_TOKEN = $( cat /tmp/.staging_token) scripts/staging-seed.sh
scripts/staging-verify-isolation.sh
```
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
`docs/req-20260811-0059-staging-environment-design.md` 。
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging` 。全程使用隔离数据、数据库、密钥和状态目录,
但宿主机身份仍是 ai-proj staging;普通“Slark 预生产”不得走这里。仅在任务明确要求 cohost 兼容验收时,
先在**干净的独立 worktree**检出待验精确 SHA,再执行:
``` bash
STAGING_SSH_KEY = ~/.ssh/ai_proj_stg.pem \
STAGING_KNOWN_HOSTS_FILE = ~/.ssh/known_hosts \
STAGING_REMOTE = ubuntu@82.157.141.202 \
STAGING_REMOTE_IP = 82.157.141.202 \
scripts/deploy-staging-cohost.sh
```
如果改动影响 daemon/runtime/公共协议,还需用 Compose `daemon` profile 更新 `daemon-staging` ,并核对
Server/daemon revision 同为待验 SHA。
### 3.3 环境选择检查
普通 Slark staging 默认走 3.1;只有用户或需求明确说“ai-proj cohost”才走 3.2。任何验收都必须记录:
环境稳定身份、当次 IP、待验 40 位 SHA、Server/daemon revision/status、Server health 和目标功能结果。
routed-remote 要 `network=enabled` 必须同时满足:① agent `workspace_access=write` +
`repository_access=write` ;② per-agent 工作区是可写 git worktree。细节见
`docs/req-20260811-0059-staging-environment-design.md` 。staging 刻意不碰 `deploy.sh` 的生产闸门。
## 阶段 4:生产发布(审批式)
**唯一姿势 ** :从干净的普通 clone 发布最新 `origin/main` ( `HEAD == origin/main` 精确 SHA)。staging 可用
独立 worktree,但生产脚本为保证同步与发布边界,会拒绝 worktree;不要在用户已有脏 checkout 中清理或发布。
``` bash
# 若当前仓库不是普通 clone,先在安全父目录创建一次性干净 clone:
git clone https://github.com/qiudl/qiu-slark.git <clean-production-clone>
cd <clean-production-clone>
git fetch origin main
test -d .git # 必须通过;worktree 的 .git 是文件
git status --porcelain # 必须干净
git checkout --detach " $( git rev-parse origin/main) " # 在产品分支上时先 detach 到该 SHA
pnpm release:prod # = release-prod.sh → deploy.sh --backend --no-install --no-mirror
# 备选:scripts/deploy.sh --pull --backend
```
- `deploy.sh` 用 `check-production-release-sha.sh` 锁 `HEAD == origin/main` SHA,不符即拒。
- `--backend` 才跑迁移:owner 连接(DDL 权)与运行时 `slark_app` (DML-only)分离;幂等、失败即非零。
- 命中审批闸(发布/sudo)→ **等人批 ** ;不要绕。脚本内部 SSH 载荷子进程的 sudo 不触发闸。
- 普通工作 clone 发布完切回原分支:`git checkout <branch>` ;一次性发布 clone 可保留作审计,清理时用可恢复方式。
## 阶段 5:发布后核查
``` bash
scripts/verify-deploy.sh # 健康(前端/api/health=200)、bundle、server/daemon SHA、迁移集、federation、凭据
```
预期 `✓ deploy verification passed: server=<sha> daemon=<sha>` 且 `<sha> == origin/main` ,舰队 `online ≥ 发布前` 。
镜像落后告警(默认 `--no-mirror` )非阻断,需补推按告警的幂等命令(需 Bitwarden session);镜像仅单向 fast-forward。
## Desktop m5max 双平台发布
Desktop 安装包不走 Server 生产部署脚本。权威流程见仓库 `docs/desktop-oss-release.md` ,已验证基线为
REQ-20260819-0005 / Desktop 0.3.1:
- 版本 PR 通过 `ci/internal-gate` 并合并后,m5max 从最新 `origin/main` 精确 SHA 的干净普通 clone 构建。
- m5max 原生构建、Developer ID 签名并公证/staple macOS DMG/ZIP;交叉构建 Windows x64 NSIS EXE。
- Darwin 不能执行 Windows 原生 `smoke:windows` 。只有发布负责人明确接手 Windows 原生验收时才能继续交叉打包,交付证据必须标注该边界。
- 资产不回传开发机;由 m5max 直接上传 `xiaoqu-public-file/slark/desktop/releases/v<version>/` , manifest 最后更新。
- `xiaoqu-public-file` 的 canonical 凭证源是发布控制机上的 `/Users/donglinlai/coding/param.rxt` (必须为 `0600` );AK/SK 分别位于 XML 标签 `ossAccessKeyId` / `ossAccessKeySecret` 。不得改猜 Bitwarden、Keychain、通用 `credentials.env` 或 m5max 本地配置;只有该文件缺失、权限不符或标签解析为空时才停止并报告。
- m5max 不持久化 OSS 凭证。发布控制机从 `param.rxt` 提取 AK/SK 后,只用 `printf '%s\n%s\n' ... | ssh m5max ...` 经 SSH stdin 传两行;远端用 `read -r` 接收并仅为当次 shell 导出 `OSS_ACCESS_KEY_ID` / `OSS_ACCESS_KEY_SECRET` ,随后运行上传脚本。禁止 `scp` 凭证文件、写远端临时文件或把值打印到日志。
- m5max 非交互 SSH 必须显式补 `PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$PATH` ;已验证 `ossutil` 位于 `/Users/johnq/.local/bin/ossutil` 。执行前检查 Node、三个安装包、精确 main SHA 和干净工作树,避免因交互 shell 环境差异重新猜路径。
- m5max 默认出口若走 `utun` ,只为当次 `ossutil` 使用 `--bind-address "$(ipconfig getifaddr en0)"` ,不得修改整机 Tailscale/默认路由。
- 完成前核对三份资产 SHA256/大小、macOS 签名与公证、公开 URL/manifest,并回写 ai-proj 证据。
## 推送路由自查(换机器/新克隆必做)
``` bash
git remote -v # origin 收发都应是 https://github.com/qiudl/qiu-slark.git
git config --get-regexp '^url\.' # 有输出 = pushInsteadOf 改写,push 可能被静默重定向到内网
scripts/git-release-governance.sh check-local
scripts/git-release-governance.sh status
```
## 故障分流
| 现象 | 处置 |
| --- | --- |
| `release-prod` 拒绝 HEAD≠origin/main | `git fetch origin main` 后 detach 到 origin/main SHA,工作树须干净 |
| `release-prod` 拒绝 git worktree / `.git` 不是目录 | 改用干净的普通 clone,锁定最新 `origin/main` ;不要在现有脏 checkout 中强行切换 |
| `ci/internal-gate` red | 看 gate 分组日志找 FAIL 项;`pnpm -r typecheck && pnpm test` 本地复现修复 |
| `BLOCKED: <label> requires <VAR>` | runner 未配外部门命令,报运维配 `SLARK_CI_*_CMD` ,非 PR 问题 |
| 合并阻塞但与 PR 无关 | merge 最新 `main` 再看 |
| push 成功但 GitHub 没有 | 查 `git config --get-regexp '^url\.'` ,移除 pushInsteadOf 或显式指定 GitHub URL |
| staging routed-remote 恒断网 | 查两道放网杠杆(workspace_access=write + 可写 git worktree) |
| cohost Server 已更新但 daemon 仍是旧 SHA | 用 `--profile daemon up -d daemon-staging` 更新 daemon;核对两容器 revision 后才算完成 |
| cohost daemon 启动即退出/fatal | 看 `docker logs slark_daemon_staging` ;修复凭据/环境或版本契约,禁止用旧 daemon 冒充验收通过 |
| verify 失败 SHA 不符 | 部署到了旧码,重发 |
## ai-proj 流程(强制)
任何改产品/代码行为的工作,动手前先在 ai-proj 立项 + 建 Task + 关联,实现/测试/交付回写;
commit/PR 引用 `REQ-...` 。纯文档/工具/只读排查除外。详见仓库 `AGENTS.md` / `CLAUDE.md` 。