Files
pangolin/docs/cicd-design.html
wangjia 7c26050cdb
Deploy Site / deploy-site (push) Has been cancelled
docs(spec): 官网部署改 Cloudflare Pages(架构变更说明)
节点 :443 被 VPN 占用 + CF 免费套餐改回源端口需 Enterprise → 官网改 CF Pages 托管
(纯静态/全程 HTTPS/CSP 生效/无 :443 冲突),已上线 pangolin.yanmeiai.com。
deploy-site.sh 用 wrangler,需 CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:08:20 +08:00

174 lines
13 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pangolin CI/CD 全流程 · 设计方案(#30)</title>
<style>
:root{
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
.wrap{max-width:960px;margin:0 auto;padding:48px 24px 96px}
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
.sub{color:var(--fg2);font-size:15px;margin:0 0 32px}
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h3{font-size:16px;margin:26px 0 8px;color:var(--accent2)}
p{margin:10px 0}
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
pre{background:#0a0c11;border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:13px;line-height:1.55;color:#cdd3df}
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
.tag.info{background:rgba(95,176,201,.16);color:var(--accent2)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
.card h3{margin-top:0;color:var(--fg)}
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14px}
th,td{text-align:left;padding:9px 12px;border-bottom:1px solid var(--border);vertical-align:top}
th{color:var(--fg2);font-weight:600;font-size:13px}
td code{font-size:.85em}
ul,ol{padding-left:22px;margin:10px 0}
li{margin:5px 0}
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
.small{color:var(--fg2);font-size:13px}
a{color:var(--accent2)}
.back{display:inline-block;margin-bottom:24px;font-size:13px}
b{color:#fff}
</style>
</head>
<body>
<div class="wrap">
<a class="back" href="index.html">← 文档索引</a>
<h1>Pangolin CI/CD 全流程 · 设计方案</h1>
<p class="sub">#30 · 2026-07-05 · <span class="tag warn">设计定稿待审</span> · 范围 A~F(排除 iOS、备份#26、TLS#25)· 真相源 <code>docs/superpowers/specs/2026-07-05-cicd-design.md</code></p>
<div class="lead">
<b>目标:</b>tag 触发的 <b>编译 → 测试 → 发版(Gitea release)→ 部署</b> 全自动。参考 jiu 的
<code>.gitea/workflows</code> + <code>scripts/ci/*.sh</code> 结构,适配 pangolin 的部署目标(pangolin1 /
pangolin.yanmeiai.com)与多端产物。现状:仅 <code>ci.yml</code> 校验无部署,服务端手动部署、官网未部署、下载死链。
</div>
<h2>1. 范围</h2>
<table>
<tr><th>子块</th><th>内容</th></tr>
<tr><td><b>A 基座</b></td><td><code>scripts/ci/*</code>env/provision/test/release/notify/lib-forgejo+ checks 保留</td></tr>
<tr><td><b>B 官网</b></td><td>Astro 构建 → 部署 <code>pangolin.yanmeiai.com</code></td></tr>
<tr><td><b>C 服务端</b></td><td>交叉编译 server/agent/migrate → release → ssh pangolin1(备份→migrate→换二进制→重启→健康检查)</td></tr>
<tr><td><b>D Android</b></td><td>apkarm64release keystore 签名)→ release 资产</td></tr>
<tr><td><b>E macOS</b></td><td>公证 dmgDeveloper ID + notarytool)→ release 资产</td></tr>
<tr><td><b>F Windows</b></td><td>exe/installerInno Setup)→ release 资产</td></tr>
</table>
<p class="small"><b>排除:</b>iOS(G,未来)、SQLite 备份/容灾(#26)、控制面 TLS(#25)。</p>
<h2>2. 已锁定决策</h2>
<table>
<tr><th>维度</th><th>决定</th><th>理由</th></tr>
<tr><td>runner</td><td>nas=官网+服务端(容器化)· mac=Android+macOS · windows=Windows</td><td>nas 常在线且 Astro/Go 轻量(非 Flutter Web);mac/windows 做必须它们的活</td></tr>
<tr><td>触发</td><td>tag <code>site-v*</code> / <code>server-v*</code> / <code>client-v*</code> + <code>manual.yml</code> 手动派发</td><td>同 jiu,发版即部署,可手动重放</td></tr>
<tr><td>macOS 签名</td><td>mac 自动 Developer ID 签名 + notarytool 公证 + staple</td><td>凭据入 secret(见 §6</td></tr>
<tr><td>Android 签名</td><td>正式 release keystore</td><td>app 级专属签名身份</td></tr>
<tr><td>下载链接</td><td>官网 href 指向 Gitea release 稳定资产 URL</td><td>发版即更新,见 §5</td></tr>
<tr><td>镜像</td><td>goproxy.cn / flutter-io.cn</td><td>国内网络</td></tr>
</table>
<h2>3. 架构</h2>
<h3>3.1 共享基座 <code>scripts/ci/</code>(镜像 jiu</h3>
<ul>
<li><code>_env.sh</code> —— 公共环境(镜像源、路径、版本号解析 <code>${tag#prefix-v}</code></li>
<li><code>lib-forgejo.sh</code> —— release 建/查 + 资产上传(用 <code>FORGEJO_TOKEN</code></li>
<li><code>provision-mac.sh</code> —— mac 幂等装 flutter / xcode-select / gomobile / NDK+JDK17</li>
<li><code>test.sh</code><code>notify.sh</code><code>compile-*.sh</code><code>deploy-*.sh</code><code>release-*.sh</code></li>
</ul>
<p class="small">每个 <code>compile-*</code> 封装该端已验证的构建命令(Android 走 <code>build-libbox.sh android</code> +
<code>flutter build apk --split-per-abi</code>macOS 走 Developer ID 签名 + <code>notarytool submit --wait</code> +
<code>stapler</code>)。工作流只调脚本,逻辑在脚本里、便于本地复现。</p>
<h3>3.2 工作流 <code>.gitea/workflows/</code></h3>
<table>
<tr><th>工作流</th><th>触发</th><th>runner</th><th>步骤</th></tr>
<tr><td><code>checks.yml</code>(现 ci.yml</td><td>push 分支</td><td>nas</td><td>保留:shellcheck / openapi / redline / flutter analyze+test / go test</td></tr>
<tr><td><code>deploy-site.yml</code></td><td><code>site-v*</code></td><td>nas</td><td><code>node:20</code> 构建 Astro(注入 SITE_URL)→ <code>deploy-site.sh</code></td></tr>
<tr><td><code>deploy-server.yml</code></td><td><code>server-v*</code></td><td>nas</td><td><code>golang:1.25</code> 交叉编译 → test → release → <code>deploy-server.sh</code></td></tr>
<tr><td><code>build-android.yml</code></td><td><code>client-v*</code></td><td>mac</td><td>provision → <code>compile-android.sh</code>(签名 apk)→ release</td></tr>
<tr><td><code>build-macos.yml</code></td><td><code>client-v*</code></td><td>mac</td><td>provision → <code>compile-macos.sh</code>(签名+公证 dmg)→ release</td></tr>
<tr><td><code>build-windows.yml</code></td><td><code>client-v*</code></td><td>windows</td><td><code>compile-windows.sh</code>exe/installer)→ release</td></tr>
</table>
<h3>3.3 服务端部署(固化 F3/F4 手动那套,带回滚)</h3>
<div class="card">
<ol>
<li>scp <code>pangolin-{server,agent,migrate}</code> 到 pangolin1 <code>/tmp</code></li>
<li><code>systemctl stop pangolin-server</code></li>
<li><code>wal_checkpoint(TRUNCATE)</code><code>cp</code> 备份 <code>pangolin.db.bak-pre-&lt;tag&gt;</code></li>
<li><code>pangolin-migrate up</code>pangolin 用户);<b>失败即恢复备份 + 重启旧 server + 退出非零</b></li>
<li><code>install</code> 新二进制到 <code>/usr/local/bin</code>(旧的备份为 <code>.bak-&lt;tag&gt;</code></li>
<li><code>systemctl start</code> + <code>/healthz</code> 健康检查;agent 随连接自恢复</li>
</ol>
</div>
<h2>4. 官网部署 —— Cloudflare Pages</h2>
<div class="card" style="border-left:3px solid var(--warn)">
<b>架构变更(2026-07-06 实施):</b>原计划 rsync 到 pangolin1 的 nginx。但节点 <code>:443</code> 被 sing-box(VPN 数据面)占用,而 CF 免费套餐 proxied 回源只能打 :80/:443、改回源端口需 Enterprise —— 同机同 IP 上官网 HTTPS 与 VPN 无法共存。<b>故官网改由 Cloudflare Pages 托管</b>:纯静态、全程 HTTPS、<code>_headers</code>/CSP 原生生效、不落 VPS,从根上无 :443 冲突,也不拖累 VPN 机器。<b>已上线</b> <code>https://pangolin.yanmeiai.com</code>
</div>
<p>Astro <code>npm ci && npm run build</code><code>SITE_URL=https://pangolin.yanmeiai.com</code>)→ <code>dist/</code>
<code>npx wrangler pages deploy</code> 发布到 Pages 项目 <b>pangolin-site</b>(自定义域 <code>pangolin.yanmeiai.com</code>,CNAME → <code>pangolin-site.pages.dev</code>,proxied)。需 secret <code>CLOUDFLARE_API_TOKEN</code>(带 Account&gt;Pages&gt;Edit+ <code>CLOUDFLARE_ACCOUNT_ID</code>deploy 步骤在 <code>node:20</code> 容器内跑 wrangler。灾备:产物仍纯静态,可另 rsync 到镜像。</p>
<h2>5. 下载链接闭环(30A</h2>
<p><code>web/website/src/config/site.ts</code><code>downloads:{ android, macos, windows }</code>,值为 Gitea release
稳定 latest 资产 URLForgejo 支持 <code>…/releases/latest/download/&lt;asset&gt;</code> 则直用;否则构建期用
<code>FORGEJO_TOKEN</code> 查最新 <code>client-v*</code> 版本烘焙进 href)。<code>Download.astro</code> 各平台按钮读
<code>SITE.downloads.&lt;platform&gt;</code>。客户端发版后官网重部署即刷新(或 build-* 完成触发 deploy-site)。</p>
<h2>6. 密钥与作用域(solo / wangjia,命名对齐 jiu 以共用)</h2>
<table>
<tr><th>Secret</th><th>作用域</th><th>说明</th></tr>
<tr><td><code>FORGEJO_TOKEN</code> / <code>FORGEJO_URL</code></td><td>账户级</td><td>建 release + 传产物,jiu 复用</td></tr>
<tr><td><code>MACOS_DEVELOPER_ID_CERT_P12_BASE64</code> / <code>MACOS_DEVELOPER_ID_CERT_PASSWORD</code></td><td>账户级</td><td>Developer ID 证书(账号级),与 jiu 共用;续期改一处</td></tr>
<tr><td><code>APPSTORE_API_KEY_P8_BASE64</code> / <code>APPSTORE_API_KEY_ID</code> / <code>APPSTORE_API_ISSUER_ID</code></td><td>账户级</td><td>公证 API keyKEY_ID=<code>3PZTHR8YMJ</code>),与 jiu 同一把</td></tr>
<tr><td><code>DEPLOY_SSH_KEY</code></td><td>pangolin 仓库级</td><td>授权到 pangolin1,最小权限</td></tr>
<tr><td><code>ANDROID_KEYSTORE_BASE64</code> / <code>ANDROID_KEYSTORE_PASSWORD</code> / <code>ANDROID_KEY_ALIAS</code> / <code>ANDROID_KEY_PASSWORD</code></td><td>pangolin 仓库级</td><td>Android app 级专属签名(<b>pangolin 自己的 keystore,不复用 jiu</b></td></tr>
<tr><td><code>MACOS_APP_PROVISION_PROFILE_BASE64</code> / <code>MACOS_SYSEXT_PROVISION_PROFILE_BASE64</code></td><td>pangolin 仓库级</td><td>主 app + PacketTunnel sysext 描述文件(pangolin bundle 专属)</td></tr>
</table>
<p class="small">命名对齐 jiu<code>MACOS_*</code>/<code>APPSTORE_*</code>/<code>ANDROID_*</code>):<b>Apple 那套放账户级 → jiu/pangolin 共用一份</b>compile-macos 脚本可复用 jiu 的;Android keystore 虽同命名规范但<b>各 app 独立、不共享</b>。工作流用 <code>secrets.XXX</code> 引用,作用域对写法透明。</p>
<h2>7. 实现顺序</h2>
<p>范围虽 A~F,按风险/依赖递增落地,每阶段独立可发、独立验收:</p>
<ol>
<li><b>A 基座</b> + <code>checks</code> 迁移(抽 <code>scripts/ci</code> 骨架)</li>
<li><b>B 官网</b>(最简,验证 release/deploy 骨架)</li>
<li><b>C 服务端</b>(固化手动部署)</li>
<li><b>D Android</b>(解锁下载链接;需 keystore 就绪 + gradle 接签名)</li>
<li><b>E macOS</b>(最复杂:证书 + 2 描述文件 + 公证)</li>
<li><b>F Windows</b>windows runner + Inno Setup</li>
</ol>
<h2>8. 验证</h2>
<ul>
<li>每条流水线先 <code>workflow_dispatch</code> 手动跑通、核对产物/部署,再依赖 tag。</li>
<li>服务端:<code>server-v*</code> → migrate 版本 + <code>/healthz</code> + 行数守恒(同 F3 核对)。</li>
<li>官网:<code>site-v*</code> → 站点可访问 + canonical 正确 + redline 扫描。</li>
<li>客户端:release 资产可下载安装(Android 侧载 / macOS <code>spctl</code> / Windows 安装)。</li>
<li>下载链接:官网按钮落到最新 release 资产。</li>
</ul>
<h2>9. 风险与缓解</h2>
<table>
<tr><th>风险</th><th>缓解</th></tr>
<tr><td>nas 内存(3.8G)构建 OOM</td><td>容器化单 job、Astro/Go 轻量;必要时该端移 mac</td></tr>
<tr><td>migrate 在生产出错</td><td>部署前备份 + 失败自动回滚(§3.3),已在 F3/F4 手动验证</td></tr>
<tr><td>Android keystore 丢失</td><td>存 Bitwarden(文件+密码);终身签名身份</td></tr>
<tr><td>macOS 公证凭据泄露</td><td>账户级 secret,不落盘;<code>.p8</code>/<code>.p12</code> 用完即删临时文件</td></tr>
<tr><td>发版后下载链接不刷新</td><td>build-* 成功触发 deploy-site 重烘焙,或用 latest-download 稳定 URL</td></tr>
</table>
<h2>10. 不在本方案</h2>
<p class="small">iOS 流水线(G)、SQLite 备份/容灾(#26)、TLS#25)、上架 Play、Windows 代码签名(先不签)。</p>
</div>
</body>
</html>