feat: docker cluster demo (frps+3 nodes, M6 auto binary pull, tunnel e2e) + fix SelfAddr/W4F_HOST/bootstrap retry, -frpc flag

This commit is contained in:
2026-08-17 21:57:00 +08:00
parent fd7604cb9c
commit a842198447
8 changed files with 238 additions and 2 deletions

21
deploy/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
# Build the Go binary with embedded web UI + demo backend.
FROM golang:1.25.0 AS build
WORKDIR /src
COPY go.mod go.sum vendor ./
COPY cmd ./cmd
COPY internal ./internal
# Vendored deps => fully offline build.
RUN CGO_ENABLED=0 go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/webui4frpc ./cmd/webui4frpc
# Static demo backend (tiny HTTP server).
COPY deploy/demo-backend ./demo-backend
RUN CGO_ENABLED=0 go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/backend ./demo-backend
# Runtime image: no frp binaries baked in (mounted via compose volume to keep
# layers small and build fast); static Go only.
FROM debian:bookworm-slim
WORKDIR /app
COPY --from=build /out/webui4frpc /app/webui4frpc
COPY --from=build /out/backend /app/backend
EXPOSE 7500 7000 7400 8080
ENTRYPOINT ["/app/webui4frpc"]

46
deploy/README.md Normal file
View File

@ -0,0 +1,46 @@
# webui4frpc Docker 集群演示
一键起一套 **frps + backend + 3×webui4frpc 节点**node-a 种子node-b/c 通过 `-peer` 加入并自动从邻居拉取 frpc 二进制)。
## 前置
- Docker 可用(无需 build用宿主编译产物挂载进容器
- 宿主机有 `/home/jianf/Program/frp/bin/{frpc,frps}`(或设置 `FRP=` 指向别的目录)
## 启动/停止
```bash
cd deploy
go build -o artifacts/webui4frpc ../cmd/webui4frpc # 首次/改代码后重建
go build -o artifacts/backend ./demo-backend
bash run-cluster.sh # 启动集群
bash run-cluster.sh stop # 停止
```
## 拓扑
```
┌──────────┐ ┌───────────────────────┐
│ w4f-frps │◄───│ node-a (7501, 种子) │
tunnel: │ 7000/7080/18081 ├── node-b (7502, peer a)│
http://127.0.0.1:18081 └──────────┘ └── node-c (7503, peer b)│
▼ └───────────────────────┘
w4f-backend (8080) ← frpc 转发
```
## 演示点
- **M6 二进制分发**node-a 预置 `bin/frpc-0.70.1/frpc`node-b/c 无 frpc入网后自动向邻居拉取并落盘缓存
- **M3 状态**node-a 配置 `adminPort`,状态页显示真实 per-proxy 状态running
- **隧道**node-a 画布 local=backend:8080 × remote=frps1frpc worker 启动后经 frps:18081 访问 backend
## 验证
```bash
# 二进制分发
curl -s -u admin:admin123 http://127.0.0.1:7502/api/manager/cluster/nodes
# 隧道
curl http://127.0.0.1:18081/ # -> hello from w4f demo backend
# 代理状态
curl -s -u admin:admin123 http://127.0.0.1:17401/api/status
```

88
deploy/compose.yaml Normal file
View File

@ -0,0 +1,88 @@
services:
frps:
image: w4f-demo:latest
container_name: w4f-frps
command: ["/app/frp/frps", "-c", "/app/frps.toml"]
volumes:
- ./frps.toml:/app/frps.toml:ro
- /home/jianf/Program/frp/bin:/app/frp:ro
ports:
- "7000:7000" # frps control
- "7080:7080" # vhostHTTPPort
networks:
- w4f
backend:
image: w4f-demo:latest
container_name: w4f-backend
command: ["/app/backend"]
ports:
- "8080:8080"
networks:
- w4f
node-a:
image: w4f-demo:latest
container_name: w4f-node-a
command:
- "-addr=0.0.0.0:7500"
- "-user=admin"
- "-password=admin123"
- "-workdir=/data"
- "-frpc=/app/frp/frpc"
volumes:
- ./node-a:/data
- /home/jianf/Program/frp/bin:/app/frp:ro
ports:
- "7501:7500"
- "17401:7400"
depends_on:
- frps
networks:
- w4f
node-b:
image: w4f-demo:latest
container_name: w4f-node-b
command:
- "-addr=0.0.0.0:7500"
- "-user=admin"
- "-password=admin123"
- "-workdir=/data"
- "-frpc=/app/frp/frpc"
- "-peer=node-a:7500"
volumes:
- ./node-b:/data
- /home/jianf/Program/frp/bin:/app/frp:ro
ports:
- "7502:7500"
depends_on:
- frps
- node-a
networks:
- w4f
node-c:
image: w4f-demo:latest
container_name: w4f-node-c
command:
- "-addr=0.0.0.0:7500"
- "-user=admin"
- "-password=admin123"
- "-workdir=/data"
- "-frpc=/app/frp/frpc"
- "-peer=node-b:7500"
volumes:
- ./node-c:/data
- /home/jianf/Program/frp/bin:/app/frp:ro
ports:
- "7503:7500"
depends_on:
- frps
- node-b
networks:
- w4f
networks:
w4f:
driver: bridge

View File

@ -0,0 +1,16 @@
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello from w4f demo backend (path=%s)", r.URL.Path)
})
_ = http.ListenAndServe("0.0.0.0:8080", nil)
}

