# 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 ``` 打开 ,输入账号密码进入画布。首次使用可到「设置」页一键安装 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` → 重启后不自动重联。 ## 数据目录 ``` ├── manager.db # SQLite(locals / remotes / links / settings / users / api_keys) ├── configs/~~.json # 每条转发的独立 frpc 配置(per-forward 模型) ├── logs/~~.log # 每条转发的独立 worker 日志(按大小轮转) └── bin/frpc-/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 ```