feat(frp): 远程访问一键部署脚本(frpc+mihomo分流DNS) + 配置模板;文档标执行中
This commit is contained in:
@@ -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
|
||||
@@ -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 = "<Bitwarden: frp home-tunnel>"
|
||||
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
|
||||
+2
-2
@@ -37,8 +37,8 @@
|
||||
<tr><th>文档</th><th>状态</th><th>说明</th></tr>
|
||||
<tr>
|
||||
<td><a href="remote-access-frp.html">家庭内网远程访问 —— frp 反向隧道 × pangolin × 分流 DNS</a></td>
|
||||
<td><span class="tag warn">待评审</span></td>
|
||||
<td>内外同域名访问 NAS/Windows:frpc(NAS)→frps(pangolin1) 反向隧道 + split-horizon DNS + pangolin 客户端分流</td>
|
||||
<td><span class="tag warn">执行中</span></td>
|
||||
<td>内外同域名访问 NAS/Windows:frpc(NAS)→frps(pangolin1) 反向隧道 + split-horizon DNS + pangolin 客户端分流;NAS 侧一键脚本 <code>scripts/setup-frp-remote.sh</code></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<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 warn">设计方案 · 待评审</span> · 相关:<a href="public-access.md">Tailscale 公网访问(现状,保留作应急后门)</a></p>
|
||||
<p class="sub">2026-07-15 · <span class="tag info">执行中</span> P0–P2 已完成(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>
|
||||
|
||||
Executable
+188
@@ -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 <<EOF
|
||||
serverAddr = "103.119.13.48"
|
||||
serverPort = 7000
|
||||
auth.method = "token"
|
||||
auth.token = "$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
|
||||
EOF
|
||||
sudo chmod 600 "$FRP_DIR/frpc.toml"
|
||||
sudo tee "$FRP_DIR/docker-compose.yml" > /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 失败:对应本地端口没起。"
|
||||
Executable
+26
@@ -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'
|
||||
Reference in New Issue
Block a user