Merge pull request 'ci: web 并入 deploy-client(一个 client-v* 发全平台) + iOS 加密键 + runner 局域网直达文档' (#13) from worktree-fix-sysext-network-ent into main
Deploy Client / build-macos (push) Failing after 2s
Deploy Client / build-ios (push) Failing after 0s
Deploy Client / build-android (push) Has been cancelled
Deploy Client / build-windows (push) Has been cancelled
Deploy Client / deploy-web (push) Has been cancelled
Deploy Client / release-deploy (push) Has been cancelled

Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
2026-09-07 22:43:26 +00:00
4 changed files with 328 additions and 15 deletions
+33 -15
View File
@@ -6,20 +6,14 @@ name: Deploy Client
# intentionally DECOUPLED — see the "why build-macos/build-ios don't block"
# note above the build-macos job below for the mechanism and rationale.
#
# TODO(controller) — RUNNER AVAILABILITY: per docs/ci-runner.md, pangolin
# currently has exactly ONE registered Gitea Actions runner
# ("mac-pangolin-2", label `nas:host`). Neither `runs-on: mac` nor
# `runs-on: windows` below has any runner registered to pick it up yet — this
# workflow will queue forever until that's fixed. Options: (a) register
# mac-pangolin-2 with an additional `mac` label (it's already a mac host —
# cheapest fix for build-android/release-deploy) and separately stand up +
# register an actual Windows host runner labeled `windows` for build-windows
# (no such machine exists per docs/ci-runner.md), or (b) repoint both at
# `nas` and accept that Android/Windows builds then compete with the
# docker-in-domain nas jobs on the same single mac host. This mirrors the
# `runs-on: mac` / `runs-on: windows` split already planned in
# docs/superpowers/plans/2026-07-05-cicd.md Task 7/10 — written that way here
# for fidelity to that plan, NOT because the runners are confirmed to exist.
# RUNNER 就绪(2026-09-07 起,过时 TODO 已删):用户级 mac runner(label `mac`)+
# windows runner(label `windows`)均已注册(单一真相源见 baize ci_runners)。
# runs-on: mac / windows 正常派工,不再无限排队。
#
# WEB 已并入本工作流(deploy-web job,构建 usercenter+website → Cloudflare Pages)——
# 一个 `client-v*` tag 即发 **web + iOS/mac/Android/Windows**(对齐 jiu 的
# deploy-client)。仅发 web 仍可用独立的 site-v* / deploy-site.yml(逻辑同源,共用
# scripts/ci/{compile-site,compile-usercenter,combine-site,deploy-site}.sh)。
on:
push:
tags:
@@ -177,6 +171,30 @@ jobs:
# No artifact upload — compile-ios.sh uploads straight to TestFlight via
# altool (matches jiu); nothing is produced under dist/ for this job.
# WEB(用户中心 usercenter + 官网 website → Cloudflare Pages):并入本工作流,使
# 一个 client-v* tag 同时发 web + 四客户端平台(对齐 jiu 的 build-client-web)。
# 与 deploy-site.yml 同源(共用 scripts/ci/{compile-site,compile-usercenter,
# combine-site,deploy-site}.sh);web 直接自部署到 CF Pages,不产 dist/ 产物、
# 不进 release-deploy 的 artifact 下载。ubuntu-latest = 家里 nas act_runner。
deploy-web:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Compile (Astro 官网)
env:
SITE_URL: https://pangolin.yanmeiai.com
run: bash scripts/ci/compile-site.sh
- name: Compile (用户中心 Next.js)
run: bash scripts/ci/compile-usercenter.sh
- name: Combine (官网 + 用户中心)
run: bash scripts/ci/combine-site.sh
- name: Deploy → Cloudflare Pages
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bash scripts/ci/deploy-site.sh
# release-deploy 发布策略:needs 全部四平台(等它们**完成**,不抢跑),但用
# `if` 让 mac/ios/windows 的失败不 skip 本 job —— 只要 android(稳定 floor)成功即发。
# 背景:build-macos/ios 必须跑在 mac runner,其 Checkout(git fetch git.51yanmei.com)
@@ -187,7 +205,7 @@ jobs:
# · continue-on-error 在 gitea 里**不**让下游 needs 在失败时继续(会 skip),故必须
# 配 `if: always() && needs.build-android.result=='success'` 才能「等全部、失败不阻」。
release-deploy:
needs: [build-android, build-windows, build-macos, build-ios]
needs: [build-android, build-windows, build-macos, build-ios, deploy-web]
if: ${{ always() && needs.build-android.result == 'success' }}
# ubuntu-latest = 家里 nas act_runner,与 gitea 同机/同网:Release → Forgejo 的
# API 调用走本地不过 frps 隧道(mac runner 走 git.51yanmei.com→frps→抖,curl 超时
+5
View File
@@ -51,5 +51,10 @@
⚠️ 红线词规范:避免 VPN/翻墙 等词,使用「网络加速」口径 -->
<key>NSVPNUsageDescription</key>
<string>Pangolin 使用网络加速通道为您的连接提供安全保护。</string>
<!-- 出口加密合规:仅使用标准加密(TLS/REALITY 基于标准 crypto 原语,无自研加密)
→ 声明不含「非豁免加密」,免去 App Store Connect 每次上传的加密合规问答。
⚠️ 若后续法务判定需正式申报,改 true 并补 ITSEncryptionExportComplianceCode。 -->
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict>
</plist>
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CI Runner 走域名注册 + 局域网/外网无感切换 实现方案</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:29px;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:52px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
h3{font-size:16px;margin:28px 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}
pre .c{color:#6b7385}
pre .r{color:var(--bad)}
pre .g{color:var(--ok)}
pre .y{color:var(--warn)}
.tag{display:inline-block;font-size:12px;font-weight:600;padding:2px 9px;border-radius:999px;vertical-align:middle}
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
.tag.ok{background:rgba(94,194,122,.16);color:var(--ok)}
.tag.warn{background:rgba(224,184,79,.16);color:var(--warn)}
.tag.you{background:rgba(95,176,201,.16);color:var(--accent2)}
.tag.me{background:rgba(224,136,79,.16);color:var(--accent)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:16px 0}
.card.root{border-left:3px solid var(--accent)}
.card.warn{border-left:3px solid var(--warn)}
.card.ok{border-left:3px solid var(--ok)}
.card.bad{border-left:3px solid var(--bad)}
.card h3{margin-top:0}
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}
.ok-c{color:var(--ok)} .bad-c{color:var(--bad)} .warn-c{color:var(--warn)}
ul,ol{padding-left:22px;margin:10px 0}
li{margin:6px 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}
.kbd{font-family:var(--mono);font-size:.85em;color:var(--accent)}
.small{color:var(--fg2);font-size:13px}
hr{border:none;border-top:1px solid var(--border);margin:40px 0}
a{color:var(--accent2)}
.diagram{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:20px;margin:18px 0;overflow-x:auto}
ol.steps{counter-reset:s;list-style:none;padding-left:0}
ol.steps>li{counter-increment:s;position:relative;padding:2px 0 2px 40px;margin:14px 0}
ol.steps>li::before{content:counter(s);position:absolute;left:0;top:0;width:26px;height:26px;border-radius:50%;background:var(--panel2);border:1px solid var(--border);color:var(--accent);font-family:var(--mono);font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center}
</style>
</head>
<body>
<div class="wrap">
<h1>CI Runner 走域名注册 + 局域网/外网无感切换</h1>
<p class="sub">gitea Actions self-hosted runner · 注册地址统一 <code>https://git.51yanmei.com</code> · 复用 pangolin 私有分流(private-split)达成家/外无感 · 2026-09-07</p>
<div class="lead">
<strong>一句话:</strong>daily 代理已从 Shadowrocket 换成 <strong>pangolin(全局 TUN)</strong>——TUN 抓所有二进制、<strong>不再像 Shadowrocket 的按 App SOCKS 那样掐断 Go 二进制</strong>,所以 <code>act_runner</code> 现在能<strong>直接连域名</strong><span class="tag bad">relay 淘汰</span>。runner 一律注册到域名 <code>https://git.51yanmei.com</code><code>git.51yanmei.com</code> 早已在 pangolin 的 <code>PANGOLIN_PRIVATE_SPLIT_DOMAINS</code> 里,靠 <code>dns-system</code>(底层物理网络 DNS)解析 —— <strong>在家</strong>局域网 DNS 覆盖成 <code>192.168.3.200</code> → 命中「LAN 直连」规则直达 NAS;<strong>在外</strong>公网 DNS 返回 ali 锚点 <code>182.92.213.171</code> → 命中「私有域名走隧道」→ 经 pangolin1 出口 → ali frp → NAS。同一个域名、同一份配置,家/外自动切换。
</div>
<div class="card ok">
<h3>✅ 已就绪(本次实测确认,无需改动)</h3>
<ul>
<li><b>pangolin 是当前活跃代理</b><code>com.pangolin.pangolin.PacketTunnel</code> 系统扩展 + <code>pangolin_vpn.app</code> 在跑;无 Shadowrocket。</li>
<li><b>域名已可直连</b>:从 runner mac 经 pangolin TUN<code>GET https://git.51yanmei.com/</code><code>/api/v1/version</code><span class="ok-c">HTTP 200</span>(解析 <code>182.92.213.171</code>~1.8s,走隧道→pangolin1→ali→frp→NAS)。<strong>relay 已不再必要。</strong></li>
<li><b>pangolin 配置已含 git</b>pangolin1 <code>/etc/pangolin/server.env</code> 第 31 行 <code>PANGOLIN_PRIVATE_SPLIT_DOMAINS=nas,git,win,brain.51yanmei.com</code><span class="tag ok">零改动</span></li>
<li><b>ali 侧外网入口已就绪</b><code>/etc/nginx/conf.d/git.conf</code> = <code>git.51yanmei.com</code> 443(LE 证书) → <code>proxy_pass 127.0.0.1:3000</code>(frp→NAS)<strong>无 IP 白名单</strong>(不像 brain 限 <code>103.119.13.48</code>),任意来源可达。</li>
</ul>
</div>
<div class="card bad">
<h3>❌ 差在「局域网直达」这条腿(本方案要补的)</h3>
<ul>
<li><b>无家内分光 DNS</b>:全网(含在家)解析 <code>git.51yanmei.com</code> 都得 <code>182.92.213.171</code>;群晖 <b>DNS Server 套件未安装</b><code>/var/packages/DNSServer</code> 不存在)。baize <code>domains.yaml</code> 里 nas/win/git 的「家内覆盖 192.168.3.200 待做」正是此项。<b>可行性已判定→支持</b>(见 §6)。</li>
<li><b>NAS 443 尚无 git 的 TLS 反代条目</b>NAS 监听 <code>0.0.0.0:443</code>(DSM nginx),需加 <code>git.51yanmei.com</code><code>gitea:3000</code> 的反代 + 证书。局域网直达 <code>https://git.51yanmei.com</code>=192.168.3.200:443)必须它成立。<b>可行性已判定→支持,证书需 DNS-01/同步</b>(见 §6)。</li>
<li><b>runner 侧仍是旧的坏配置</b>mac-pangolin-2(gitea_id=7)标签错(<code>nas</code>)、注册到直连公网域名(Shadowrocket 时代坏)jiu 的 mac-runner(id=3)仓库级、经 relay。均待收口成<strong>一个用户级 mac runner 走域名</strong></li>
</ul>
</div>
<h2>0 · 目标架构(一张图)</h2>
<div class="diagram">
<svg viewBox="0 0 900 430" width="100%" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,PingFang SC,sans-serif">
<defs>
<marker id="ar" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#5fb0c9"/></marker>
<marker id="arg" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#5ec27a"/></marker>
<marker id="ary" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#e0b84f"/></marker>
</defs>
<!-- runner mac -->
<rect x="20" y="170" width="150" height="90" rx="10" fill="#1d2129" stroke="#e0884f"/>
<text x="95" y="198" fill="#e6e8ee" font-size="13" font-weight="700" text-anchor="middle">runner mac</text>
<text x="95" y="218" fill="#a8afbd" font-size="11" text-anchor="middle">act_runner</text>
<text x="95" y="234" fill="#a8afbd" font-size="11" text-anchor="middle">pangolin TUN</text>
<text x="95" y="250" fill="#5fb0c9" font-size="10" text-anchor="middle">→ git.51yanmei.com</text>
<!-- DNS decision -->
<rect x="215" y="180" width="140" height="70" rx="10" fill="#171a22" stroke="#5fb0c9"/>
<text x="285" y="205" fill="#e6e8ee" font-size="12" font-weight="700" text-anchor="middle">私有分流判定</text>
<text x="285" y="222" fill="#a8afbd" font-size="10" text-anchor="middle">DNS 劫持 → dns-system</text>
<text x="285" y="237" fill="#a8afbd" font-size="10" text-anchor="middle">(底层物理网络 DNS)</text>
<line x1="170" y1="215" x2="212" y2="215" stroke="#5fb0c9" stroke-width="1.6" marker-end="url(#ar)"/>
<!-- LAN lane -->
<text x="430" y="60" fill="#5ec27a" font-size="12" font-weight="700">在家 · 局域网</text>
<rect x="410" y="75" width="170" height="66" rx="9" fill="#1d2129" stroke="#5ec27a"/>
<text x="495" y="99" fill="#e6e8ee" font-size="11.5" font-weight="700" text-anchor="middle">群晖 DNS Server</text>
<text x="495" y="116" fill="#5ec27a" font-size="10.5" text-anchor="middle">git → 192.168.3.200</text>
<text x="495" y="131" fill="#a8afbd" font-size="10" text-anchor="middle">命中「LAN 直连」规则</text>
<rect x="640" y="75" width="230" height="66" rx="9" fill="#171a22" stroke="#5ec27a"/>
<text x="755" y="99" fill="#e6e8ee" font-size="11.5" font-weight="700" text-anchor="middle">NAS 192.168.3.200</text>
<text x="755" y="116" fill="#a8afbd" font-size="10.5" text-anchor="middle">:443 反代(LE证书) → gitea:3000</text>
<text x="755" y="131" fill="#5ec27a" font-size="10" text-anchor="middle">直达,无隧道 · ~ms</text>
<path d="M300,180 C300,108 405,108 408,108" fill="none" stroke="#5ec27a" stroke-width="1.6" marker-end="url(#arg)"/>
<line x1="580" y1="108" x2="638" y2="108" stroke="#5ec27a" stroke-width="1.6" marker-end="url(#arg)"/>
<!-- WAN lane -->
<text x="430" y="300" fill="#e0b84f" font-size="12" font-weight="700">在外 · 公网</text>
<rect x="410" y="312" width="150" height="62" rx="9" fill="#1d2129" stroke="#e0b84f"/>
<text x="485" y="335" fill="#e6e8ee" font-size="11" font-weight="700" text-anchor="middle">公网 DNS</text>
<text x="485" y="351" fill="#e0b84f" font-size="10.5" text-anchor="middle">git → 182.92.213.171</text>
<text x="485" y="366" fill="#a8afbd" font-size="9.5" text-anchor="middle">命中「私有域名走隧道」</text>
<rect x="600" y="306" width="120" height="74" rx="9" fill="#171a22" stroke="#e0884f"/>
<text x="660" y="330" fill="#e6e8ee" font-size="11" font-weight="700" text-anchor="middle">pangolin1</text>
<text x="660" y="346" fill="#a8afbd" font-size="9.5" text-anchor="middle">隧道出口</text>
<text x="660" y="361" fill="#a8afbd" font-size="9.5" text-anchor="middle">103.119.13.48</text>
<rect x="760" y="306" width="120" height="74" rx="9" fill="#171a22" stroke="#e0b84f"/>
<text x="820" y="330" fill="#e6e8ee" font-size="11" font-weight="700" text-anchor="middle">ali nginx</text>
<text x="820" y="346" fill="#a8afbd" font-size="9.5" text-anchor="middle">:443 → frp</text>
<text x="820" y="361" fill="#5ec27a" font-size="9.5" text-anchor="middle">→ NAS:3000</text>
<path d="M300,250 C300,343 405,343 408,343" fill="none" stroke="#e0b84f" stroke-width="1.6" marker-end="url(#ary)"/>
<line x1="560" y1="343" x2="598" y2="343" stroke="#e0b84f" stroke-width="1.6" marker-end="url(#ary)"/>
<line x1="720" y1="343" x2="758" y2="343" stroke="#e0b84f" stroke-width="1.6" marker-end="url(#ary)"/>
</svg>
</div>
<p class="small">两条腿只有 <b>DNS 解析结果不同</b>驱动分叉:家内被 <code>dns-system</code> 解成私网 IP → 先命中 route 里更靠前的「LAN 直连」;在外解成公网锚点 → 落到「私有域名 → auto(隧道)」。<b>runner 侧完全不感知</b>,永远只认 <code>https://git.51yanmei.com</code></p>
<h2>1 · 为什么无感切换成立(机制,已在代码里且有测试)</h2>
<p>核心在 <code>server/internal/httpapi/clientconfig.go</code><code>PANGOLIN_PRIVATE_SPLIT_DOMAINS</code> 非空时注入三处,<b>顺序即优先级</b></p>
<table>
<tr><th>#</th><th>位置</th><th>作用</th></tr>
<tr><td></td><td>DNS server <code>dns-system</code>type=localline 370-373</td><td>私有域名不走公共 DNS(223.5.5.5/8.8.8.8),改用<strong>底层物理网络 DNS</strong>——在家=路由器/NAS 下发的局域网 DNS(含覆盖),在外=所在网络 DNS(公网记录)。</td></tr>
<tr><td></td><td>DNS rule <code>{domain: 私有域名, server: dns-system}</code>line 383-385</td><td>把私有域名的解析定向到 ①。排在 <code>geosite-cn</code> 之前,优先级最高。</td></tr>
<tr><td></td><td>route rule <code>{domain: 私有域名, outbound: auto}</code>line 239-242+ 前置 LAN 直连 <code>{ip_cidr:[10/8,172.16/12,192.168/16,127/8], outbound:direct}</code>line 230-233</td><td>在家:DNS 解出私网 IP → <strong>LAN 直连规则先命中</strong>(更靠前)→ 直达 NAS<b>不进隧道</b>。在外:解出公网 IP → 落到私有域名规则 → <code>auto</code>(隧道) → pangolin1 出口。<code>dns.reverse_mapping=true</code> 补回域名元数据,使按 IP 发起的连接仍能命中该规则。</td></tr>
</table>
<p class="small">这正是 <a href="private-dest-acl-design.html">私有服务 ACL 设计</a> / <a href="private-dest-acl-plan.html">实现计划</a> 已落地的家庭内网穿透机制(nas/win/brain 同款)。git 只是复用它,<b>无新代码</b></p>
<h2>2 · 分项实现(workstreams</h2>
<table>
<tr><th>#</th><th>工作项</th><th>动谁</th><th>状态</th></tr>
<tr><td>W1</td><td>pangolin 把 git 纳入 private-split</td><td>pangolin1 <code>server.env</code></td><td><span class="tag ok">已完成</span></td></tr>
<tr><td>W2</td><td>ali 外网入口(git nginx 443→frp→NAS,开放)</td><td>ali</td><td><span class="tag ok">已就绪</span></td></tr>
<tr><td>W3</td><td>家内<b>分光 DNS</b>git/nas/win → 192.168.3.200 / .88</td><td>NAS(群晖 DNS Server+ <s>DHCP 下发</s>→仅 runner mac 手动 DNS</td><td><span class="tag ok">已完成 2026-09-07(改法,见 §8</span></td></tr>
<tr><td>W4</td><td>NAS <b>443 TLS 反代</b> git → gitea:3000+证书)</td><td>NASDSM 反向代理 + 导入 ali LE 证书)</td><td><span class="tag ok">已完成 2026-09-07</span></td></tr>
<tr><td>W5</td><td>runner 收口:1 个用户级 mac runner 走<b>域名</b>;退役 id=3;删 relay</td><td>dev maclaunchd+ gitea</td><td><span class="tag ok">已完成 2026-09-07</span></td></tr>
<tr><td>W6</td><td>验证 + 回写 baize 台账</td><td></td><td><span class="tag warn">待做</span></td></tr>
</table>
<div class="card">
<p><b>降级路径(若暂不做 W3/W4):</b>runner <b>现在</b>就能改注册到域名并删 relay(W5 单独可行)——在家会走「隧道→pangolin1→ali→NAS」的<b>回环</b>路径(实测 ~1.8s/请求,能用但慢)。W3+W4 是把在家这条腿从「回环」优化成「局域网直达(~ms)」,即你要的「局域网域名直接解析成局域网地址」。<b>建议:W5 先落地拿掉 relay,W3/W4 作为随后的直连优化。</b></p>
</div>
<h3>W3 · 家内分光 DNS <span class="tag you">你确认/我执行</span></h3>
<p>目标:局域网内解析 <code>git/nas/win.51yanmei.com</code> → 私网 IP;其余照常公网。方案(二选一):</p>
<ul>
<li><b>群晖 DNS Server(推荐,baize 既定方向)</b>DSM 套件中心装 <b>DNS Server</b> → 建 Master Zone <code>51yanmei.com</code>(或用「解析区域 + A 记录覆盖」)→ 加 A:<code>git 192.168.3.200</code><code>nas 192.168.3.200</code><code>win 192.168.3.88</code>;开启 forwarders 把其余查询转公网(223.5.5.5)。<b>DHCP 下发 DNS 指向 NAS</b>——好消息:<b>NAS 本身就是 LAN 的 DHCP 服务器</b><code>DhcpServer</code> 套件已装、引擎 dnsmasq、段 .10.230),在 DSM DHCP 设置里把下发 DNS 改成 <code>192.168.3.200</code> 即可,<b>无需碰路由器</b></li>
<li><b>ContainerManager 容器(备选,已装)</b>:跑 AdGuard Home / dnsmasq 容器做 <code>git→192.168.3.200</code> rewriteDHCP 下发指向它。免装 DSM DNS Server 套件。</li>
</ul>
<p class="small"><b>注意</b>:装 DNS Server / 改 DHCP 下发 = 机器与网络改动,按规矩<strong>先经你确认</strong>再动。<code>dns-system</code>(type=local)吃的是 OS resolver → DHCP 下发的 DNS,所以 DHCP 那一步不可省(NAS 既是 DHCP 服务器,这步在 DSM 内完成)。</p>
<h3>W4 · NAS 443 为 git 供 TLS 反代 <span class="tag you">你确认/我执行</span></h3>
<p>局域网直达 <code>https://git.51yanmei.com</code> 即打到 <code>192.168.3.200:443</code>,须 NAS 在 443 用 <b>git.51yanmei.com 有效证书</b>反代到 <code>gitea:3000</code>。步骤:</p>
<ol class="steps">
<li><b>证实现状</b>(需 sudo,你在 NAS 上跑或授权我):看 DSM「控制面板 → 登录门户 → 高级 → 反向代理」是否已有 <code>git.51yanmei.com</code> 条目;<code>sudo ss -tlnp | grep :443</code> 看谁占 443。</li>
<li><b></b>DSM 反向代理新建 <code>git.51yanmei.com:443</code><code>localhost:3000</code>,勾 HTTP/2、WebSocket<code>client_max_body_size 0</code> 等价项在 DSM 里放开大包(git push)。</li>
<li><b>证书</b>DSM「安全性 → 证书」为 <code>git.51yanmei.com</code> 配 LE 证书。家内无 80 端口暴露→用 <b>DNS-01</b>(阿里云 DNSAK 在 Bitwarden「ali dev」),或从 ali <code>/etc/letsencrypt/live/git.51yanmei.com/</code> 定期同步导入。绑到反代条目。</li>
</ol>
<p class="small">gitea 的 <code>ROOT_URL</code> 已是 <code>https://git.51yanmei.com</code>(外网就这么跑),Host 头一致,反代到 3000 无需改 gitea。</p>
<h3>W5 · runner 收口到域名 + 删 relay <span class="tag me">我执行(经你确认)</span></h3>
<p>act_runner 的标签在<b>首次注册即固定</b>,改配置不重注册无效——所以必须重注册。目标态:<b>一个用户级 mac runner</b></p>
<ol class="steps">
<li><b>取用户级注册 token</b>gitea 网页 头像 → Settings → Actions → Runners → <b>Create new Runner</b>(用户级 reg-token 的 API 端点本版本 404,只能网页取)。<span class="tag you">你提供</span></li>
<li><b>注册</b>新 runner<code>instance = https://git.51yanmei.com</code><b>域名,不是 127.0.0.1:13000,不是 IP</b>),labels <code>mac,self-hosted,macos</code>name <code>mac</code></li>
<li><b>launchd 收敛</b>:新建 <code>com.yanmei.act-runner</code>(用户级常驻,配置目录 <code>~/.act_runner_mac_config.yaml</code>);<b>卸载</b> <code>com.pangolin.act-runner</code>(mac-pangolin-2) 与 <code>com.jiu.act-runner</code></li>
<li><b>删 relay</b><code>com.jiu.act-runner</code> 的 start 脚本里附带拉起 <code>~/bin/tcp_relay.py</code>——runner 走域名后 relay 无用,随该 launchd 一并退休(脚本、plist、13000 监听全清)。</li>
<li><b>gitea 退役</b>旧 runner:删 mac-pangolin-2(id=7) 与 mac-runner(id=3)。jiu 仓的 job 由新用户级 mac runner 接管(标签 <code>mac</code>;如 jiu workflow 用 <code>runs-on: mac</code> 直接兼容,否则补标签)。</li>
</ol>
<div class="card warn">
<h3>⚠️ relay 到底还要不要(结论)</h3>
<p>relay(<code>tcp_relay.py</code> 127.0.0.1:13000 → NAS 192.168.3.200:3000)当初只为绕开 <b>Shadowrocket 拦 act_runner 这个 Go 二进制的直连</b>Python 进程不被 Shadowrocket 拦)。现在 daily 代理是 pangolin 全局 TUN<b>TUN 不做按-App 拦截、对 Go 二进制透明</b>,实测域名直连 200 通。<strong>relay 已淘汰,随 W5 删除。</strong></p>
</div>
<div class="card ok">
<h3>✅ W5 执行记录(2026-09-07</h3>
<p>比预想更简单——<b>无需重注册、无需注册 token</b>mac-pangolin-2(id=7)<b>本就是用户级、start 脚本本就指向域名</b>,唯一 bug 是 label 错成 <code>nas</code>。根因是当年 Shadowrocket 掐断连接 → act_runner 的 <code>Declare(labels)</code> 从没上报成功。换 pangolin 后连通,做法:</p>
<ol>
<li><code>~/.act_runner_pangolin2_config.yaml</code><code>labels: nas:host → mac:host</code></li>
<li><code>launchctl kickstart -k gui/501/com.pangolin.act-runner</code> 重启 → 日志 <code>declare successfully, labels:[mac]</code>,并<b>立即接走 pangolin 队列 task 2299</b></li>
<li>退役 jiu<code>launchctl bootout</code> <code>com.jiu.act-runner</code>+watchdog(连带杀 relay)→ plist 改名 <code>.disabled</code> → repo API 删 id=3<code>DELETE …/repos/wangjia/jiu/actions/runners/3</code> = 204)。</li>
<li>删 relay<code>~/bin/tcp_relay.py</code> / jiu start 脚本 / jiu config 均 <code>.retired</code><code>:13000</code> 已释放。</li>
</ol>
<p class="small">现状:单一用户级 mac runnerid=7<code>com.pangolin.act-runner</code>label <code>mac</code>,域名直连、无 relay),服务全部 wangjia 仓。回写 baize <code>ci_runners</code>(commit 4b7ffe1)。<b>遗留(非阻塞)</b>label 仍单 <code>mac</code>(可后加 self-hosted/macos);launchd 名仍带 pangolin(拟后续中性化 <code>com.yanmei.act-runner</code>)。在家目前走隧道回环(~1.8s),W3/W4 后转局域网直达。</p>
</div>
<h2>3 · 迁移顺序(依赖)</h2>
<ol class="steps">
<li><b>W5 先行</b>(可独立):runner 改注册到域名 + 删 relay。此刻在家走回环(能跑绿),先把「多头 + relay + 坏标签」这堆乱账清掉。</li>
<li><b>W4</b>:NAS 443 反代 + 证书(先证实、再补建)。</li>
<li><b>W3</b>:家内分光 DNS + DHCP 指向 NAS。W3 生效后,家内解析即翻私网 IP,runner/所有设备访问 git/nas/win 自动走局域网直达。</li>
<li><b>W6</b>:验证(见下)+ 回写 baize(DNS 覆盖落地、relay 退役、runner 台账更新)。</li>
</ol>
<p class="small">W3 与 W4 是一对:只做 W3 不做 W4,家内会打到 192.168.3.200:443 但无有效 TLS → 失败;两者同批上。</p>
<h2>4 · 验证清单(W6</h2>
<pre><span class="c"># 在家(局域网)——应直达私网、无隧道回环</span>
dig +short git.51yanmei.com <span class="c"># 期望 192.168.3.200</span>
curl -sS -o /dev/null -w "%{remote_ip} %{time_total}\n" https://git.51yanmei.com/api/v1/version
<span class="c"># 期望 ip=192.168.3.200time≈ms 级</span>
<span class="c"># 在外(手机热点/换网)——应回落公网锚点、经隧道</span>
dig +short git.51yanmei.com <span class="c"># 期望 182.92.213.171</span>
curl ... /api/v1/version <span class="c"># 200ip=182.92.213.171</span>
<span class="c"># runner 状态</span>
launchctl list | grep act-runner <span class="c"># 只剩 com.yanmei.act-runner</span>
lsof -i :13000 <span class="c"># 空(relay 已死)</span>
</pre>
<p>gitea 网页 Runners:只余 <code>windows</code>(id=8) / <code>mac</code>(新,用户级) / <code>nas</code>(id=2,实例级不动)。触发一次 pangolin <code>build-macos</code> 确认被 <code>mac</code> runner 接走、跑绿。</p>
<h2>6 · NAS 可行性判定(read-only 实测 2026-09-07</h2>
<p>「涉及 NAS 先判可行性,别做一半发现一般 nas 不支持」——判定结论:<b>这台 DS925+ 不属于「不支持」的那类</b>。它是 Plus x86 机(<code>x86_64</code> / DSM <code>7.3.2</code>),关键能力齐备。</p>
<table>
<tr><th>能力</th><th>现状</th><th>判定</th></tr>
<tr><td>NAS 是否 LAN DHCP 服务器</td><td><code>DhcpServer</code> 套件<b>已装</b>,引擎正是 <b>dnsmasq</b><code>dnsmasq-2.x-virtual-dhcpserver</code>),租约段 <code>192.168.3.10230</code></td><td class="ok-c">✅ DNS 下发掌握在自己手里,无需改路由器</td></tr>
<tr><td>DNS Server(分光 A 记录)</td><td>套件未装,但 DS925+/DSM7.3 catalog 支持;另 <code>ContainerManager</code> <b>已装</b>可跑 AdGuard/dnsmasq 容器</td><td class="ok-c">✅ 两条路径(官方套件 / 容器)任选</td></tr>
<tr><td>443 TLS 反代</td><td>443 由 DSM nginx 持有;<b>DSM Reverse Proxy 已在用</b><code>/usr/syno/etc/www/ReverseProxy.json</code> 有条目);gitea 为 3000 上 native 进程(非容器)</td><td class="ok-c">✅ 加一条 SNI vhost git→localhost:3000 即可</td></tr>
<tr><td>证书签发</td><td>DSM 内置 LE 仅 HTTP-01,家里 git 的 :80 不对公网→<b>一键签不了</b>acme.sh 未装</td><td class="warn-c">⚠ 唯一非默认项:走 <b>acme.sh DNS-01</b>(阿里云 DNSAK 在 Bitwarden「ali dev」) 或<b>同步 ali 已有 LE 证书</b>(cron scp+DSM 导入,最省)</td></tr>
</table>
<div class="card ok">
<p><b>判定:W3 + W4 均可行。</b>唯一 caveat 是证书需 DNS-01 或同步(非 DSM 一键 LE),但两条路都成熟、不阻塞。DHCP 已由 NAS(dnsmasq) 承担、ContainerManager 与 Reverse Proxy 都在——不存在「一般 nas 不支持」的情形。<br><span class="small">未证实项(需 sudo 才能读,不阻塞判定):ReverseProxy.json 具体条目、certificate 目录内容、DHCP 当前下发的 DNS 指向谁。落地时一并处理。</span></p>
</div>
<h2>7 · 需要你拍板 / 提供的(机器改动,先确认再动)</h2>
<table>
<tr><th></th><th>属于</th><th>需要你</th></tr>
<tr><td>用户级 runner 注册 token(网页 Create new Runner 取)</td><td>W5</td><td>提供 token</td></tr>
<tr><td>装群晖 DNS Server + 改路由器 DHCP DNS</td><td>W3</td><td>确认动手(装套件+改网络)</td></tr>
<tr><td>NAS DSM 反向代理 + 证书(DNS-01 或同步 ali 证书)</td><td>W4</td><td>确认动手 / 或授权 sudo 让我先证实现状</td></tr>
<tr><td>卸载 <code>com.jiu.act-runner</code> / <code>com.pangolin.act-runner</code>、删 relay</td><td>W5</td><td>确认(会短暂影响 jiu CI,直到新 runner 接管)</td></tr>
</table>
<p class="small">相关台账:<code>~/code/baize/data/hosts.yaml</code> <code>ci_runners</code> 段(relay_note、mac runner state+ <code>domains.yaml</code>nas/win/git「家内覆盖 待做」)。W3/W5 落地后回写。</p>
<h2>8 · W3/W4 执行记录(2026-09-07,实际落地与偏差)</h2>
<div class="card ok">
<h3>✅ 结果:git 在家从隧道回环 ~1.8s → <b>局域网直达 24ms</b></h3>
<p><code>curl https://git.51yanmei.com/api/v1/version</code> 从 runner mac 经 pangolin<code>http=200 ip=192.168.3.200 time=0.025s</code>,证书受信。runner 重启后 <code>declare successfully</code>、无 <code>no such host</code></p>
</div>
<h3>W4NAS 443 反代)— 按计划</h3>
<ul>
<li>DSM 反向代理原有 <code>git.51yanmei.com</code> 条目从 <b>HTTP:80 改 HTTPS:443</b>,目的地仍 <code>localhost:3000</code></li>
<li>证书:把 ali 上 <code>/etc/letsencrypt/live/git.51yanmei.com/</code> 的 LE 证书(cert/privkey/chain)导入 DSM,绑到该反代服务。(我这边拉私钥被安全闸拦,改由用户在自己终端拉取 + DSM GUI 导入。)</li>
</ul>
<h3>W3(家内分光 DNS)— <span class="tag warn">改了方案</span></h3>
<p>原计划「NAS 接管 DHCP、下发 .200 当 DNS」<b>失败</b>——根因:家里网关是 <b>华为 Q2S 路由器,带防私接 DHCP</b>,把 NAS(dnsmasq,绑 :67 正常)发出的 DHCP 应答全丢,客户端永远拿不到 NAS 租约(华为 DHCP 关掉后 mac 也 DISCOVER 不到 NAS→一度 No-IP,靠重开华为 DHCP 兜底恢复)。NAS 不是路由器、改不了这个。</p>
<p>改成的可行方案:</p>
<ol class="steps">
<li>NAS 装 <b>Synology DNS Server</b><code>synopkg install_from_server DNSServer</code>):给 <code>git/nas/win.51yanmei.com</code> 各建<b>单主机主区</b>(只对这三名字权威→私网 IP+ <code>named.options.user.conf</code><code>forwarders{223.5.5.5;119.29.29.29}</code> 转发其余。故 <code>pay/jiu/api</code> 等兄弟子域照常公网。</li>
<li><b>只给 runner mac 的家里 Wi-Fi 手动指 DNS=<code>192.168.3.200</code></b>(macOS 按网络记,出门用别的网 DNS、不受影响)。别的家里设备不动(它们不需要 git 覆盖)。NAS 的 DhcpServer 已停,华为继续当 DHCP。</li>
<li>⚠ 改 DNS 后 mDNSResponder 负缓存旧失败(<code>dig</code> 通但 <code>curl</code> no-such-host)→ <code>sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder</code>runner 再 <code>launchctl kickstart -k</code> 重启。</li>
</ol>
<p class="small"><b>已知/待办</b>:① 只有 runner mac 走直达(本就够用,它才是 CI 需要的),非全网分光。② DNS=.200 仅设在家里 Wi-Fi;若 mac 换网上不了网就删这条。③ NAS DhcpServer 已 <code>stop</code>,如需永久别开机自启可在 DSM「DHCP Server」取消勾选。④ 台账真相源:baize <code>domains.yaml</code>『家内分光』note;NAS 详细運維归 <code>~/code/nas</code></p>
<hr>
<p class="small">关联:<a href="private-dest-acl-design.html">私有服务 ACL 设计</a> · <a href="private-dest-acl-plan.html">私有服务 ACL 实现计划</a> · <a href="control-plane-tls-tunnel.html">控制面 TLSTunnel 前置)</a> · <a href="index.html">← 文档索引</a></p>
</div>
</body>
</html>
+5
View File
@@ -146,6 +146,11 @@
<div class="d">阅读版;执行真相源 <code>docs/superpowers/plans/2026-07-05-cicd.md</code>(含 checkbox)。三期 11 任务:Phase1 基座+官网+服务端(无签名可立即上线,服务端固化 F3/F4 备份/迁移/回滚) → Phase2 Android(接 release keystore 签名,解锁下载链接) → Phase3 macOS 公证 dmg + Windows 安装包。runner 混合 nas/mac/windows;密钥已建(对齐 jiu)。设计见 cicd-design.html。</div>
<div class="path">docs/cicd-plan.html · 真相源 docs/superpowers/plans/2026-07-05-cicd.md</div>
</a>
<a class="doc" href="ci-runner-domain-registration-plan.html">
<div class="t">CI Runner 走域名注册 + 局域网/外网无感切换 <span class="tag html">HTML</span></div>
<div class="d">daily 代理已从 Shadowrocket 换成 pangolin 全局 TUN(对 Go 二进制透明,不再掐 act_runner 直连)→ <b>relay 淘汰</b>runner 一律注册到域名 <code>https://git.51yanmei.com</code>。git 早在 <code>PANGOLIN_PRIVATE_SPLIT_DOMAINS</code>,靠 <code>dns-system</code> 解析:在家局域网 DNS 覆盖成 192.168.3.200 → LAN 直连规则先命中直达 NAS;在外公网锚点 182.92.213.171 → 私有域名走隧道 → pangolin1 出口 → ali frp → NAS。已就绪(pangolin配置/ali入口/域名实测200);待补 W3 家内分光 DNS(群晖 DNS Server+DHCP)+W4 NAS 443 TLS 反代+W5 runner 收口成一个用户级 mac runner 并删 relay。机制复用私有服务 ACL。</div>
<div class="path">docs/ci-runner-domain-registration-plan.html</div>
</a>
<a class="doc" href="device-session-management-plan.html">
<div class="t">设备 &amp; 会话管理 + 每设备流量归因 实现计划(P1–P6)<span class="tag html">HTML</span></div>
<div class="d">阅读版;执行真相源为 <code>docs/superpowers/plans/2026-06-29-device-session-management.md</code>(含 checkbox)。P1 设备注册打通 → P2 sessions表+在线/最后登录 → P3 强制退出/清除 → P4 每设备流量 → P5 2FA信任(future) → P6 UI重做。</div>