Files
webui4frpc/scripts/probe-with-virtual-frps.sh
JianFeeeee 2dd32648e6 docs: 补充内部机制说明与虚拟 frps 复现脚本
与本次 leader 修复无关,是工作区里此前遗留未提交的两个文件,误被 git add -A
卷进上一个提交,此处独立成提交。docs/how-it-works.md 记录了 DB 是唯一真源、
集群只复制环状态不复制配置、forward owner 归属等实测结论;配
scripts/probe-with-virtual-frps.sh 可在隔离环境复现全部结论,不触碰生产。
2026-10-05 17:02:41 +08:00

138 lines
5.4 KiB
Bash
Executable File
Raw Permalink 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.

#!/usr/bin/env bash
# 用隔离的虚拟 frps 探测 webui4frpc 行为。
#
# 全部端口与现网隔离(现网:7000/4434/7500/18082),不修改任何生产配置。
# 用法:scripts/probe-with-virtual-frps.sh
set -euo pipefail
LAB="${LAB:-/tmp/frplab}"
FRP_VER="${FRP_VER:-0.61.1}"
FRP_TGZ="frp_${FRP_VER}_linux_amd64"
UI_ADDR="${UI_ADDR:-127.0.0.1:17500}"
UI_USER="${UI_USER:-labadmin}"
UI_PASS="${UI_PASS:-labpass123}"
LAB_TOKEN="${LAB_TOKEN:-lab-token-$(date +%Y%m%d)}"
# 现网 frpc 二进制(唯一允许从生产读取的东西,只读)
SRC_FRPC="${SRC_FRPC:-/opt/webui4frpc/data/bin/frpc-${FRP_VER}/frpc}"
BACKEND_PORT=17090
TCP_PORT=17091
UI_FORWARD_PORT=17092
cleanup() {
echo "==> 清理"
pkill -f "webui4frpc -addr ${UI_ADDR}" 2>/dev/null || true
pkill -f "frplab/.*frpc" 2>/dev/null || true
[[ -n "${FRPS_PID:-}" ]] && kill "${FRPS_PID}" 2>/dev/null || true
[[ -n "${BACKEND_PID:-}" ]] && kill "${BACKEND_PID}" 2>/dev/null || true
}
trap cleanup EXIT
mkdir -p "${LAB}"/{frps,probe,logs,ui/data}
# ---------- 1. frps(虚拟服务端) ----------
if [[ ! -x "${LAB}/frpsd" ]]; then
echo "==> 下载 frps ${FRP_VER}"
curl -sL -o "${LAB}/frps-bin" \
"https://github.com/fatedier/frp/releases/download/v${FRP_VER}/${FRP_TGZ}.tar.gz"
tar xzf "${LAB}/frps-bin" -C "${LAB}"
cp "${LAB}/${FRP_TGZ}/frps" "${LAB}/frpsd"
chmod +x "${LAB}/frpsd"
fi
cat > "${LAB}/frps/frps.toml" <<EOF
bindPort = 17000
vhostHTTPPort = 17080
vhostHTTPsPort = 17443
auth.token = "${LAB_TOKEN}"
log.to = "${LAB}/logs/frps.log"
log.level = "debug"
EOF
echo "==> 启动虚拟 frps (17000/17080/17443)"
"${LAB}/frpsd" -c "${LAB}/frps/frps.toml" > "${LAB}/logs/frps-stdout.log" 2>&1 &
FRPS_PID=$!
sleep 3
ss -ltn | grep -E ':17000|:17443' || { echo "frps 未监听"; exit 1; }
# ---------- 2. 后端服务 ----------
echo "==> 启动后端 http://127.0.0.1:${BACKEND_PORT}"
( cd "${LAB}" && python3 -m http.server "${BACKEND_PORT}" --bind 127.0.0.1 > logs/backend.log 2>&1 & )
BACKEND_PID=$!
sleep 1
# ---------- 3. 裸 frpc 验证 frp 协议本身 ----------
cat > "${LAB}/probe/frpc-probe.json" <<EOF
{
"serverAddr": "127.0.0.1",
"serverPort": 17000,
"loginFailExit": false,
"auth": {"method": "token", "token": "${LAB_TOKEN}"},
"proxies": [
{"name":"lab-tcp","type":"tcp","localIP":"127.0.0.1","localPort":${BACKEND_PORT},"remotePort":${TCP_PORT}}
]
}
EOF
cp "${SRC_FRPC}" "${LAB}/frpc" && chmod +x "${LAB}/frpc"
echo "==> 裸 frpc 连接(验证 frp 协议通路)"
"${LAB}/frpc" -c "${LAB}/probe/frpc-probe.json" > "${LAB}/logs/frpc.log" 2>&1 &
sleep 4
grep -q 'login to server success' "${LAB}/logs/frpc.log" \
&& echo " frpc 登录成功" || { echo " frpc 登录失败"; tail -5 "${LAB}/logs/frpc.log"; }
printf " tcp ${TCP_PORT} -> "; curl -s -o /dev/null -w "%{http_code}\n" "http://127.0.0.1:${TCP_PORT}/"
pkill -f 'frplab/frpc' 2>/dev/null || true
# ---------- 4. webui4frpc 接管 ----------
echo "==> 启动 webui4frpc ${UI_ADDR}(独立 workdir)"
mkdir -p "${LAB}/ui/data/bin/frpc-${FRP_VER}"
cp "${SRC_FRPC}" "${LAB}/ui/data/bin/frpc-${FRP_VER}/frpc"
chmod +x "${LAB}/ui/data/bin/frpc-${FRP_VER}/frpc"
( setsid /opt/webui4frpc/webui4frpc -addr "${UI_ADDR}" \
-user "${UI_USER}" -password "${UI_PASS}" \
-workdir "${LAB}/ui/data" > "${LAB}/logs/ui.log" 2>&1 < /dev/null & )
sleep 5
ss -ltn | grep -q "${UI_ADDR##*:}" || { echo "webui4frpc 未监听"; exit 1; }
AUTH="${UI_USER}:${UI_PASS}"
echo "==> 注入画布(注意:loopback 会被自动改写为 LAN 地址)"
curl -s -u "${AUTH}" -X PUT "http://${UI_ADDR}/api/manager/canvas" \
-H 'Content-Type: application/json' \
-d "{\"remotes\":[{\"name\":\"lab-frps\",\"ip\":\"127.0.0.1\",\"port\":17000,\"token\":\"${LAB_TOKEN}\",\"enabled\":true}],
\"locals\":[{\"name\":\"lab-backend\",\"ip\":\"127.0.0.1\",\"port\":${BACKEND_PORT}}],
\"links\":[{\"local\":\"lab-backend\",\"remote\":\"lab-frps\",\"remotePort\":${UI_FORWARD_PORT}}]}" \
| python3 -m json.tool
echo "==> binaryPath 必须先写入 settings,否则 worker 静默不启动"
python3 - "${LAB}/ui/data/manager.db" "${LAB}/ui/data/bin/frpc-${FRP_VER}/frpc" <<'PY'
import json, sqlite3, sys
db, binary = sys.argv[1], sys.argv[2]
c = sqlite3.connect(db)
raw = c.execute("select value from settings where key='settings'").fetchone()[0]
d = json.loads(raw); d["binaryPath"] = binary
c.execute("update settings set value=? where key='settings'", (json.dumps(d),))
c.commit()
print(" binaryPath 已写入:", binary)
PY
echo "==> 启动 forward(非 localOnly 需 owner claim,观察 status)"
curl -s -u "${AUTH}" -X POST "http://${UI_ADDR}/api/manager/forwards/start" \
-H 'Content-Type: application/json' \
-d "{\"local\":\"lab-backend\",\"remote\":\"lab-frps\",\"remotePort\":${UI_FORWARD_PORT}}"
echo
sleep 5
echo "==> 观察结果"
curl -s -u "${AUTH}" "http://${UI_ADDR}/api/manager/status" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(' forwards:', [(f['local'], f['active']) for f in d.get('forwards',[])])
print(' profiles:', [(p['name'], p['process']['state'], p.get('connState')) for p in d.get('profiles',[])])
"
echo " binaryPath: $(curl -s -u "${AUTH}" "http://${UI_ADDR}/api/manager/binary/status")"
echo " configs/: $(ls "${LAB}/ui/data/configs/" 2>/dev/null | tr '\n' ' ')"
echo " workers: $(pgrep -af "${LAB}/ui/data" | wc -l) 个"
echo
echo "日志: ${LAB}/logs/"
echo "复现结论见 docs/how-it-works.md"