From 9aa5edbdef12a88aeaf6c6b03219227769d3a9e5 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Wed, 15 Jul 2026 00:53:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(frp):=20=E8=BF=9C=E7=A8=8B=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E4=B8=80=E9=94=AE=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= =?UTF-8?q?(frpc+mihomo=E5=88=86=E6=B5=81DNS)=20+=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=EF=BC=9B=E6=96=87=E6=A1=A3=E6=A0=87=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/frp/docker-compose.yml | 8 ++ docker/frp/frpc.toml.example | 49 +++++++++ docs/index.html | 4 +- docs/remote-access-frp.html | 2 +- scripts/setup-frp-on-nas.sh | 188 ++++++++++++++++++++++++++++++++++ scripts/setup-frp-remote.sh | 26 +++++ 6 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 docker/frp/docker-compose.yml create mode 100644 docker/frp/frpc.toml.example create mode 100755 scripts/setup-frp-on-nas.sh create mode 100755 scripts/setup-frp-remote.sh diff --git a/docker/frp/docker-compose.yml b/docker/frp/docker-compose.yml new file mode 100644 index 0000000..e828cad --- /dev/null +++ b/docker/frp/docker-compose.yml @@ -0,0 +1,8 @@ +services: + frpc: + image: snowdreamtech/frpc:0.70.0 + container_name: frpc + network_mode: host # 需直达 127.0.0.1 各服务 + 192.168.3.88(Windows) + restart: unless-stopped + volumes: + - ./frpc.toml:/etc/frp/frpc.toml:ro diff --git a/docker/frp/frpc.toml.example b/docker/frp/frpc.toml.example new file mode 100644 index 0000000..7a6c863 --- /dev/null +++ b/docker/frp/frpc.toml.example @@ -0,0 +1,49 @@ +# 复制为 frpc.toml 并把 token 换成真值(Bitwarden「frp home-tunnel」)。 +# frpc.toml 含密钥,不入 git(部署由 scripts/setup-frp-remote.sh 自动完成)。 +serverAddr = "103.119.13.48" # pangolin1(frps) +serverPort = 7000 +auth.method = "token" +auth.token = "" +transport.tls.enable = true + +[[proxies]] +name = "git-ssh" +type = "tcp" +localIP = "127.0.0.1" +localPort = 2222 +remotePort = 2222 + +[[proxies]] +name = "git-web" +type = "tcp" +localIP = "127.0.0.1" +localPort = 3000 +remotePort = 3000 + +[[proxies]] +name = "dsm" +type = "tcp" +localIP = "127.0.0.1" +localPort = 5001 +remotePort = 5001 + +[[proxies]] +name = "nas-ssh" +type = "tcp" +localIP = "127.0.0.1" +localPort = 22 +remotePort = 10022 + +[[proxies]] +name = "win-rdp" +type = "tcp" +localIP = "192.168.3.88" +localPort = 3389 +remotePort = 3389 + +[[proxies]] +name = "win-ssh" +type = "tcp" +localIP = "192.168.3.88" +localPort = 22 +remotePort = 10023 diff --git a/docs/index.html b/docs/index.html index 1306c24..ebafd09 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,8 +37,8 @@ 文档状态说明 家庭内网远程访问 —— frp 反向隧道 × pangolin × 分流 DNS - 待评审 - 内外同域名访问 NAS/Windows:frpc(NAS)→frps(pangolin1) 反向隧道 + split-horizon DNS + pangolin 客户端分流 + 执行中 + 内外同域名访问 NAS/Windows:frpc(NAS)→frps(pangolin1) 反向隧道 + split-horizon DNS + pangolin 客户端分流;NAS 侧一键脚本 scripts/setup-frp-remote.sh diff --git a/docs/remote-access-frp.html b/docs/remote-access-frp.html index b8ce9a6..c574349 100644 --- a/docs/remote-access-frp.html +++ b/docs/remote-access-frp.html @@ -51,7 +51,7 @@
← 文档索引

家庭内网远程访问方案
frp 反向隧道 × pangolin × 分流 DNS

-

2026-07-15 · 设计方案 · 待评审 · 相关:Tailscale 公网访问(现状,保留作应急后门)