16
deploy/frps.toml Normal file
View File

@ -0,0 +1,16 @@
# frps demo config for the cluster showcase.
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "demo-token"
# HTTP virtual host port (for http proxies)
vhostHTTPPort = 7080
webServer.addr = "0.0.0.0"
webServer.port = 7500
log.to = "/app/frps.log"
log.level = "info"
log.maxDays = 3

34
deploy/run-cluster.sh Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# w4f demo cluster: node-a seeds frpc cache, node-b/c auto-pull from neighbors (M6).
set -euo pipefail
cd "$(dirname "$0")"
FRP=${FRP:-/home/jianf/Program/frp/bin}
NET=w4f-demo
VER=0.70.1
docker network inspect $NET >/dev/null 2>&1 || docker network create $NET
# clean + seed node-a cache with real frpc binary
for d in node-a node-b node-c; do rm -rf data/$d 2>/dev/null || true; mkdir -p data/$d; done
mkdir -p data/node-a/bin/frpc-$VER
[ -x data/node-a/bin/frpc-$VER/frpc ] || cp $FRP/frpc data/node-a/bin/frpc-$VER/frpc
chmod +x data/node-a/bin/frpc-$VER/frpc
echo "== starting backend =="
docker run -d --name w4f-backend --user $(id -u):$(id -g) --network $NET --network-alias backend -p 18080:8080 -v $PWD/artifacts/backend:/app/backend:ro --restart unless-stopped --entrypoint /app/backend debian:bookworm-slim
echo "== starting frps =="
docker run -d --name w4f-frps --user $(id -u):$(id -g) --network $NET --network-alias frps -p 7000:7000 -p 7080:7080 -p 18081:18081 -v $FRP/frps:/app/frps:ro -v $PWD/frps.toml:/app/frps.toml:ro --restart unless-stopped --entrypoint /app/frps debian:bookworm-slim -c /app/frps.toml
echo "== starting node-a (seed, has frpc cache) =="
docker run -d --name w4f-node-a --user $(id -u):$(id -g) --network $NET --network-alias node-a -e W4F_HOST=node-a -p 7501:7500 -p 17401:7400 -v $PWD/artifacts/webui4frpc:/app/webui4frpc:ro -v $PWD/data/node-a:/data --restart unless-stopped --entrypoint /app/webui4frpc debian:bookworm-slim -addr=0.0.0.0:7500 -user=admin -password=admin123 -workdir=/data
echo "== starting node-b (no frpc; peer node-a) =="
docker run -d --name w4f-node-b --user $(id -u):$(id -g) --network $NET --network-alias node-b -e W4F_HOST=node-b -p 7502:7500 -v $PWD/artifacts/webui4frpc:/app/webui4frpc:ro -v $PWD/data/node-b:/data --restart unless-stopped --entrypoint /app/webui4frpc debian:bookworm-slim -addr=0.0.0.0:7500 -user=admin -password=admin123 -workdir=/data -peer=node-a:7500
echo "== starting node-c (no frpc; peer node-b) =="
docker run -d --name w4f-node-c --user $(id -u):$(id -g) --network $NET --network-alias node-c -e W4F_HOST=node-c -p 7503:7500 -v $PWD/artifacts/webui4frpc:/app/webui4frpc:ro -v $PWD/data/node-c:/data --restart unless-stopped --entrypoint /app/webui4frpc debian:bookworm-slim -addr=0.0.0.0:7500 -user=admin -password=admin123 -workdir=/data -peer=node-b:7500
echo "== cluster up =="
docker ps --filter "name=w4f-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"