Files
webui4frpc/README.md
JianFeeeee 21eb52df24 chore: v0.1.4 — 版本号收拢(6 处)
internal/version/version.go(ldflags 示例 + 默认值)、scripts/release.sh(注释 + 默认值)、
scripts/build_installers.sh、packaging/rpm/webui4frpc.spec(Version + changelog,含本次
负载均衡四条)、README.md 与 packaging/windows/README.md 的安装示例。

漏一处就会让某个安装包的示例文件名指向不存在的产物,所以按 v0.1.3 的 5 处清单逐个核对,
并补上 084ef62 才纳入的 packaging/windows/README.md,共 6 个文件。
2026-10-06 16:38:00 +08:00

332 lines
18 KiB
Markdown
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.

# webui4frpc
> **AI-assisted**: 本项目使用 AI 辅助编程(代码与文档由 AI 协作完成,经人工复核)。
一个**独立于 frp 源码**的可视化 frpc 控制器。用画布方式把「本地转发项」连到「远程服务器」,自动为每台远程服务器生成 frpc 配置并拉起独立 worker 进程,免去运维反复手写 frpc 配置的麻烦。支持多节点组成令牌环集群,协同分发转发任务。
> 本仓库**不捆绑、不包含任何 frp 源码**。frpc 可执行文件由用户一键从官方 GitHub Releases 下载,或手动指定路径。
## 特性
### 画布配置
- **Scratch 风格画布**:本地转发项(local)与远程服务器(remote)可视化连线,一个 local 可连多个 remote,一个 remote 可连多个 local
- **曲线连线 + 端口标签**:每条连线独立曲线,标签为远程端口,可拖拽调整曲线位置,端口可点开编辑
- **画布冲突检查**:保存时自动检测远程端口/域名重复,避免配置冲突
- **边开关与分组**:画布边上可一键禁用/启用单条转发,可打分组标签(状态页按分组管理、一键启停整组)
- **Del 键/× 按钮删除**:选中连线按 Delete 键或点击端口标签红 × 删除,确认后标记脏
- **一键生成配置**:保存画布即渲染每台 remote 的 frpc JSON 配置,自动拉起/重启对应 worker
- **画布导入导出**:支持 JSON 信封备份/恢复(含 provenance 元数据)
### frpc 能力
- **代理类型**:tcp(完整)、http/https(customDomains / subdomain / locations / basicAuth / headerRewrite)
- **传输参数**:useEncryption / useCompression / bandwidthLimit / poolCount / transport.protocol(tcp/quic/kcp/websocket)/ TLS
- **负载均衡与健康检查**:lbGroup + healthCheck 渲染输出;状态页通过 frpc admin API 拉取 per-proxy 真实状态(running / check failed / wait start …)
- **worker 自愈**:崩溃自动重启(指数退避),随 webui 启停
- **frpc 一键安装**:从官方 Releases 下载任意版本 frpc,或手动指定二进制路径
### 集群模式(令牌环)
- **令牌环协议**:若干 webui4frpc 节点组成协作网络;令牌按固定顺序传递,每节点持完整集群信息,单轮即全网收敛
- **负载最低者摘取**:新转发任务附加在令牌中,由负载最低的节点自行摘取并创建 frpc worker
- 主负载信号:当前节点拥有的转发数(权重 ×100)
- 次级信号:**NIC 网络接口利用率**(从 `/proc/net/dev` 增量 + `/sys/class/net/*/speed` 计算,排除了 lo/docker/veth 等虚拟接口)
- 同转发数节点间由内存占用 + 网络饱和度打破平局
- **负载均衡(自导式)**:每个节点对**自己的**转发跑同一条规则 —— 持有数超过
`ceil(总转发数 / 存活节点数)` 且至少 2 条时,自行撤销一条、把任务丢回环上,
再由环按「最闲者优先」重新认领
- **没有中心调度**:一节点的负载只有它自己的拓扑条目消失时才会下降,而条目是
本地簿记,没有对端能代它写,所以只能由持有者自己动手
- 规则自限:撤到平摊值即停(否则会把转发卸光),且低于下限永不撤、不会卸空节点
- 每节点默认 10 分钟冷却,限制抖动频率;默认 **dry-run**(只报告不释放)
- 集群页每节点一个开关:关闭 / 观察模式 / 启用,可持久化
- **per-forward 独立进程**:每条转发拥有独立的 frpc 配置文件和 frpc 进程(worker 键 = `local~remote~port`),单条转发故障不影响兄弟,消除旧模型的多节点 `proxy already exists` 冲突
- **容错自愈**:leader / 非 leader 节点宕机 → 自动检测 → 环跳过死亡节点、任务重挂;leader 宕机 → 上邻居晋升为新 leader
- **宕机自动重联**:节点宕机重启后通过缓存的集群拓扑(对端地址 + 准入密钥)自动重联,显式脱离集群的节点不自动重联
- **准入密钥**:每节点有 nodeKey,新节点加入需提供对端密钥;密钥随令牌环交换,全节点互通
- **集群二进制分发**:节点间优先互传 frpc 二进制,外部 URL 兜底
### 认证与权限
- **Session-cookie 登录 + Basic Auth**:UI 使用 session-cookie(支持登出),API 保持 Basic Auth + Bearer API Key 兼容
- **三级角色**:superadmin(账号管理)、admin(全功能除账号)、viewer(只读状态/集群/审计导出)
- **用户管理**:superadmin 角色创建/管理用户,bcrypt 存储,flag 内置账号为 system 只读
- **API Key**:read / write / admin 三级 scope,`w4f_` 前缀,sha256 存储,创建时明文仅展示一次
- **审计导出**:read 级 view 即可导出 CSV(账号登录记录 / API Key 使用记录 / 集群操作日志 / worker 运行日志)
## 构建
### 后端(Go 1.25+)
```bash
go build -o webui4frpc ./cmd/webui4frpc
```
### 前端(Node 20+)
```bash
cd web && npm install && npm run build
```
前端产物在 `web/dist`,需拷贝到 `internal/httpapi/dist` 以便 Go `embed` 打包进单一二进制:
```bash
rm -rf internal/httpapi/dist && cp -r web/dist internal/httpapi/dist
```
一键全量构建(dist 不进入 git,编译前必须构建前端):
```bash
cd web && npm run build && rm -rf internal/httpapi/dist && cp -r web/dist internal/httpapi/dist && go build -o webui4frpc ./cmd/webui4frpc
```
## 安装
从 [Release 页](https://gitcode.com/JianFeeeee/webui4frpc/releases/latest) 下载对应平台的安装包。
### Linux (Debian / Ubuntu)
```bash
sudo dpkg -i webui4frpc-0.1.4-linux-amd64.deb # 或 linux-arm64.deb
sudo nano /etc/webui4frpc/webui4frpc.env # 改掉默认密码
sudo systemctl restart webui4frpc
```
### Linux (RHEL / Fedora / openEuler / 默认 / 龙蜥)
```bash
sudo rpm -i webui4frpc-0.1.4-linux-x86_64.rpm
sudo vi /etc/webui4frpc/webui4frpc.env
sudo systemctl enable --now webui4frpc
```
安装后自动建 `webui4frpc` 系统账号、注册 systemd 单元,数据目录在 `/var/lib/webui4frpc`。
服务以非 root 运行,并启用 `ProtectSystem=full` / `NoNewPrivileges` 等加固项。
### Windows
双击 `webui4frpc-0.1.4-windows-amd64-setup.exe`(ARM 设备用 `windows-arm64-setup.exe`)。
安装向导会询问监听地址与管理员凭据,完成后:
- 程序装到 `C:\Program Files\webui4frpc`
- 开始菜单生成启动快捷方式与卸载项
- 自动添加防火墙入站规则
### macOS
```bash
sudo installer -pkg webui4frpc-0.1.4-darwin-arm64.pkg -target / # Apple Silicon
sudo installer -pkg webui4frpc-0.1.4-darwin-amd64.pkg -target / # Intel
```
或直接双击 `.pkg` 走图形安装器。安装后 launchd 开机自启,配置在
`/usr/local/etc/webui4frpc/webui4frpc.env`,改完执行:
```bash
sudo launchctl kickstart -k system/com.jianfeeeee.webui4frpc
```
> 该 `.pkg` 未经 Apple 公证(notarization)。首次打开若被 Gatekeeper 拦下,
> 在「系统设置 → 隐私与安全」选择仍要打开。
### 免安装(tar.gz / zip)
不想装服务只想跑一下,用对应的 `.tar.gz` / `.zip`,解压即用:
```bash
tar xzf webui4frpc-0.1.4-linux-amd64.tar.gz
./webui4frpc -addr 127.0.0.1:7500 -user admin -password 你的密码 -workdir ./data
```
下载后可用 `SHA256SUMS` 校验完整性:
```bash
sha256sum -c SHA256SUMS --ignore-missing
```
## 运行
从源码构建后直接跑:
```bash
./webui4frpc -addr 127.0.0.1:7500 -user admin -password admin123 -workdir ./data
```
打开 <http://127.0.0.1:7500/> ,输入账号密码进入画布。首次使用可到「设置」页一键安装 frpc(或手动指定路径),然后回到画布创建 local / remote 并连线、保存。
### 参数
| 参数 | 默认 | 说明 |
| --- | --- | --- |
| `-addr` | `127.0.0.1:7500` | 监听地址 |
| `-user` / `-password` | `admin` / `admin` | Basic 认证(自动同步为 system 管理员账号) |
| `-workdir` | `./webui-frpc` | 数据目录(db / 配置 / 日志 / bin) |
| `-bin` | 空 | 初始 frpc 二进制路径(可选,首次启动写入 settings) |
| `-frpc` | 空 | worker 默认 frpc 路径(settings 无值时的回退) |
| `-peer` | 空 | 集群对端地址 `host:port`(可重复,仅首次引导加入用) |
| `-join-key` | `$W4F_JOIN_KEY` | 集群准入密钥 = 对端节点的 nodeKey(与 `-peer` 配合使用) |
环境变量 `W4F_HOST` 可覆盖节点对外可达的主机名(Docker DNS / 通配监听场景用)。
### 负载均衡参数
每节点独立生效;集群页的开关**优先于**这里的默认值(在页面上选过就以页面为准,重启不丢)。
| 环境变量 | 默认 | 说明 |
| --- | --- | --- |
| `W4F_BALANCE_MODE` | `dry-run` | `off` 不参与 / `dry-run` 只报告 / `on` 真正释放 |
| `W4F_BALANCE_COOLDOWN` | `600` | 同一节点两次释放之间的最小秒数 |
| `W4F_BALANCE_INTERVAL` | `10` | 检查间隔(秒) |
**释放会停掉该转发的 frpc worker,其上的在途连接会断一次**,由新节点认领后恢复;
因此默认是 `dry-run`。集群页的开关走 write 级权限。
### 启动行为
节点启动时的集群引导优先级:
1. **缓存拓扑优先**:读取持久化的 `ClusterPeers`(宕机前最后令牌周期保存的对端列表),逐个尝试 rejoin
2. **`-peer` 引导**:无缓存拓扑时,用 `-peer` + `-join-key` 加入指定对端(10s 重试、5min 超时)
3. **新建集群**:无缓存无 `-peer` → `CreateCluster` 成为独立 leader
显式脱离集群(detachAsStandalone)会清除 `ClusterPeers` → 重启后不自动重联。
## 数据目录
```
<workdir>
├── manager.db # SQLite(locals / remotes / links / settings / users / api_keys)
├── configs/<local>~<remote>~<port>.json # 每条转发的独立 frpc 配置(per-forward 模型)
├── logs/<local>~<remote>~<port>.log # 每条转发的独立 worker 日志(按大小轮转)
└── bin/frpc-<v>/frpc # 一键安装的 frpc
```
## 架构
```
webui4frpc(单二进制,无 frp 依赖,前端 go:embed)
├── cmd/webui4frpc 入口:flag 解析 → store/process/cluster 装配 → HTTP 服务 + 生命周期
├── internal/
│ ├── store SQLite 持久化(locals / remotes / links / settings / users / api_keys / sessions)
│ ├── canvas 画布模型(local / remote / link 多对多)
│ ├── render 渲染 frpc JSON 配置(per-forward 单 proxy + 高级参数 + LB/健康检查)
│ ├── process worker 进程管理(spawn/stop/restart/自愈/日志轮转,worker 键 = local~remote~port)
│ ├── install 一键下载官方 frpc(GitHub Releases,含解压安全防护)
│ ├── cluster 令牌环协议(ring engine / leader 选举 / 容错 / 宕机重联 / 二进制分发 / NIC 负载采样 / 自导式负载均衡)
│ └── httpapi REST API + 静态资源(auth/session / canvas / forwards / cluster / users / audit 导出)
└── web/ Vue 3 SPA(Element Plus + VueFlow 画布 + Pinia)
└── src/views/ 状态总览 / 连接配置(画布)/ 集群(含负载均衡开关)/ 设置 / 账号与密钥
```
## API
所有接口前缀 `/api/manager`。UI 使用 session-cookie 登录(`/login` `/logout`),API 兼容 Basic Auth 或 Bearer API Key。权限分 read / write / admin 三级,角色 superadmin / admin / viewer 映射到不同 UI 可见性与操作能力。
| 类别 | 方法 | 路径 | 权限 | 说明 |
| --- | --- | --- | --- | --- |
| **状态** | GET | `/status` | read | 总览(版本/服务/节点/转发/profiles) |
| **画布** | GET/PUT | `/canvas` | read/write | 读写完整画布 |
| | GET | `/canvas/export` | read | 导出画布备份 |
| | POST | `/canvas/import` | write | 导入画布 |
| **转发** | POST | `/forwards/start\|stop` | write | 启停单条转发 |
| | POST | `/forwards/group/start\|stop` | write | 启停整组 |
| | POST | `/forwards/assign` | write | 修改转发分组 |
| | POST | `/forwards/group/delete` | write | 删除分组 |
| **单资源** | PUT/DELETE | `/locals[/{name}]` | write | 增删 local |
| | PUT/DELETE | `/remotes[/{name}]` | write | 增删 remote |
| | POST/DELETE | `/links[/{id}]` | write | 增删 link |
| **Profile** | POST | `/profiles/{name}/start\|stop\|restart` | write | worker 启停重启 |
| | GET | `/profiles/{name}/config\|logs` | read | 配置/日志 |
| **设置** | GET/PUT | `/settings` | read/write | 运行策略 |
| | GET | `/binary/status` | read | frpc 路径 |
| | POST | `/binary/install` | write | 安装 frpc |
| **集群** | GET | `/cluster/ring` | read | 令牌环快照 |
| | GET | `/cluster/balance` | read | 本节点负载均衡视图(只读,不变更状态) |
| | POST | `/cluster/balance/mode` | write | 切换本节点负载均衡模式(持久化) |
| | POST | `/cluster/create` | write | 创建独立集群 |
| | POST | `/cluster/join-ring` | write | 加入集群(本节点发起) |
| | POST | `/cluster/join` | write | 接收新节点加入 |
| | POST | `/cluster/task` | write | 提交转发任务到环 |
| | POST | `/cluster/node-remove` | write | 移除节点 |
| | POST | `/cluster/token` | write | 令牌中继 + 心跳 |
| | GET | `/cluster/nodes` | read | 集群节点 + 缓存 |
| | GET/POST | `/cluster/cache` | read/write | 二进制缓存管理 |
| | GET | `/node/logs` | read | 本节点 worker 日志 |
| | GET | `/cluster/logs/export` | read | 导出全节点 worker 日志(JSON bundle) |
| **审计导出** | GET | `/audit/users.csv` | read | 账号清单 CSV(含最后登录) |
| | GET | `/audit/apikeys.csv` | read | API Key 清单 CSV(含最后使用/过期) |
| | GET | `/audit/cluster-log.csv` | read | 集群操作日志 CSV(时间线 + 原始载荷) |
| | GET | `/audit/worker-logs.csv` | read | 全节点 worker 运行日志 CSV(逐行展开) |
| | GET | `/frpc/{version}` | read | 节点间 frpc 二进制分发 |
| **认证** | POST | `/login` | 无 | 登录(表单凭证 → session cookie) |
| | POST | `/logout` | 无 | 登出(清除 session) |
| | GET | `/me` | read | 当前身份 |
| | GET/POST | `/users` | admin | 用户列表/创建 |
| | PUT/DELETE | `/users/{name}` | admin | 修改/删除用户 |
| | GET/POST | `/apikeys` | admin | API Key 列表/创建 |
| | DELETE | `/apikeys/{id}` | admin | 吊销 API Key |
> 集群管理 API 的详细说明(请求体、响应格式、错误码、示例)见 [docs/cluster-api.md](docs/cluster-api.md)。
## 集群模式
多个 webui4frpc 节点可组成令牌环集群协同工作:
- **创建集群**:首个节点 `CreateCluster` 成为 leader,生成准入密钥(nodeKey)
- **加入集群**:在集群页复制对端的 nodeKey,填入「加入集群」对话框(对端地址 + 密钥)
- **任务分发**:在任意节点画布上配置 `localOnly=false` 的转发并保存 → 画布差异判断生成命令 → 令牌携带 → 负载最低节点摘取 → 拉起 frpc worker
- **容错**:节点宕机 → 自动检测 → 环自愈(跳过死亡节点 / leader failover);重启 → 自动重联
- **退出集群**:在集群页「退出集群」→ 本节点脱离为 standalone,不自动重联
- **负载均衡**:见下节
集群页展示:环拓扑(self/leader 标记)、令牌轮次、节点负载、待办任务、活跃转发拓扑(含 owner 归属)、负载均衡(本机份额与释放计划)、增量事件日志。
### 负载均衡(自导式释放)
**为什么不是 leader 统一调度**:一节点的负载信号是它持有的转发数,而这个数只在
**它自己的拓扑条目消失**时才下降 —— 条目是本地簿记。所以 leader 从外面只能*看见*
失衡,无法*消除*它;要消除就得伸手改另一个节点的簿记,而这正是这个环到处都在避免
的跨节点写(撤销、重启同样都是定向往 owner)。
于是规则由每个节点对自己的转发本地执行:
```
当 持有转发数 >= 2 且 持有转发数 > ceil(总转发数 / 存活节点数)
则 撤销其中一条 → 删除条目 → 以启用状态重新发布到环 → 由最闲节点认领
```
- **自限**:撤到平摊值即停。3 条分 3 台时,持有 2 条的那台卸一条后变成 1 条,
`1 > 1` 不成立 → 停手。没有这个上界,owner 会把转发全部卸光。
- **不会卸空**:低于下限 2 条永不撤销。
- **不碰已停用的转发**:停用条目的 entry 是用户「停用」决定的载体,释放它等于把
一条**已停**的转发当活干交给别人。
- **释放 ≠ 停止**:用户停止保留条目并标记链路为停用;均衡释放**删除条目**且链路保持
启用。两者不能混用 —— 复用停止的话,每个节点的重复认领守卫都会拒绝这条已是「有主」
的转发,它会被永久搁死在刚放弃它的那台机器上。
三种模式(集群页每节点一个开关,也可用 `W4F_BALANCE_MODE` 设默认值):
| 模式 | 行为 |
| --- | --- |
| `off` | 不参与均衡 |
| `dry-run` | 只报告「将释放哪条」,不动任何东西(默认) |
| `on` | 真正释放 |
只读接口 `GET /cluster/balance` 返回本节点视角:`selfForwards` / `share` /
`totalForwards` / `aliveNodes` / `selfOverShare`、`wouldRelease`、`releasing`、
`blockedBy`(为何暂不动的机器原因)、`mode` / `modeSource` / `canControl`。
该接口**不改变任何状态** —— 反复轮询不会启动、延迟或取消一次释放。
## 测试
```bash
# 后端
go test ./...
# 单包(负载均衡规则与状态机)
go test ./internal/cluster/ -run TestShare -v
# 前端类型检查
cd web && npm run build
```