+

2026-07-15 · 执行中 P0–P2 已完成(frps@pangolin1 运行中、hairpin 实测 OK、CF 三条 A 记录已生效);P3 待跑 scripts/setup-frp-remote.sh · 相关:Tailscale 公网访问(现状,保留作应急后门)

目标:①在家直连内网、不出外网;②在外开着 pangolin 就能穿透回家(不装第二个 VPN 客户端);③内外用同一套地址——git.yanmeiai.com(Forgejo)、nas.yanmeiai.com(DSM/Photos)、win.yanmeiai.com(Windows RDP/SSH)。
diff --git a/scripts/setup-frp-on-nas.sh b/scripts/setup-frp-on-nas.sh new file mode 100755 index 0000000..04f2970 --- /dev/null +++ b/scripts/setup-frp-on-nas.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# 在 NAS 上执行(由 setup-frp-remote.sh 上传并调用,也可手动跑): +# ① 写 frpc 配置 + docker compose 拉起(需 sudo,会提示输 DSM 密码) +# ② mihomo 加 hosts 覆盖 / fake-ip-filter / use-hosts(幂等,自动备份,热重载) +# ③ 端到端验证 +# 前提:/tmp/frp-setup/token 里是 frp auth token(由远端脚本从 Bitwarden 送来)。 +set -euo pipefail + +FRP_DIR=/volume1/docker/frp +CFG=/volume1/docker/proxy/mihomo/config.yaml +TOKEN_FILE=/tmp/frp-setup/token + +[ -s "$TOKEN_FILE" ] || { echo "!! 缺 token 文件 $TOKEN_FILE(应由 setup-frp-remote.sh 生成)"; exit 1; } +read -r TOKEN < "$TOKEN_FILE" + +echo "==> 1/6 写 frpc.toml + docker-compose.yml(sudo)" +sudo mkdir -p "$FRP_DIR" +sudo tee "$FRP_DIR/frpc.toml" > /dev/null < /dev/null <<'EOF' +services: + frpc: + image: snowdreamtech/frpc:0.70.0 + container_name: frpc + network_mode: host + restart: unless-stopped + volumes: + - ./frpc.toml:/etc/frp/frpc.toml:ro +EOF + +echo "==> 2/6 拉起 frpc 容器" +cd "$FRP_DIR" +if sudo docker compose version >/dev/null 2>&1; then + sudo docker compose up -d +else + sudo docker-compose up -d +fi + +echo "==> 3/6 mihomo:hosts 覆盖 + fake-ip-filter + use-hosts(幂等)" +cp "$CFG" "$CFG.bak.frp" +echo " 备份: $CFG.bak.frp" +python3 - "$CFG" <<'PY' +import re, sys + +p = sys.argv[1] +text = open(p).read() +orig = text + +HOSTS = { + "nas.yanmeiai.com": "192.168.3.200", + "git.yanmeiai.com": "192.168.3.200", + "win.yanmeiai.com": "192.168.3.88", + "git.51yanmei.com": "192.168.3.200", # 兼容既有 git remote +} + +lines = text.splitlines(True) + +# ---- ① fake-ip-filter 追加域名(防 fake-ip 吞掉 hosts 真实值)---- +missing = [d for d in HOSTS if ("'" + d + "'") not in text and ('"' + d + '"') not in text and ("- " + d) not in text] +out = [] +inserted_filter = False +for i, ln in enumerate(lines): + out.append(ln) + if (not inserted_filter) and re.match(r"\s*fake-ip-filter:\s*$", ln): + # 探测下一行列表项缩进 + indent = " " + for nxt in lines[i+1:]: + m = re.match(r"(\s*)- ", nxt) + if m: + indent = m.group(1) + break + for d in missing: + out.append(indent + "- '" + d + "'\n") + inserted_filter = True +if missing and not inserted_filter: + print(" !! 没找到 fake-ip-filter: 段,请人工检查", file=sys.stderr) + sys.exit(1) +lines = out + +# ---- ② hosts 段(家内答案:域名 → 内网 IP)---- +text = "".join(lines) +have_hosts = re.search(r"^hosts:\s*$", text, re.M) +need = {d: ip for d, ip in HOSTS.items() if not re.search(r"^\s+" + re.escape(d) + ":", text, re.M)} +if need: + if have_hosts: + out = [] + done = False + for ln in text.splitlines(True): + out.append(ln) + if (not done) and re.match(r"^hosts:\s*$", ln): + for d, ip in need.items(): + out.append(" " + d + ": " + ip + "\n") + done = True + text = "".join(out) + else: + block = "\nhosts:\n" + for d, ip in need.items(): + block += " " + d + ": " + ip + "\n" + text += block + +# ---- ③ dns.use-hosts: true(mihomo 默认 false,不开 hosts 对 DNS 响应不生效)---- +if re.search(r"^\s*use-hosts:", text, re.M): + text = re.sub(r"(^\s*use-hosts:\s*)false", r"\1true", text, flags=re.M) +else: + out = [] + done = False + for ln in text.splitlines(True): + out.append(ln) + if (not done) and re.match(r"^dns:\s*$", ln): + out.append(" use-hosts: true\n") + done = True + if not done: + print(" !! 没找到 dns: 段,请人工检查", file=sys.stderr) + sys.exit(1) + text = "".join(out) + +if text != orig: + open(p, "w").write(text) + print(" 已写入(hosts %d 条新增 / fake-ip-filter %d 条新增)" % (len(need), len(missing))) +else: + print(" 无需改动(已是目标状态)") +PY + +echo "==> 4/6 热重载 mihomo" +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"}' +echo " reload 已请求" + +echo "==> 5/6 验证家内解析(期望 192.168.3.200)" +sleep 1 +nslookup git.yanmeiai.com 127.0.0.1 2>&1 | tail -3 + +echo "==> 6/6 验证 frpc 隧道" +sleep 2 +sudo docker logs frpc --tail 20 2>&1 || true +rm -f "$TOKEN_FILE" +echo "" +echo "完成。检查上方日志:应有 'login to server success',且 git-ssh/git-web/dsm/nas-ssh/win-rdp/win-ssh 六个 proxy 'start proxy success'。" +echo "若 login 失败:token 不对或 pangolin1:7000 不通;若个别 proxy 失败:对应本地端口没起。" diff --git a/scripts/setup-frp-remote.sh b/scripts/setup-frp-remote.sh new file mode 100755 index 0000000..abdea6a --- /dev/null +++ b/scripts/setup-frp-remote.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# 在 Mac 上执行的一键部署入口: +# rbw 取 token → 送到 NAS → 上传并执行 setup-frp-on-nas.sh(sudo 会提示输 DSM 密码) +# 用法: +# bash scripts/setup-frp-remote.sh # 默认走 Tailscale IP(人在外) +# bash scripts/setup-frp-remote.sh wangjia@192.168.3.200 # 在家走局域网 +# 前提:Mac 已连 Tailscale(pangolin 先断开);rbw 已 unlock。 +set -euo pipefail + +NAS_HOST="${1:-wangjia@100.95.151.13}" +cd "${0%/*}" # scripts/ 目录 + +echo "==> 从 Bitwarden 取 frp token" +TMPTOKEN=/tmp/frp-token.$$ +rbw get "frp home-tunnel" > "$TMPTOKEN" +[ -s "$TMPTOKEN" ] || { echo "!! rbw 取 token 失败(先 rbw unlock)"; rm -f "$TMPTOKEN"; exit 1; } +chmod 600 "$TMPTOKEN" + +echo "==> 上传 token 与脚本到 $NAS_HOST:/tmp/frp-setup/" +ssh "$NAS_HOST" 'mkdir -p /tmp/frp-setup && chmod 700 /tmp/frp-setup' +scp -q "$TMPTOKEN" "$NAS_HOST:/tmp/frp-setup/token" +scp -q setup-frp-on-nas.sh "$NAS_HOST:/tmp/frp-setup/" +rm -f "$TMPTOKEN" + +echo "==> 在 NAS 上执行(sudo 步骤会提示输入 DSM 密码)" +ssh -t "$NAS_HOST" 'bash /tmp/frp-setup/setup-frp-on-nas.sh'