Files
nas/docs/remote-access-frp.html
T

425 lines
32 KiB
HTML
Raw 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>家庭内网远程访问方案 —— frp 反向隧道 × pangolin × 分流 DNS</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:24px 0 8px;color:var(--fg)}
p{margin:10px 0}
code{font-family:var(--mono);font-size:.86em;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:12.5px;line-height:1.55;color:#cdd3df}
pre code{background:none;padding:0;color:inherit}
.c{color:#6a7385}
.k{color:#5fb0c9}
.s{color:#9ec97f}
.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.info{background:rgba(95,176,201,.16);color:var(--accent2)}
.tag.bad{background:rgba(224,106,106,.16);color:var(--bad)}
.card{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px 20px;margin:14px 0}
.card h3{margin-top:0;color:var(--accent2)}
.files{font-family:var(--mono);font-size:12px;color:var(--fg2);margin:6px 0 10px}
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}
ul,ol{padding-left:22px;margin:8px 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}
.phase{font-size:19px;margin:40px 0 6px;color:var(--accent);font-weight:700}
.diagram{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:12px;margin:16px 0;overflow-x:auto}
</style>
</head>
<body>
<div class="wrap">
<a class="back" href="index.html">← 文档索引</a>
<h1>家庭内网远程访问方案<br>frp 反向隧道 × pangolin × 分流 DNS</h1>
<p class="sub">2026-07-15 · <span class="tag info">执行中</span> P0P2 已完成(frps@pangolin1 运行中、hairpin 实测 OK、CF 三条 A 记录已生效);P3 待跑 <code>scripts/setup-frp-remote.sh</code> · 相关:<a href="public-access.md">Tailscale 公网访问(现状,保留作应急后门)</a></p>
<div class="lead">
<b>目标:</b>①在家直连内网、不出外网;②在外<b>开着 pangolin</b> 就能穿透回家(不装第二个 VPN 客户端);③内外用<b>同一套地址</b>——<code>git.yanmeiai.com</code>Forgejo)、<code>nas.yanmeiai.com</code>DSM/Photos)、<code>win.yanmeiai.com</code>Windows RDP/SSH)。<br>
<b>核心思路:</b>NAS 上的 frpc 主动外连 pangolin1 上的 frps 建反向隧道(家里零开放端口、不暴露家庭 IP);同一批域名<b>公网解析到 pangolin1、家内 DNS 解析到内网 IP</b>split-horizon);pangolin 客户端配置加一条「这几个域名走本地 DNS、私网结果直连」的分流规则,保证 pangolin 常开时内外自动走对路。
</div>
<h2>1 · 现状盘点(事实依据)</h2>
<table>
<tr><th>对象</th><th>现状</th><th>与本方案的关系</th></tr>
<tr><td><b>NAS</b>DS925+</td><td><code>192.168.3.200</code>;跑 Forgejo<code>:3000</code> HTTP / <code>:2222</code> SSH)、mihomo 透明代理(<code>:53</code> fake-ip DNS / TUN)、Tailscale<code>100.95.151.13</code></td><td>装 frpcmihomo 加 hosts 覆盖;Tailscale 作本次部署跳板</td></tr>
<tr><td><b>Windows</b></td><td><code>192.168.3.88</code>,网关/DNS 均指向 NAS;有 OpenSSH server<code>ssh windows</code></td><td>基本不改;frpc 从 NAS 直接转发其 RDP/SSH</td></tr>
<tr><td><b>pangolin1</b></td><td><code>103.119.13.48</code>Debian 122C/1G<code>:443</code> sing-box REALITY、<code>:8080</code> 控制面(仅 127.0.0.1)、<code>:9443</code> gRPC、cloudflared 出站隧道;ufw 默认拒入站</td><td>装 frps~20MB 内存);<code>7000/2222/3000/3389/5001/10022/10023</code> 均空闲无冲突</td></tr>
<tr><td><b>家宽</b></td><td>有真实公网 IP(动态),路由器无端口映射</td><td>保持零开放端口,不做 DDNS</td></tr>
<tr><td><b>DNS</b></td><td><code>yanmeiai.com</code> 在 Cloudflare(管理走 <code>cf-api</code>);<code>git.51yanmei.com</code> 公网 A 记录指 <code>192.168.3.200</code>(仅家内可达)</td><td>新增 3 条 DNS-only A 记录;旧 git 域名可选迁移</td></tr>
</table>
<h2>2 · 架构总览</h2>
<div class="diagram">
<svg viewBox="0 0 900 470" width="100%" font-family="-apple-system,'PingFang SC',sans-serif" font-size="13">
<!-- ===== 在外场景 ===== -->
<text x="20" y="28" fill="#e0884f" font-weight="700" font-size="15">在外(pangolin 开)</text>
<rect x="20" y="44" width="130" height="52" rx="8" fill="#1d2129" stroke="#272c36"/>
<text x="85" y="66" fill="#e6e8ee" text-anchor="middle">Mac / 手机</text>
<text x="85" y="84" fill="#a8afbd" text-anchor="middle" font-size="11">pangolin TUN</text>
<line x1="150" y1="70" x2="255" y2="70" stroke="#e0884f" stroke-width="2"/>
<polygon points="255,70 246,65 246,75" fill="#e0884f"/>
<text x="202" y="60" fill="#e0884f" text-anchor="middle" font-size="11">REALITY 隧道</text>
<rect x="258" y="30" width="240" height="130" rx="10" fill="#171a22" stroke="#e0884f"/>
<text x="378" y="52" fill="#e6e8ee" text-anchor="middle" font-weight="600">pangolin1 · 103.119.13.48</text>
<rect x="274" y="64" width="98" height="36" rx="6" fill="#1d2129" stroke="#272c36"/>
<text x="323" y="87" fill="#e6e8ee" text-anchor="middle" font-size="12">sing-box</text>
<rect x="386" y="64" width="98" height="36" rx="6" fill="#1d2129" stroke="#272c36"/>
<text x="435" y="87" fill="#e6e8ee" text-anchor="middle" font-size="12">frps</text>
<line x1="372" y1="82" x2="386" y2="82" stroke="#5fb0c9" stroke-width="2"/>
<polygon points="386,82 378,78 378,86" fill="#5fb0c9"/>
<text x="378" y="125" fill="#5fb0c9" text-anchor="middle" font-size="11">机内回环(hairpin):出口连自己</text>
<text x="378" y="143" fill="#a8afbd" text-anchor="middle" font-size="11">业务端口 ufw 不开放 → 外网扫不到</text>
<line x1="498" y1="82" x2="610" y2="82" stroke="#5fb0c9" stroke-width="2" stroke-dasharray="6 4"/>
<polygon points="502,82 511,77 511,87" fill="#5fb0c9"/>
<text x="554" y="72" fill="#5fb0c9" text-anchor="middle" font-size="11">frp 反向长连接</text>
<text x="554" y="100" fill="#a8afbd" text-anchor="middle" font-size="10">(家里主动拨出,TLS+token</text>
<rect x="612" y="30" width="268" height="150" rx="10" fill="#171a22" stroke="#5ec27a"/>
<text x="746" y="52" fill="#e6e8ee" text-anchor="middle" font-weight="600">家庭内网 192.168.3.0/24</text>
<rect x="628" y="64" width="110" height="52" rx="6" fill="#1d2129" stroke="#272c36"/>
<text x="683" y="84" fill="#e6e8ee" text-anchor="middle" font-size="12">NAS .200</text>
<text x="683" y="102" fill="#a8afbd" text-anchor="middle" font-size="10">frpc·git·DSM·DNS</text>
<rect x="754" y="64" width="110" height="52" rx="6" fill="#1d2129" stroke="#272c36"/>
<text x="809" y="84" fill="#e6e8ee" text-anchor="middle" font-size="12">Windows .88</text>
<text x="809" y="102" fill="#a8afbd" text-anchor="middle" font-size="10">RDP 3389 · SSH 22</text>
<line x1="738" y1="90" x2="754" y2="90" stroke="#5ec27a" stroke-width="2"/>
<polygon points="754,90 746,86 746,94" fill="#5ec27a"/>
<text x="746" y="168" fill="#a8afbd" text-anchor="middle" font-size="11">路由器零端口映射,家庭 IP 不出现在公网 DNS</text>
<!-- ===== 在家场景 ===== -->
<text x="20" y="250" fill="#5ec27a" font-weight="700" font-size="15">在家(pangolin 照样开)</text>
<rect x="20" y="266" width="130" height="52" rx="8" fill="#1d2129" stroke="#272c36"/>
<text x="85" y="288" fill="#e6e8ee" text-anchor="middle">Mac / 手机</text>
<text x="85" y="306" fill="#a8afbd" text-anchor="middle" font-size="11">pangolin TUN</text>
<rect x="240" y="252" width="300" height="80" rx="10" fill="#171a22" stroke="#272c36"/>
<text x="390" y="276" fill="#e6e8ee" text-anchor="middle" font-weight="600">客户端分流规则(BuildClientConfig 下发)</text>
<text x="390" y="298" fill="#a8afbd" text-anchor="middle" font-size="11">这 3 个域名 → 本地 DNS 解析 → 得内网 IP</text>
<text x="390" y="316" fill="#5ec27a" text-anchor="middle" font-size="11">私网地址命中 LAN 直连规则 → 不进隧道</text>
<line x1="150" y1="292" x2="240" y2="292" stroke="#5ec27a" stroke-width="2"/>
<polygon points="240,292 231,287 231,297" fill="#5ec27a"/>
<line x1="540" y1="292" x2="640" y2="292" stroke="#5ec27a" stroke-width="2"/>
<polygon points="640,292 631,287 631,297" fill="#5ec27a"/>
<text x="590" y="282" fill="#5ec27a" text-anchor="middle" font-size="11">纯局域网直连</text>
<rect x="642" y="252" width="238" height="80" rx="10" fill="#171a22" stroke="#5ec27a"/>
<text x="761" y="280" fill="#e6e8ee" text-anchor="middle">NAS .200 / Windows .88</text>
<text x="761" y="302" fill="#a8afbd" text-anchor="middle" font-size="11">mihomo hosts 把 3 个域名</text>
<text x="761" y="318" fill="#a8afbd" text-anchor="middle" font-size="11">解析成内网 IP</text>
<!-- ===== DNS 分流对照 ===== -->
<rect x="20" y="366" width="860" height="84" rx="10" fill="#0a0c11" stroke="#272c36"/>
<text x="40" y="392" fill="#a8afbd" font-size="12">同一个域名,两处答案(split-horizon):</text>
<text x="40" y="416" fill="#e6e8ee" font-size="12" font-family="ui-monospace,Menlo,monospace">git.yanmeiai.com</text>
<text x="220" y="416" fill="#5fb0c9" font-size="12">公网 DNSCloudflare)→ 103.119.13.48pangolin1/frps</text>
<text x="220" y="438" fill="#5ec27a" font-size="12">家内 DNSNAS mihomo)→ 192.168.3.200(直连)</text>
</svg>
</div>
<h2>3 · 统一地址与端口规划</h2>
<p>选端口的原则:<b>内外端口号一致</b>(同一个 URL 全球通用)。唯一例外是裸 SSH——<code>:22</code> 在 pangolin1 上被它自己的 sshd 占用,只能错位到 10022/10023,由 <code>~/.ssh/config</code> 别名消化(见 §7)。</p>
<table>
<tr><th>服务</th><th>统一地址</th><th>frpc 转发目标(家内)</th><th>pangolin1 remotePort</th><th>备注</th></tr>
<tr><td>Forgejo Git SSH</td><td><code>ssh://git@git.yanmeiai.com:2222</code></td><td><code>127.0.0.1:2222</code></td><td>2222</td><td>内外一致 ✓</td></tr>
<tr><td>Forgejo Web</td><td><code>http://git.yanmeiai.com:3000</code></td><td><code>127.0.0.1:3000</code></td><td>3000</td><td>内外一致 ✓</td></tr>
<tr><td>DSM / Photos</td><td><code>https://nas.yanmeiai.com:5001</code></td><td><code>127.0.0.1:5001</code></td><td>5001</td><td>内外一致 ✓;Photos App 填这个地址</td></tr>
<tr><td>Windows RDP</td><td><code>win.yanmeiai.com:3389</code></td><td><code>192.168.3.88:3389</code></td><td>3389</td><td>内外一致 ✓</td></tr>
<tr><td>NAS SSH</td><td>外:<code>nas.yanmeiai.com:10022</code><br>家:<code>192.168.3.200:22</code></td><td><code>127.0.0.1:22</code></td><td>10022</td><td>端口错位,ssh alias 兜底</td></tr>
<tr><td>Windows SSH</td><td>外:<code>win.yanmeiai.com:10023</code><br>家:<code>192.168.3.88:22</code></td><td><code>192.168.3.88:22</code></td><td>10023</td><td>端口错位,ssh alias 兜底</td></tr>
</table>
<p class="small">不转发的:SMB/445(协议不适合过公网,文件走 Synology Drive/Photos 或 git);DSM 5000(明文 HTTP,只留 5001 HTTPS);mihomo 9090 管理口(有需要时临时经 NAS SSH 端口转发)。</p>
<h2>4 · 安全模型(三层)</h2>
<div class="card">
<h3>第一层:业务端口对公网「隐形」(关键设计)</h3>
<p>pangolin1 的 ufw 默认拒绝入站。<b>frps 的业务端口(2222/3000/3389/5001/10022/10023)一条 allow 规则都不加。</b>你在外面走 pangolin 时,流量从 sing-box 出站连 <code>103.119.13.48</code>——目的地是本机自己的公网 IP,内核走 <b>loopback 回环(hairpin</b>送达,而 ufw 对 <code>lo</code> 接口默认全放行。效果:<b>这些端口只有「连着 pangolin 的你」能访问</b>,公网扫描器看到的是全拒。RDP 这种绝不能裸奔的服务因此是安全的。</p>
<p class="small">验证兜底:若实测 hairpin 未走 lo(§9 验收步骤 P1-4 会测),补一条 <code>ufw allow from 103.119.13.48</code> 等价实现,语义不变。</p>
</div>
<div class="card">
<h3>第二层:frp 控制通道加固</h3>
<p><code>:7000</code> 是唯一新开的公网端口(frpc 从家里拨入用,家宽 IP 动态、无法限源)。加固:<code>auth.token</code>32 字节随机,存 Bitwarden+ <code>transport.tls.force = true</code>(强制 TLS,未握手连接直接断)。</p>
</div>
<div class="card">
<h3>第三层:业务自身认证不放松</h3>
<p>Forgejo 密钥登录、DSM 账号 2FA + 自动封锁、Windows RDP 强密码/NLA——原有认证全部保留,隧道只是网络通路。Tailscale 保留不动,作为 frp 链路故障时的应急后门。</p>
</div>
<h2>5 · pangolin1 怎么配(装 frps</h2>
<p class="files">机器:<code>ssh pangolin1</code> · 人在外也能直接做(公网可达)</p>
<h3>5.1 生成 token(本机 Mac 上)</h3>
<pre><code><span class="c"># 生成 32 字节随机 token,存进 Bitwarden(条目名:frp home-tunnel</span>
openssl rand -hex 32
rbw add "frp home-tunnel" <span class="c"># 密码字段贴上面的输出;两端配置从这里取,不落明文文件</span></code></pre>
<h3>5.2 安装 frps 二进制 + systemd</h3>
<pre><code><span class="c"># ssh pangolin1 之后(root)。VER 以 frp releases 最新版为准,下例为示意</span>
VER=0.61.1
curl -LO https://github.com/fatedier/frp/releases/download/v${VER}/frp_${VER}_linux_amd64.tar.gz
tar xzf frp_${VER}_linux_amd64.tar.gz
install -m 755 frp_${VER}_linux_amd64/frps /usr/local/bin/frps
mkdir -p /etc/frp</code></pre>
<p><code>/etc/frp/frps.toml</code><span class="tag warn">token 不入 git</span>,与 <code>/etc/pangolin*</code> 密钥同规矩):</p>
<pre><code><span class="k">bindAddr</span> = <span class="s">"0.0.0.0"</span>
<span class="k">bindPort</span> = 7000
<span class="k">auth.method</span> = <span class="s">"token"</span>
<span class="k">auth.token</span> = <span class="s">"&lt;Bitwarden: frp home-tunnel&gt;"</span>
<span class="k">transport.tls.force</span> = true <span class="c"># 拒绝非 TLS 的 frpc</span>
<span class="c"># 不开 dashboard;业务端口按 frpc 请求动态监听</span>
<span class="k">allowPorts</span> = [
{ <span class="k">start</span> = 2222, <span class="k">end</span> = 2222 }, { <span class="k">start</span> = 3000, <span class="k">end</span> = 3000 },
{ <span class="k">start</span> = 3389, <span class="k">end</span> = 3389 }, { <span class="k">start</span> = 5001, <span class="k">end</span> = 5001 },
{ <span class="k">start</span> = 10022, <span class="k">end</span> = 10023 },
] <span class="c"># 白名单锁死,token 泄露也开不了别的口</span></code></pre>
<p><code>/etc/systemd/system/frps.service</code></p>
<pre><code>[Unit]
Description=frp server (home reverse tunnel)
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=always
RestartSec=5
User=nobody <span class="c"># 非特权端口,无需 root</span>
LimitNOFILE=65536
<span class="c"># 1G 小机安全带:内存超限就重启,绝不拖垮 sing-box/控制面</span>
MemoryMax=128M
[Install]
WantedBy=multi-user.target</code></pre>
<pre><code>chmod 600 /etc/frp/frps.toml &amp;&amp; chown nobody /etc/frp/frps.toml
systemctl daemon-reload &amp;&amp; systemctl enable --now frps
ufw allow 7000/tcp comment 'frp control (frpc dial-in from home)'
<span class="c"># 验证:只多了 7000 这一个对外口</span>
systemctl status frps --no-pager | head -5
ss -tlnp | grep frps</code></pre>
<p class="small">⚠️ 台账提醒:pangolin1 续费到期 <b>2026-07-19</b>baize <code>billing.renew_at</code>)——动工前先确认已续费。</p>
<h2>6 · DNS 怎么配(Cloudflare + 家内 mihomo</h2>
<h3>6.1 公网侧:Cloudflare 加 3 条 A 记录(本机 Maccf-api</h3>
<p><b>必须 DNS only(灰云,<code>proxied:false</code></b>——git SSH / RDP 是任意 TCPCF 橙云只代理 HTTP。zone id 见 baize<code>2325730de45276d87180a8b66bd4cca0</code></p>
<pre><code>cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
--data '{"type":"A","name":"nas","content":"103.119.13.48","proxied":false,"ttl":300}'
cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
--data '{"type":"A","name":"git","content":"103.119.13.48","proxied":false,"ttl":300}'
cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
--data '{"type":"A","name":"win","content":"103.119.13.48","proxied":false,"ttl":300}'</code></pre>
<p class="small">代价说明:灰云记录会暴露 pangolin1 的 IP——它本来就是 VPN 数据面入口(客户端 endpoint 直连该 IP),无新增暴露。改完回写 baize <code>domains.yaml</code></p>
<h3>6.2 家内侧:NAS mihomo 加 hosts 覆盖</h3>
<p><code>/volume1/docker/proxy/mihomo/config.yaml</code> 两处改动(仓库 <code>docker/proxy/mihomo/config.yaml</code> 同步改):</p>
<pre><code><span class="k">hosts</span>: <span class="c"># ① 家内答案:直接给内网 IP</span>
<span class="k">nas.yanmeiai.com</span>: <span class="s">192.168.3.200</span>
<span class="k">git.yanmeiai.com</span>: <span class="s">192.168.3.200</span>
<span class="k">win.yanmeiai.com</span>: <span class="s">192.168.3.88</span>
<span class="k">git.51yanmei.com</span>: <span class="s">192.168.3.200</span> <span class="c"># 兼容既有 git remote(可选,见 §10</span>
<span class="k">dns</span>:
<span class="k">fake-ip-filter</span>: <span class="c"># ② 追加:不给这几个域名发 fake-ip,走 hosts 真实值</span>
<span class="c"># ……既有条目保持不动(含 +.tailscale.com 等)……</span>
- <span class="s">nas.yanmeiai.com</span>
- <span class="s">git.yanmeiai.com</span>
- <span class="s">win.yanmeiai.com</span>
- <span class="s">git.51yanmei.com</span></code></pre>
<pre><code><span class="c"># 热重载(不重启容器),沿用仓库既有做法</span>
ssh nas "curl -s -X PUT 'http://127.0.0.1:9090/configs?force=true' \
-H 'Content-Type: application/json' \
-d '{\"path\":\"/root/.config/mihomo/config.yaml\"}'"
<span class="c"># 验证:家内解析应返回 192.168.3.200</span>
ssh nas "nslookup git.yanmeiai.com 127.0.0.1"</code></pre>
<h3>6.3 路由器:DHCP 的 DNS 指到 NAS(回家后做)</h3>
<p>Windows 已手动指 NAS;要让 Mac/手机在家也吃到 hosts 覆盖,把路由器 DHCP 下发的 DNS 改为 <code>192.168.3.200</code>(一处改动、全家生效)。<span class="tag warn">取舍</span>:NAS 由此成为家内 DNS 单点——不要配第二个公共 DNS 兜底(客户端会随机用它,覆盖时灵时不灵);NAS 重启的几分钟内家里断解析,可接受。</p>
<h2>7 · NAS 怎么配(装 frpc</h2>
<p class="files">仓库新增:<code>docker/frp/docker-compose.yml</code> + <code>docker/frp/frpc.toml</code> + <code>docker/frp/.env.example</code> · NAS 路径:<code>/volume1/docker/frp/</code></p>
<p>沿用本仓库 docker-compose 惯例(forgejo/proxy 同款)。<code>wangjia</code> 不在 docker 组,部署用 <code>sudo</code> 或 Container Manager UI。</p>
<p><code>docker-compose.yml</code></p>
<pre><code><span class="k">services</span>:
<span class="k">frpc</span>:
<span class="k">image</span>: <span class="s">snowdreamtech/frpc:0.61</span> <span class="c"># 多架构维护镜像;版本与 frps 对齐</span>
<span class="k">container_name</span>: frpc
<span class="k">network_mode</span>: host <span class="c"># 需直达 127.0.0.1 各服务 + 192.168.3.88</span>
<span class="k">restart</span>: unless-stopped
<span class="k">volumes</span>:
- <span class="s">./frpc.toml:/etc/frp/frpc.toml:ro</span></code></pre>
<p><code>frpc.toml</code>(token 同样不入 git,仓库放 <code>frpc.toml.example</code> 占位):</p>
<pre><code><span class="k">serverAddr</span> = <span class="s">"103.119.13.48"</span>
<span class="k">serverPort</span> = 7000
<span class="k">auth.method</span> = <span class="s">"token"</span>
<span class="k">auth.token</span> = <span class="s">"&lt;Bitwarden: frp home-tunnel&gt;"</span>
<span class="k">transport.tls.enable</span> = true
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"git-ssh"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"127.0.0.1"</span>
<span class="k">localPort</span> = 2222
<span class="k">remotePort</span> = 2222
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"git-web"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"127.0.0.1"</span>
<span class="k">localPort</span> = 3000
<span class="k">remotePort</span> = 3000
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"dsm"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"127.0.0.1"</span>
<span class="k">localPort</span> = 5001
<span class="k">remotePort</span> = 5001
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"nas-ssh"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"127.0.0.1"</span>
<span class="k">localPort</span> = 22
<span class="k">remotePort</span> = 10022
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"win-rdp"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"192.168.3.88"</span> <span class="c"># 同网段代转 Windows,一个 frpc 管全家</span>
<span class="k">localPort</span> = 3389
<span class="k">remotePort</span> = 3389
[[<span class="k">proxies</span>]]
<span class="k">name</span> = <span class="s">"win-ssh"</span>
<span class="k">type</span> = <span class="s">"tcp"</span>
<span class="k">localIP</span> = <span class="s">"192.168.3.88"</span>
<span class="k">localPort</span> = 22
<span class="k">remotePort</span> = 10023</code></pre>
<pre><code><span class="c"># 部署(经 Tailscale 跳板 ssh 到 NAS 后)</span>
sudo mkdir -p /volume1/docker/frp &amp;&amp; cd /volume1/docker/frp
<span class="c"># (把仓库里的两个文件放进来,frpc.toml 填真 token</span>
sudo docker compose up -d
sudo docker logs frpc --tail 20 <span class="c"># 应见 "login to server success" + 6 个 proxy 启动成功</span></code></pre>
<h3>Mac 的 ssh 别名收尾(<code>~/.ssh/config</code></h3>
<pre><code><span class="c"># 既有 Host nas / windows(内网 IP 直连)保持不动,家里继续用</span>
Host nas-r <span class="c"># 在外用:ssh nas-r</span>
HostName nas.yanmeiai.com
Port 10022
User wangjia
Host win-r <span class="c"># 在外用:ssh win-r</span>
HostName win.yanmeiai.com
Port 10023
User wangjia</code></pre>
<h2>8 · Windows 怎么改(几乎不用)</h2>
<table>
<tr><th></th><th>动作</th><th>说明</th></tr>
<tr><td>网络</td><td><span class="tag ok">不改</span></td><td>网关/DNS 已指 NAS,天然吃到 hosts 覆盖</td></tr>
<tr><td>RDP</td><td>确认已启用(设置 → 系统 → 远程桌面),保持 NLA 开启</td><td>frpc 从 NAS(同网段 .200)连入,默认防火墙「专用网络」范围即放行</td></tr>
<tr><td>OpenSSH server</td><td><span class="tag ok">已有</span><code>ssh windows</code> 在用),确认服务设为自动启动</td><td>同上,无需改防火墙</td></tr>
<tr><td>防火墙</td><td>仅当连不通时检查:RDP/SSH 入站规则的作用域含本地子网</td><td>不需要也不要开「任意来源」</td></tr>
</table>
<h2>9 · pangolin 客户端分流(BuildClientConfig,让家里直连成立)</h2>
<p class="files">代码:<code>server/internal/httpapi/clientconfig.go::BuildClientConfig</code> · 单独一刀,四端零改动(配置服务端渲染下发)</p>
<p>pangolin TUN 的首条路由规则是 <code>hijack-dns</code>,所有 DNS 都被劫去隧道解析——家里也会拿到公网 IP、绕道外网。解法是给这几个域名开「本地解析 + 按结果路由」的侧门:</p>
<pre><code><span class="c">// 伪代码示意。域名清单走 server 配置(env PRIVATE_SPLIT_DOMAINS),默认空 = 行为不变,</span>
<span class="c">// 不影响其他用户。</span>
<span class="c">// ① dns.servers 增本地解析器</span>
{ <span class="s">"tag"</span>: <span class="s">"dns-local"</span>, <span class="s">"address"</span>: <span class="s">"local"</span>, <span class="s">"detour"</span>: <span class="s">"direct"</span> }
<span class="c">// ② dns.rules 前插:这 3 个域名用本地 DNS(家=mihomo hosts→内网IP;外=公网DNS→pangolin1</span>
{ <span class="s">"domain"</span>: [<span class="s">"nas.yanmeiai.com"</span>,<span class="s">"git.yanmeiai.com"</span>,<span class="s">"win.yanmeiai.com"</span>], <span class="s">"server"</span>: <span class="s">"dns-local"</span> }
<span class="c">// ③ route.rules 在 hijack-dns 之后插:先解析再按 IP 匹配后续规则</span>
{ <span class="s">"domain"</span>: [<span class="s">"…同上…"</span>], <span class="s">"action"</span>: <span class="s">"resolve"</span> }
<span class="c">// ④ 解析结果为私网 → 命中既有 LAN 直连规则 → 家里直连;</span>
<span class="c">// 公网 IPpangolin1)→ 不匹配 → 走默认代理出口 → 机内 hairpin 到 frps</span></code></pre>
<p><span class="tag warn">实现时必须验证</span><code>action: resolve</code> 是 sing-box ≥ 1.11 语法——先确认四端内嵌 libbox 版本;不够就先升 libbox<code>scripts/build-libbox.sh</code> 重编,产物 gitignore 需各端重新放置)。按仓库惯例先在 macOS 端端到端验证,再推 iOS/Android/Windows。</p>
<p class="small">此刀未落地前的过渡用法:在外一切正常(隧道 DNS 拿公网 IP 本来就该走隧道);<b>在家</b>访问这几个域名会绕外网一圈(功能可用、不满足目标①),或临时关 pangolin 直连。</p>
<h2>10 · 执行顺序(人在外,现在就能开工)</h2>
<p class="phase">P0 · 前置(5 分钟)</p>
<ul>
<li>确认 pangolin1 已续费(<b>2026-07-19 到期</b></li>
<li>生成 token 存 Bitwarden「frp home-tunnel」(§5.1</li>
</ul>
<p class="phase">P1 · pangolin1 装 frps(直接可做,§5.2</p>
<ul>
<li>装二进制 + 配置 + systemd 拉起,<code>ufw allow 7000/tcp</code></li>
<li>验证:<code>ss -tlnp | grep frps</code> 只见 7000<b>P1-4 hairpin 实测</b>——pangolin1 本机 <code>curl --max-time 3 http://103.119.13.48:7000</code> 通(说明本机对自己公网 IP 走 lo),外网机器(如 ali)<code>nc -zw3 103.119.13.48 2222</code> 必须<b>不通</b></li>
</ul>
<p class="phase">P2 · Cloudflare 加 3 条灰云 A 记录(直接可做,§6.1)</p>
<ul>
<li>验证:<code>dig +short git.yanmeiai.com @1.1.1.1</code><code>103.119.13.48</code></li>
</ul>
<p class="phase">P3 · 经 Tailscale 跳板配 NAS(§6.2 + §7</p>
<ul>
<li>Mac:断 pangolin → 连 Tailscale →(若登录卡住:Tailscale 登录要过 Google,可先在 pangolin 开着时完成浏览器授权再切换)</li>
<li><code>ssh wangjia@100.95.151.13</code>:部署 frpc compose、改 mihomohosts + fake-ip-filter)并热重载</li>
<li>验证:<code>docker logs frpc</code> 见 6 个 proxy 成功;<code>nslookup git.yanmeiai.com 127.0.0.1</code> → 192.168.3.200</li>
<li>Mac:断 Tailscale → 恢复 pangolin</li>
</ul>
<p class="phase">P4 · 在外端到端验收(pangolin 开着)</p>
<ul>
<li><code>git ls-remote ssh://git@git.yanmeiai.com:2222/wangjia/nas.git</code></li>
<li>浏览器 <code>http://git.yanmeiai.com:3000</code> 出 Forgejo 登录页 ✓</li>
<li><code>https://nas.yanmeiai.com:5001</code> 出 DSM(证书告警属预期,见 §12 增强)✓</li>
<li>Microsoft Remote Desktop 连 <code>win.yanmeiai.com</code> ✓;<code>ssh nas-r</code> / <code>ssh win-r</code></li>
<li>反证:<b>关掉 pangolin</b> 后上述地址应全部超时(ufw 隐形生效)</li>
</ul>
<p class="phase">P5 · 回家后收尾(§6.3</p>
<ul>
<li>路由器 DHCP DNS → 192.168.3.200Mac 重新拿 DHCP</li>
<li>验证:<code>dig +short git.yanmeiai.com</code> → 192.168.3.200;关 pangolin 时 <code>git ls-remote</code>/DSM 直连均通且 traceroute 一跳</li>
</ul>
<p class="phase">P6 · pangolin 分流规则(单独一刀,§9)</p>
<ul>
<li>确认 libbox 版本 → 改 <code>BuildClientConfig</code> + 单测 → macOS 端在家实测「pangolin 开着直连内网」→ 推其余三端</li>
</ul>
<p class="phase">P7 · 台账与文档回写</p>
<ul>
<li>baize <code>domains.yaml</code>3 条新记录 + expected_resolution);nas 仓 <code>CLAUDE.md</code>frp 服务 + 端口清单);<code>~/.ssh/config</code>nas-r/win-r);todo 登记</li>
</ul>
<h2>11 · 风险与回滚</h2>
<table>
<tr><th>风险</th><th>影响</th><th>缓解 / 回滚</th></tr>
<tr><td>NAS 成家内 DNS 单点</td><td>NAS 挂 → 全家断解析</td><td>接受(NAS 稳定);应急把路由器 DHCP DNS 改回运营商</td></tr>
<tr><td>token 泄露</td><td>他人可向 frps 注册代理</td><td><code>allowPorts</code> 白名单锁死端口范围;业务端口本就 ufw 隐形;换 token 重启两端即失效</td></tr>
<tr><td>frps 占用拖累 1G 小机</td><td>控制面/数据面受影响</td><td>systemd <code>MemoryMax=128M</code>,超限自杀重启,隔离爆炸半径</td></tr>
<tr><td>hairpin 假设不成立</td><td>在外连不上业务端口</td><td>P1-4 提前实测;兜底 <code>ufw allow from 103.119.13.48</code></td></tr>
<tr><td>pangolin1 迁移/换 IP</td><td>公网侧全断</td><td>frps 配置随 deploy 脚本走;改 3 条 A 记录即恢复;家内直连不受影响</td></tr>
<tr><td>libbox 版本不支持 resolve</td><td>P6 延期</td><td>P1–P5 不依赖 P6;过渡期家里关 pangolin 或接受绕行</td></tr>
<tr><td>整体回滚</td><td></td><td>NAS<code>docker compose down</code>pangolin1<code>systemctl disable --now frps</code> + 删 ufw 7000CF 删 3 条记录;mihomo 还原备份重载。全程不动业务服务</td></tr>
</table>
<h2>12 · 可选增强(不阻塞主线)</h2>
<ul>
<li><b>旧 git 域名兼容</b>:把 <code>git.51yanmei.com</code> 公网 A 记录(阿里云)从 192.168.3.200 改指 103.119.13.48——既有仓库 remote 在外也能用,家内靠 §6.2 的 hosts 覆盖。或者反过来:全量迁 remote 到 <code>git.yanmeiai.com</code>,旧域名退役。</li>
<li><b>DSM 正经证书</b>DSM 用 Let's Encrypt DNS-01CF API)给 <code>nas.yanmeiai.com</code> 签证书,消掉 5001 的浏览器告警。</li>
<li><b>Forgejo ROOT_URL</b>app.ini 的 DOMAIN/SSH_DOMAIN/ROOT_URL 改为 <code>git.yanmeiai.com</code>(对应 CLAUDE.md 里既有 TODO),web 页面上的 clone 地址就会是新域名。</li>
<li><b>监控</b>pangolin-monitorbootstrap 自带 Telegram 通道)加一条 frps 存活/7000 端口探测;NAS 侧 frpc 掉线重连靠 docker restart 策略兜底。</li>
</ul>
</div>
</body>
</html>