Files
ai-proj-helper/skills-dev/slark-cicd-plugin/skills/SKILL.md

277 lines
18 KiB
Markdown
Raw Permalink 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.
---
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`;当前 governed staging 候选入口以仓库脚本契约和历史成功回执为准。
该文档中直接运行 `deploy-staging.sh` 的示例尚未列出当前脚本要求的不可变候选材料。
仓库:`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-10-23 只读核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
实例 ID 从 AWS 控制面重查并确认 Name/状态/IP;不得凭历史 IP 或 SSH 可达性判断发布目标。
- `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。`15.135.112.181` 是 2026-10-23 的只读核验值,
不是可直接用于将来发布的固定地址:
```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
# 只使用上一步 AWS 返回且已核对 Name=slark-staging、State=running 的公网 IP。
export STAGING_IP='<verified-public-ip>'
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
export STAGING_REMOTE="ubuntu@$STAGING_IP"
export STAGING_REMOTE_IP="$STAGING_IP"
export STAGING_BASE="http://$STAGING_IP"
# 使用 Slark 应用内的 project ID,不是 ai-proj 的数字项目/需求/任务 ID。
export SLARK_RELEASE_PROJECT_ID='<verified-slark-app-project-id>'
export SLARK_RELEASE_TASK_ID='REQ-...'
export SLARK_RELEASE_ID='release-staging-<40-sha>-<unique-attempt>'
# 普通发布还需从既有受信 provider 提供 mirror token;不要打印或写进日志。
# 先确认候选 SHA 在 canonical GitHub 上可获取,且包含当前 main 的全部提交。
bash scripts/release-staging-governed.sh '<40-sha>'
scripts/staging-verify-isolation.sh
# 使用上一步发布输出的同一 release receipt;观察窗口未结束时保留原 receipt 重试。
scripts/complete-staging-verification.sh '<release-receipt-file>'
```
这是历史 Slark 发布实际采用的 governed 入口:控制器从 canonical GitHub
获取不可变候选、持有本地锁,内层 `deploy-staging.sh` 执行远端构建、数据库迁移、
切换和健康检查。需要先单独演练迁移时,按仓库发布文档生成同一精确 SHA 的
`staging-candidate` manifest/archive/source,再向 `deploy-staging.sh --migrate-dry-run`
传入 `STAGING_CANDIDATE_MANIFEST`、`STAGING_CANDIDATE_ARCHIVE` 和
`STAGING_SOURCE_DIR`;当前 `deploy-staging.sh` 不接受缺少这些候选材料的直接调用。
这条演练在隔离数据库副本执行 pending up,不重启 Server;正式 governed 发布则在
live staging 数据库执行 migrate up。历史 2026-10-22 成功回执见
`acceptance/REQ-20261010-0007-rejection-execution-20261022/staging-deploy-r2.log`
与 `staging-verify-r2.log`。历史脚本里为特定 DSH UI 发布加入的配对、手动切换及回退步骤
不属于普通 Slark Server/daemon 发布命令,不能直接照搬。
候选若包含 canonical main 尚未收录的 migration,或修改发布控制面,staging admission
只允许在隔离 PDV 中验证,不会切换共享 staging。隔离验证通过后,先按 PR 门禁将改动并入
canonical main,再用新的 main 精确 SHA 重新准备候选和执行共享 staging 发布;不要把
`isolated_required` 的成功回执写成共享 staging 已发布。
若镜像 token 确实不可读,仓库另有 `deploy-staging.sh --no-mirror --reason '<具体原因>'`
审计路径。仅在已按官方 `staging-candidate.mjs` 生成并核验同一精确 SHA 的 manifest、archive、
source 和 daemon seed revision 列表,且已满足正常 CI、环境身份和 release admission 门禁时使用;
该路径不读取 Bitwarden、不切换 `staging-latest` 镜像频道。历史先例见
`docs/reports/REQ-20261012-0006-delivery-status.md`。使用后应明确记录“共享 staging 已发布,
镜像频道未更新”,不能把 no-mirror 写成镜像发布成功。
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
`docs/req-20260811-0059-staging-environment-design.md`。
**AWS 凭证不可用时的只读排查(2026-10-23 记录):**先查 Slark 自己的历史发布记录,
例如 `acceptance/REQ-20260930-0015/release-staging-main-history-window.sh`;其中记录的
staging IP 是 `15.135.112.181`,Slark 应用 release project ID 是
`tpqvorREPTj2kx9poKJWH`。这些值只是定位线索,使用前须重新核对。可用已有
`cloud-server-syd.pem`、`StrictHostKeyChecking=yes` 只读 SSH 到历史 IP,并核对远端
`/sys/devices/virtual/dmi/id/board_asset_tag`、IMDSv2 `instance-id` 与
`public-ipv4`;2026-10-23 三项分别核对为 `i-013e5ca3fdfddc13e`、
`i-013e5ca3fdfddc13e`、`15.135.112.181`,当时 `slark-server` 和
`slark-daemon-staging` 均在运行。不要输出 IMDS token 或应用凭证。
只读身份核验示例(历史 IP 仅用于定位;任何一项不匹配就停止):
```bash
ssh -i ~/.ssh/cloud-server-syd.pem -o BatchMode=yes \
-o StrictHostKeyChecking=yes -o ConnectTimeout=5 \
ubuntu@15.135.112.181 'bash -s' <<'STAGING_IDENTITY'
set -euo pipefail
tag="$(cat /sys/devices/virtual/dmi/id/board_asset_tag)"
token="$(curl -fsS --noproxy '*' --max-time 3 -X PUT \
-H 'X-aws-ec2-metadata-token-ttl-seconds: 60' \
http://169.254.169.254/latest/api/token)"
id="$(curl -fsS --noproxy '*' --max-time 3 \
-H "X-aws-ec2-metadata-token: $token" \
http://169.254.169.254/latest/meta-data/instance-id)"
ip="$(curl -fsS --noproxy '*' --max-time 3 \
-H "X-aws-ec2-metadata-token: $token" \
http://169.254.169.254/latest/meta-data/public-ipv4)"
test "$tag" = i-013e5ca3fdfddc13e
test "$id" = "$tag"
test "$ip" = 15.135.112.181
printf 'instance=%s public_ip=%s\n' "$id" "$ip"
STAGING_IDENTITY
```
这条 SSH/DMI/IMDS 路径只证明所连主机的实例身份,**不提供 AWS Name 标签的当次证明,
不能代替上面的发布前控制面核验**。`docs/runbooks/REQ-20260922-0005-staging-watchdog-emergency-recovery.md`
中的替代路径仅授权给那次特定事故,不能移用于普通 staging 发布。不要借用其他项目
的 AWS profile;若缺少 Slark AWS 控制面凭证,停在只读排查并记录缺口。2026-10-23
实例自带角色 `dsh-identity-staging-ec2` 调用 `DescribeInstances` 返回
`UnauthorizedOperation`,不能靠这个角色补齐 Name 核验。
### 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`。