@@ -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 : 15 px }
. wrap { max-width : 960 px ; margin : 0 auto ; padding : 48 px 24 px 96 px }
h1 { font-size : 29 px ; line-height : 1.3 ; margin : 0 0 8 px ; letter-spacing : -.01 em }
. sub { color : var ( - - fg2 ) ; font-size : 15 px ; margin : 0 0 32 px }
h2 { font-size : 21 px ; margin : 52 px 0 14 px ; padding-bottom : 8 px ; border-bottom : 1 px solid var ( - - border ) }
h3 { font-size : 16 px ; margin : 28 px 0 8 px ; color : var ( - - accent2 ) }
p { margin : 10 px 0 }
code { font-family : var ( - - mono ) ; font-size : .88 em ; background : var ( - - panel2 ) ; padding : 1 px 6 px ; border-radius : 5 px ; color : #f0d9c4 }
pre { background : #0a0c11 ; border : 1 px solid var ( - - border ) ; border-radius : 10 px ; padding : 14 px 16 px ; overflow-x : auto ; font-family : var ( - - mono ) ; font-size : 13 px ; 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 : 12 px ; font-weight : 600 ; padding : 2 px 9 px ; border-radius : 999 px ; 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 : 1 px solid var ( - - border ) ; border-radius : 12 px ; padding : 18 px 20 px ; margin : 16 px 0 }
. card . root { border-left : 3 px solid var ( - - accent ) }
. card . warn { border-left : 3 px solid var ( - - warn ) }
. card . ok { border-left : 3 px solid var ( - - ok ) }
. card . bad { border-left : 3 px solid var ( - - bad ) }
. card h3 { margin-top : 0 }
table { width : 100 % ; border-collapse : collapse ; margin : 16 px 0 ; font-size : 14 px }
th , td { text-align : left ; padding : 9 px 12 px ; border-bottom : 1 px solid var ( - - border ) ; vertical-align : top }
th { color : var ( - - fg2 ) ; font-weight : 600 ; font-size : 13 px }
td code { font-size : .85 em }
. ok-c { color : var ( - - ok ) } . bad-c { color : var ( - - bad ) } . warn-c { color : var ( - - warn ) }
ul , ol { padding-left : 22 px ; margin : 10 px 0 }
li { margin : 6 px 0 }
. lead { background : linear-gradient ( 180 deg , rgba ( 224 , 136 , 79 , .10 ) , transparent ) ; border : 1 px solid var ( - - border ) ; border-radius : 12 px ; padding : 18 px 20 px ; margin : 0 0 8 px }
. kbd { font-family : var ( - - mono ) ; font-size : .85 em ; color : var ( - - accent ) }
. small { color : var ( - - fg2 ) ; font-size : 13 px }
hr { border : none ; border-top : 1 px solid var ( - - border ) ; margin : 40 px 0 }
a { color : var ( - - accent2 ) }
. diagram { background : var ( - - panel ) ; border : 1 px solid var ( - - border ) ; border-radius : 12 px ; padding : 20 px ; margin : 18 px 0 ; overflow-x : auto }
ol . steps { counter-reset : s ; list-style : none ; padding-left : 0 }
ol . steps > li { counter-increment : s ; position : relative ; padding : 2 px 0 2 px 40 px ; margin : 14 px 0 }
ol . steps > li :: before { content : counter ( s ) ; position : absolute ; left : 0 ; top : 0 ; width : 26 px ; height : 26 px ; border-radius : 50 % ; background : var ( - - panel2 ) ; border : 1 px solid var ( - - border ) ; color : var ( - - accent ) ; font-family : var ( - - mono ) ; font-size : 13 px ; 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=local, line 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 > NAS( DSM 反向代理 + 导入 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 mac( launchd) + 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 > rewrite, DHCP 下发指向它。免装 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 > (阿里云 DNS, AK 在 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 runner( id=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.200, time≈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" > # 200, ip=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.10– 230< / 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 > (阿里云 DNS, AK 在 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 > W4( NAS 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" > 控制面 TLS( Tunnel 前置)< / a > · < a href = "index.html" > ← 文档索引< / a > < / p >
< / div >
< / body >
< / html >