frpsctl 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {frpsctl-0.2.0 → frpsctl-0.2.2}/.github/workflows/ci.yml +39 -0
- frpsctl-0.2.2/CHANGELOG.md +160 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/PKG-INFO +122 -15
- {frpsctl-0.2.0 → frpsctl-0.2.2}/README.md +121 -14
- {frpsctl-0.2.0 → frpsctl-0.2.2}/frpsctl-/350/256/276/350/256/241/346/226/271/346/241/210.md +416 -4
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/__init__.py +1 -1
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/cli/__init__.py +702 -127
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/cli/ui.py +68 -10
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/admin.py +129 -5
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/config.py +152 -14
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/doctor.py +54 -34
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/healthcheck.py +36 -2
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/instance.py +43 -9
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/lifecycle.py +59 -11
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/lock.py +4 -0
- frpsctl-0.2.2/src/frpsctl/core/logs.py +52 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/release.py +42 -17
- frpsctl-0.2.2/src/frpsctl/core/systemd.py +933 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/transaction.py +258 -79
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/audit.py +5 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/engine.py +46 -4
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/policy.py +78 -16
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/server.py +24 -26
- frpsctl-0.2.2/src/frpsctl/web/__init__.py +35 -0
- frpsctl-0.2.2/src/frpsctl/web/api.py +452 -0
- frpsctl-0.2.2/src/frpsctl/web/auth.py +142 -0
- frpsctl-0.2.2/src/frpsctl/web/server.py +339 -0
- frpsctl-0.2.2/src/frpsctl/web/static/index.html +631 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_cli.py +382 -5
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_facts.py +77 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_faults.py +7 -27
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_integration.py +287 -37
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_plugin.py +140 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/test_units.py +663 -11
- frpsctl-0.2.2/tests/test_web.py +547 -0
- frpsctl-0.2.0/CHANGELOG.md +0 -67
- frpsctl-0.2.0/src/frpsctl/core/systemd.py +0 -420
- {frpsctl-0.2.0 → frpsctl-0.2.2}/.github/workflows/release.yml +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/.gitignore +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/LICENSE +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/NOTICE +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/install.sh +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/pyproject.toml +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/__main__.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/cli/context.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/__init__.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/health.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/platform.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/schema.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/core/version.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/errors.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/__init__.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/quota.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/plugin/types.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/src/frpsctl/py.typed +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/__init__.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/conftest.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/fake_frps.py +0 -0
- {frpsctl-0.2.0 → frpsctl-0.2.2}/tests/faults.py +0 -0
|
@@ -85,6 +85,45 @@ jobs:
|
|
|
85
85
|
.venv/bin/frpsctl stop
|
|
86
86
|
.venv/bin/frpsctl status
|
|
87
87
|
|
|
88
|
+
- name: Web 管理台冒烟(后台起服务 + HTTP 全流程)
|
|
89
|
+
run: |
|
|
90
|
+
set -euo pipefail
|
|
91
|
+
export FRPSCTL_ROOT="$RUNNER_TEMP/smoke/instances"
|
|
92
|
+
export FRPSCTL_DATA_HOME="$RUNNER_TEMP/smoke/data"
|
|
93
|
+
WP=$(python3 -c "import socket;s=socket.socket();s.bind(('127.0.0.1',0));print(s.getsockname()[1])")
|
|
94
|
+
.venv/bin/frpsctl web serve --bind "127.0.0.1:$WP" --password smoke-pw &
|
|
95
|
+
WEB_PID=$!
|
|
96
|
+
sleep 2
|
|
97
|
+
python3 - "$WP" <<'EOF'
|
|
98
|
+
import json, sys, urllib.request
|
|
99
|
+
|
|
100
|
+
base = f"http://127.0.0.1:{int(sys.argv[1])}"
|
|
101
|
+
login = urllib.request.Request(
|
|
102
|
+
base + "/api/login", method="POST",
|
|
103
|
+
data=json.dumps({"password": "smoke-pw"}).encode(),
|
|
104
|
+
headers={"Content-Type": "application/json"},
|
|
105
|
+
)
|
|
106
|
+
with urllib.request.urlopen(login, timeout=10) as resp:
|
|
107
|
+
cookie = resp.headers["Set-Cookie"].split(";")[0]
|
|
108
|
+
csrf = json.load(resp)["csrf"]
|
|
109
|
+
|
|
110
|
+
def call(path):
|
|
111
|
+
req = urllib.request.Request(base + path, headers={"Cookie": cookie})
|
|
112
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
113
|
+
return json.load(resp)
|
|
114
|
+
|
|
115
|
+
status = call("/api/status")
|
|
116
|
+
assert status["state"] == "STOPPED", status
|
|
117
|
+
assert call("/api/session")["csrf"] == csrf
|
|
118
|
+
config = call("/api/config")
|
|
119
|
+
assert any(item["key"] == "bindPort" for item in config["entries"]), config
|
|
120
|
+
html = urllib.request.urlopen(base + "/", timeout=10).read().decode("utf-8")
|
|
121
|
+
assert "frpsctl 管理台" in html
|
|
122
|
+
print("web 冒烟通过:login / session / status / config / 静态页")
|
|
123
|
+
EOF
|
|
124
|
+
kill "$WEB_PID"
|
|
125
|
+
wait "$WEB_PID" 2>/dev/null || true
|
|
126
|
+
|
|
88
127
|
no-binary:
|
|
89
128
|
name: 无二进制降级路径(Python 3.13)
|
|
90
129
|
runs-on: ubuntu-latest
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
|
+
版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。
|
|
5
|
+
|
|
6
|
+
## [0.2.2] - 2026-09-17
|
|
7
|
+
|
|
8
|
+
Web 管理台(内置界面)与 `kick` 语义修正。
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- `frpsctl web serve`:内置 Web 管理台——仪表盘(状态 / 三层健康 / 7 天流量
|
|
13
|
+
柱状图 / 会话内实时曲线)、客户端与代理列表、日志面板、进程启停、配置编辑
|
|
14
|
+
(预览打码 diff → 应用 → 失败自动回滚 → 一键回滚)。单文件前端、零外部
|
|
15
|
+
资源(CSP `default-src 'none'`),默认只绑回环。
|
|
16
|
+
- `frpsctl web service install|uninstall|status`:Web 管理台的 systemd 集成
|
|
17
|
+
(生成 0600 口令文件并移交服务用户;口令明文不进 unit)。
|
|
18
|
+
- `frpsctl prune`:清理 dashboard 统计里的离线代理记录。
|
|
19
|
+
- 配置编辑的多键事务 `apply_sets` / `plan_set_many`:一次快照、一次重启,
|
|
20
|
+
带 CAS(预览之后文件被改 → 拒绝而不是覆盖)。
|
|
21
|
+
- 契约层新增 **C9**(代理写 API 的真实语义)与 **C10**(traffic 端点的"无数据
|
|
22
|
+
= 404"语义,Web 容错的前提),0.70.0 上同样成立。
|
|
23
|
+
|
|
24
|
+
### 修复
|
|
25
|
+
|
|
26
|
+
- **修正 `kick` 的语义错误**:frp 的 `DELETE /api/proxies` 实际是
|
|
27
|
+
`ClearOfflineProxies()`(只接受 `?status=offline`),**不存在**强制下线在线
|
|
28
|
+
代理的 API。原 `kick` 按"按 name 下线"实现该端点,真机永远返回 400——一个
|
|
29
|
+
从未工作过的功能(Web 端到端测试暴露)。已由 `prune` 取代。
|
|
30
|
+
|
|
31
|
+
### 工程
|
|
32
|
+
|
|
33
|
+
- 新增 78 条测试(357 → 435),覆盖率 82%。含 Web 层的全路由认证扫描(16 条)、
|
|
34
|
+
浏览器刷新恢复、TOML datetime 序列化、单代理故障注入等回归用例。
|
|
35
|
+
- core 下沉三处共用逻辑(消除重复实现):`parse_bind`(插件服务同时受益)、
|
|
36
|
+
`mask_value`(打码单点)、`core/logs.py`(`frpsctl log` 与 web 共用)。
|
|
37
|
+
- CI 新增 **Web 管理台冒烟**步骤(此前 CI 从不触碰 web):登录 → 会话恢复 →
|
|
38
|
+
状态 → 配置 → 静态页,全链路验证。
|
|
39
|
+
|
|
40
|
+
## [0.2.1] - 2026-09-17
|
|
41
|
+
|
|
42
|
+
第四轮全量迭代:并发根治、策略 fail-open 修复与便捷性命令。
|
|
43
|
+
|
|
44
|
+
### 新增
|
|
45
|
+
|
|
46
|
+
- `frpsctl instances [--health]`:多实例一行式概览(owner / 状态 / pid / 版本 / 健康)。
|
|
47
|
+
- `frpsctl clients` / `frpsctl proxies [--type]`:v2 Admin API 的客户端与代理列表
|
|
48
|
+
(自动翻页取全量,`--json` 可管道)。
|
|
49
|
+
- `frpsctl config list [--prefix] [--tree]`:列出全部配置键(值自动打码;
|
|
50
|
+
`--tree` 按表分组缩进展示)。
|
|
51
|
+
- `frpsctl plugin service install|uninstall|status`:插件服务的 systemd unit
|
|
52
|
+
(`Restart=always`,安装前四项体检:账户 / frpsctl 可达且不在家目录 / 策略文件 / 回环)。
|
|
53
|
+
- `frpsctl service logs [-f] [-n]`:journald 集成(unit 级日志)。
|
|
54
|
+
- `start`/`restart` 等待健康检查时输出**逐轮进度**(终端原地刷新,非终端按行
|
|
55
|
+
限流;`--json` 不输出进度);`install` 在终端上恢复 curl 下载进度条
|
|
56
|
+
(管道中自动静默)。
|
|
57
|
+
|
|
58
|
+
### 修复
|
|
59
|
+
|
|
60
|
+
- **并发(P0)**:`config set` 的候选生成(plan)移入实例锁内——两个并发变更不再
|
|
61
|
+
互相静默覆盖(实测复现:后写入者覆盖前者,两边都报成功);`config edit` 引入
|
|
62
|
+
锁内 CAS(编辑期间文件被并发修改 → 拒绝草稿并提示重新编辑);
|
|
63
|
+
`config rollback` / `config diff` 的读取同样入锁。
|
|
64
|
+
- **安全**:策略 JSON 的宽松转换曾是 fail-open——`"allow_unknown_user": "false"`
|
|
65
|
+
(字符串)被 `bool()` 判成 **True**,等于打开鉴权后门;`allow_random_port` 同理。
|
|
66
|
+
现改为严格类型(类型错误报 3 并指出字段名),并修掉 `"audit": "false"` 的裸
|
|
67
|
+
`AttributeError`。
|
|
68
|
+
- `--health-timeout` 对 systemd 实例生效(此前硬编码 10s,参数被静默忽略)。
|
|
69
|
+
- `same_config_active` 扫描全部 active service:自建 unit 指向同一配置不再漏检
|
|
70
|
+
(direct 与 systemd 双起防护补洞)。
|
|
71
|
+
- `status` 单次探测所有权(systemd 下从 4 个子进程降为 2 个,消除两次探测间的 TOCTOU)。
|
|
72
|
+
- 下载在 curl 失败时回退 urllib(此前兜底代码不可达);curl 进度不再被捕获。
|
|
73
|
+
- `config edit` / `verify` / `config diff` 在配置缺失时给出配置错误(3),而不是
|
|
74
|
+
未分类错误(1);`EDITOR="vim -u NONE"` 这类带参数写法可用(引号不配对归用法错误 2)。
|
|
75
|
+
- `reject_log_burst` / `reject_log_window` 真正生效:拒绝风暴期间审计限速,
|
|
76
|
+
被抑制的条数在 `suppressed` 字段如实汇报(此前是零引用的死配置)。
|
|
77
|
+
- `write_state` 走统一原子写(fsync + 随机临时名 + 保留属主)。
|
|
78
|
+
- `parse_listen`:`bindPort = 0` 实测回落默认 7000,不再当作"无监听"。
|
|
79
|
+
- `do_GET /healthz` 的 BrokenPipe 不再把回溯打进插件 stderr。
|
|
80
|
+
- "是否需要 `--allow-unsafe`" 收敛为单点判定(此前四份实现)。
|
|
81
|
+
- 死代码清理(`restart` 的无效 try、`policy.validate` 的空分支)。
|
|
82
|
+
- `doctor` 重复探测去重(`frps -v` 与 `resolve_owner` 各一次)。
|
|
83
|
+
- 回归 review 追加修复:`doctor` 对低于门槛的二进制给出**正确诊断**
|
|
84
|
+
("低于最低支持版本"而非"无法读取版本 / 可能不是官方 frps");systemd unit
|
|
85
|
+
渲染统一绝对化——`--root ./instances` / `--binary ./bin/frps` 这类相对输入
|
|
86
|
+
不再写出 `ExecStart=bin/frps` 的坏 unit。
|
|
87
|
+
|
|
88
|
+
- 回归 review 追加修复:展示层异常不再拖垮启动(`on_tick` 进度回调的异常被
|
|
89
|
+
隔离——`start 2>&1 | head` 场景曾把刚派生的 frps 误杀);`note` / `progress` /
|
|
90
|
+
`warn` / `trace` 在 stderr 管道断开时静默(不再二次崩溃);tty 进度补清行尾码
|
|
91
|
+
(消除残影)。
|
|
92
|
+
|
|
93
|
+
### 工程
|
|
94
|
+
|
|
95
|
+
- 新增 61 条回归用例(总计 357),覆盖率 83%。
|
|
96
|
+
- `init --bind-port` 加范围约束(`0` 归用法错误 2);`config set --json` 的 noop
|
|
97
|
+
路径输出 JSON(此前打印人读文本)。
|
|
98
|
+
|
|
99
|
+
## [0.2.0] - 2026-09-16
|
|
100
|
+
|
|
101
|
+
第三轮全量迭代:安全缺口、部署守护、稳定性与发布链路。
|
|
102
|
+
|
|
103
|
+
### 新增
|
|
104
|
+
|
|
105
|
+
- `start` / `restart` 在健康 gate(L1 ∧ L2)未通过时以**退出码 12** 收场并向
|
|
106
|
+
stderr 告警——此前完全静默(退出码 0,脚本会当成成功)。未过 gate 的进程
|
|
107
|
+
仍被托管:`status` 可见、`stop` 可停。
|
|
108
|
+
- `install --mirror URL`(可重复)与 `FRPSCTL_MIRROR` 环境变量:下载镜像可配置,
|
|
109
|
+
兑现"下载地址可被镜像替换"的承诺。
|
|
110
|
+
- `service install --user/--group`:systemd 服务账户可指定(默认 `frps`)。
|
|
111
|
+
- `status` 输出 `listen` 行(人读)与 `listen` 字段(JSON),对齐设计文档 §7.4。
|
|
112
|
+
- `status --watch --json` 改为单行 NDJSON,可被逐行消费(`jq -c` 等)。
|
|
113
|
+
- shell 补全(`--install-completion` / `--show-completion`)。
|
|
114
|
+
- PyPI 发布链路:版本号单一来源(hatchling 读 `__init__.py`)、release workflow、
|
|
115
|
+
CHANGELOG、`py.typed`。
|
|
116
|
+
|
|
117
|
+
### 修复
|
|
118
|
+
|
|
119
|
+
- **安全**:`config diff` / `edit` / `set` / `rollback` 的内联表与**跨行结构**
|
|
120
|
+
(三引号多行字符串、多行内联表/数组)机密不再明文泄露;解析失败但形似含机密
|
|
121
|
+
时保守打码;裸键名 `token`/`password`/`clientSecret` 同样命中。
|
|
122
|
+
- `config rollback` 每次只产生 **1** 份快照:此前 `rollback_to` 与 `apply_change`
|
|
123
|
+
各存一份完全相同的内容,10 份历史实际只够 5 次操作,且快照创建在实例锁外。
|
|
124
|
+
- 全局选项前移支持 `--` 终止符:`config set k -- --json` 的字面值不再被搬走。
|
|
125
|
+
- `service install` 前置体检:服务账户不存在、二进制对服务用户不可达、
|
|
126
|
+
日志目录不可写、**路径位于家目录(`ProtectHome=true`)**——这些过去都要等
|
|
127
|
+
`systemctl start` 才炸,且错误现场与安装动作相隔很远。
|
|
128
|
+
- unit 的 `ReadWritePaths` 增加**实例目录**:`ProtectSystem=strict` 下其余路径
|
|
129
|
+
只读,而 frp 默认要往实例目录写 `./frps.log`。
|
|
130
|
+
- `config set` 重建配置时**保留原属主**:systemd 部署把配置移交给服务用户后,
|
|
131
|
+
再次改配置不会再"夺回"属主导致 frps 读不到。
|
|
132
|
+
- `plugin serve` 的 SIGTERM handler 安装纳入 `try`:安装瞬间收到信号不再绕过
|
|
133
|
+
审计刷盘(此前 `finally` 尚未生效,进程直接死亡)。
|
|
134
|
+
- 配额检查按用户锁重构:dashboard 查询不再持全局锁,一个用户的慢查询不会串行
|
|
135
|
+
挂住所有用户的登录链路(frp 侧对插件 HTTP 客户端没有超时)。
|
|
136
|
+
- `install --with-frpc --only-download` 不再切换 frpc 软链,与 frps 语义统一。
|
|
137
|
+
- `log` 改为纯 Python tail:不依赖外部 `tail` 命令,支持日志轮转后自动重开新文件。
|
|
138
|
+
- `plugin init` 生成策略文件改用原子写(此前 `write_text` 存在半截文件与权限窗口)。
|
|
139
|
+
- 健康等待期间进程死亡时按启动失败收尾并清理 `state.json`(不再留下假 RUNNING)。
|
|
140
|
+
- 负数数值选项(`--interval` / `--timeout` / `-n` 等)归入用法错误(2):
|
|
141
|
+
此前 `--timeout -1` 会跳过等待直接 SIGKILL——参数笔误造成不可逆动作。
|
|
142
|
+
- `status --watch` 重定向到文件时不再写入 ANSI 清屏码。
|
|
143
|
+
|
|
144
|
+
### 工程
|
|
145
|
+
|
|
146
|
+
- CI 增加 `ruff check` 与 80% 覆盖率门禁;`[dev]` 补齐 `pytest-cov` / `ruff`。
|
|
147
|
+
- 新增 70 条回归用例(单元 / CLI / 集成 / 插件各层,总计 296 条)。
|
|
148
|
+
|
|
149
|
+
## [0.1.0] - 2026-09-15
|
|
150
|
+
|
|
151
|
+
首个版本。
|
|
152
|
+
|
|
153
|
+
- 生命周期:`install` / `init` / `start` / `stop` / `restart` / `status` / `log`,
|
|
154
|
+
含实例锁、三重进程身份校验、启动早退检测、三层健康判定。
|
|
155
|
+
- 配置闭环:`config get|set|edit|diff|rollback`,tomlkit 无损补丁、官方
|
|
156
|
+
`frps verify` 权威校验、原子写、快照历史、失败自动回滚。
|
|
157
|
+
- 运维面:`doctor` 体检与安全 lint、`service install` systemd 集成、`kick`。
|
|
158
|
+
- 服务端插件:多用户鉴权、端口白名单、代理名/类型约束、`max_proxies` 配额、
|
|
159
|
+
异步 JSONL 审计;fail-closed 与绑回环硬约束。
|
|
160
|
+
- 五层测试:单元 / 集成 / CLI / 契约(真二进制)/ 故障注入。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: frpsctl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: 把 frp 服务端(frps)包装成命令行工具:配置翻译器 + 进程保镖 + 状态聚合器
|
|
5
5
|
Project-URL: Homepage, https://github.com/ThzxxArt/frpsctl
|
|
6
6
|
Project-URL: Repository, https://github.com/ThzxxArt/frpsctl
|
|
@@ -41,13 +41,15 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
- [五分钟上手](#五分钟上手)
|
|
42
42
|
- [日常使用](#日常使用)
|
|
43
43
|
- [看状态](#看状态)
|
|
44
|
+
- [看客户端与代理](#看客户端与代理)
|
|
44
45
|
- [改配置](#改配置)
|
|
45
46
|
- [看日志](#看日志)
|
|
46
47
|
- [停下来](#停下来)
|
|
47
48
|
- [体检](#体检)
|
|
48
|
-
- [
|
|
49
|
+
- [清理离线记录](#清理离线记录)
|
|
49
50
|
- [多实例](#多实例)
|
|
50
51
|
- [服务端插件(多用户鉴权 + 端口白名单)](#服务端插件多用户鉴权-端口白名单)
|
|
52
|
+
- [Web 管理台](#web-管理台)
|
|
51
53
|
- [用 systemd 托管](#用-systemd-托管)
|
|
52
54
|
- [升级 frps](#升级-frps)
|
|
53
55
|
- [退出码(脚本化契约)](#退出码脚本化契约)
|
|
@@ -100,9 +102,8 @@ Description-Content-Type: text/markdown
|
|
|
100
102
|
## 安装
|
|
101
103
|
|
|
102
104
|
```bash
|
|
103
|
-
# 1) 装 Python
|
|
104
|
-
pipx install frpsctl #
|
|
105
|
-
# 或:pip install frpsctl / pip install -U frpsctl(升级)
|
|
105
|
+
# 1) 装 Python 侧(已发布到 PyPI)
|
|
106
|
+
pipx install frpsctl # 或:pip install frpsctl;升级:pip install -U frpsctl
|
|
106
107
|
|
|
107
108
|
# 2) 装 frps 二进制(从官方发布页下载,sha256 强校验)
|
|
108
109
|
frpsctl install
|
|
@@ -178,7 +179,7 @@ $ ./install.sh
|
|
|
178
179
|
注册全局命令
|
|
179
180
|
✓ 已注册:/home/u/.local/bin/frpsctl
|
|
180
181
|
自检
|
|
181
|
-
✓ 命令可用:frpsctl 0.2.
|
|
182
|
+
✓ 命令可用:frpsctl 0.2.2
|
|
182
183
|
|
|
183
184
|
frpsctl 安装完成
|
|
184
185
|
```
|
|
@@ -192,7 +193,7 @@ frpsctl 安装完成
|
|
|
192
193
|
| `--no-verify` | 跳过安装后自检 |
|
|
193
194
|
|
|
194
195
|
**反复运行即为升级**(会重新装依赖并重写命令)。源码用 `-e` 方式安装,因此改完
|
|
195
|
-
|
|
196
|
+
源码无需重装,命令立即生效。用 pipx / pip 安装的版本可分别用
|
|
196
197
|
`pipx upgrade frpsctl` / `pip install -U frpsctl` 升级。
|
|
197
198
|
|
|
198
199
|
建议开启 shell 补全(`frpsctl --install-completion`,支持 bash/zsh/fish)。
|
|
@@ -235,7 +236,7 @@ dashboard 端口(0 = 不启用,将失去状态聚合能力) [7500]:
|
|
|
235
236
|
```
|
|
236
237
|
|
|
237
238
|
**把这三项记下来**——`auth.token` 要填到每个 frpc 客户端,dashboard 口令用于
|
|
238
|
-
`
|
|
239
|
+
`prune` 与 Web 管理台等操作。
|
|
239
240
|
|
|
240
241
|
```console
|
|
241
242
|
$ frpsctl verify
|
|
@@ -393,6 +394,26 @@ $ frpsctl status --json
|
|
|
393
394
|
| `systemd` | 由 systemd 托管,`start`/`stop`/`restart` 委托 systemctl |
|
|
394
395
|
| `none` | 没有进程在跑,也没有 unit |
|
|
395
396
|
|
|
397
|
+
### 看客户端与代理
|
|
398
|
+
|
|
399
|
+
`status` 给的是总数;"谁在线、哪个代理在跑、各跑了多少流量"用这两条
|
|
400
|
+
(走 v2 Admin API,自动翻页取全量,`--json` 可管道给 `jq`):
|
|
401
|
+
|
|
402
|
+
```console
|
|
403
|
+
$ frpsctl clients
|
|
404
|
+
name user hostname online ip version
|
|
405
|
+
alice.f2a3e2edeef4a920 alice DESKTOP-S8A3AVK True 127.0.0.1 0.71.0
|
|
406
|
+
|
|
407
|
+
$ frpsctl proxies
|
|
408
|
+
name user type port phase conns traffic(in/out)
|
|
409
|
+
alice.alice-ssh alice tcp 6000 online 0 0 B / 0 B
|
|
410
|
+
|
|
411
|
+
$ frpsctl proxies --type http # 只看某类型
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`prune` 清理离线代理记录;在线代理无法从服务端强制下线(frp 没有该 API,
|
|
415
|
+
见"清理离线记录"一节)。
|
|
416
|
+
|
|
396
417
|
### 改配置
|
|
397
418
|
|
|
398
419
|
frps 没有热重载,所以"改配置"和"重启"是同一件事。`config set` 把它实现为一次
|
|
@@ -429,6 +450,9 @@ $ frpsctl config set maxPortsPerClient 30
|
|
|
429
450
|
|
|
430
451
|
```bash
|
|
431
452
|
frpsctl config set bindPort 8000 --no-restart # 只写不重启(输出会提示"尚未生效")
|
|
453
|
+
frpsctl config list # 列出全部键(值自动打码)
|
|
454
|
+
frpsctl config list --prefix webServer # 只看某张表
|
|
455
|
+
frpsctl config list --tree # 按表分组缩进展示
|
|
432
456
|
frpsctl config get bindPort # 读单键
|
|
433
457
|
frpsctl config get auth # 读整张表(机密自动打码)
|
|
434
458
|
frpsctl config get auth.token --reveal # 需要看原值时显式索取
|
|
@@ -462,11 +486,15 @@ $ frpsctl config set webServer.addr '"0.0.0.0"' # 这一步会让它变成完
|
|
|
462
486
|
frpsctl log # 最近 100 行
|
|
463
487
|
frpsctl log -n 500 # 最近 500 行
|
|
464
488
|
frpsctl log -f # 持续跟踪(tail -f)
|
|
489
|
+
frpsctl service logs -f # systemd 模式:unit 级日志(journalctl -u)
|
|
465
490
|
```
|
|
466
491
|
|
|
467
492
|
日志路径取自配置里的 `log.to`(相对路径按实例目录解析)。frpsctl **不写**这个
|
|
468
493
|
文件——它由 frp 自己写并按天轮转。多一个写入者会和轮转互相破坏。
|
|
469
494
|
|
|
495
|
+
`service logs` 看的是 **journald** 里的 unit 级日志(启动失败、OOM、权限拒绝
|
|
496
|
+
这类"frp 还没写进自己的日志文件"的问题),两者互补。
|
|
497
|
+
|
|
470
498
|
### 停下来
|
|
471
499
|
|
|
472
500
|
```bash
|
|
@@ -520,13 +548,16 @@ $ frpsctl doctor
|
|
|
520
548
|
|
|
521
549
|
**有 ERROR 时退出码为 1**,可直接接进 CI 或监控。
|
|
522
550
|
|
|
523
|
-
###
|
|
551
|
+
### 清理离线记录
|
|
524
552
|
|
|
525
553
|
```bash
|
|
526
|
-
frpsctl
|
|
554
|
+
frpsctl prune # 清理 dashboard 统计里的离线代理记录
|
|
527
555
|
```
|
|
528
556
|
|
|
529
|
-
|
|
557
|
+
⚠️ **frp 没有强制下线在线代理的 API**——`DELETE /api/proxies` 的实际语义是
|
|
558
|
+
`ClearOfflineProxies()`(只接受 `?status=offline`,源码与真机均已核实)。
|
|
559
|
+
要断开某个客户端请停掉它的 frpc。此前版本的 `kick` 基于对该端点的误读,
|
|
560
|
+
从未真正工作过,已由 `prune` 取代。
|
|
530
561
|
|
|
531
562
|
---
|
|
532
563
|
|
|
@@ -545,6 +576,17 @@ FRPSCTL_INSTANCE=web frpsctl status # 或长期用环境变量
|
|
|
545
576
|
|
|
546
577
|
优先级:`--instance` > `FRPSCTL_INSTANCE` > `default`。
|
|
547
578
|
|
|
579
|
+
一眼看全部实例(一行一个:owner / 状态 / pid / 版本 / 健康):
|
|
580
|
+
|
|
581
|
+
```console
|
|
582
|
+
$ frpsctl instances
|
|
583
|
+
default direct RUNNING (pid 10582, up 2m10s) frps 0.71.0 L1 process ok L2 control ok L3 plugin skipped
|
|
584
|
+
web systemd SYSTEMD_ACTIVE (pid 20041)
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
默认不做网络探测(快速);加 `--health` 会对运行中的实例跑三层健康检查。
|
|
588
|
+
`instances --json` 输出与 `status --json` 同构的数组。
|
|
589
|
+
|
|
548
590
|
服务端场景可把实例根目录放到 `/etc`:
|
|
549
591
|
|
|
550
592
|
```bash
|
|
@@ -645,6 +687,25 @@ localPort = 8080
|
|
|
645
687
|
remotePort = 6005 # 必须在自己被允许的范围内
|
|
646
688
|
```
|
|
647
689
|
|
|
690
|
+
### 用 systemd 守护(推荐)
|
|
691
|
+
|
|
692
|
+
`plugin serve` 是前台进程,而插件是**全部客户端登录的单点**且 fail-closed——
|
|
693
|
+
生产环境必须让它随系统自启、退出即拉起。工具直接生成 unit:
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
sudo frpsctl plugin service install # 体检 → 渲染 frpsctl-plugin@.service → enable
|
|
697
|
+
sudo frpsctl plugin service status
|
|
698
|
+
sudo frpsctl plugin service uninstall # 停用并移除
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
生成的 unit 要点:`Restart=always`、`ProtectSystem=strict`、`ReadWritePaths=<实例目录>`
|
|
702
|
+
(策略与审计都在那里)、`ExecStart=... --instance %i plugin serve --policy ...`。
|
|
703
|
+
|
|
704
|
+
安装前的体检与 frps 的 `service install` 同样严格,且多一条硬约束——绑定地址
|
|
705
|
+
必须是回环。另外 `frpsctl` 本身必须对服务用户可达且**不在家目录**:
|
|
706
|
+
`ProtectHome=true` 会挡住 `~/.local/bin`,pipx 用户请用
|
|
707
|
+
`sudo pipx install --global frpsctl`(或 `sudo pip install frpsctl`)。
|
|
708
|
+
|
|
648
709
|
### 审计
|
|
649
710
|
|
|
650
711
|
每次裁决写入 JSONL(默认 `./plugin-audit.jsonl`,可用 `audit.path` 改):
|
|
@@ -676,6 +737,51 @@ $ tail -1 plugin-audit.jsonl
|
|
|
676
737
|
|
|
677
738
|
---
|
|
678
739
|
|
|
740
|
+
## Web 管理台
|
|
741
|
+
|
|
742
|
+
内置的浏览器界面——不是 frp 自带 dashboard 的复刻,而是**含控制面**的管理台:
|
|
743
|
+
进程启停、配置编辑(预览 diff → 应用 → 失败自动回滚)、日志、7 天流量图。
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
frpsctl web serve # 默认只绑 127.0.0.1:8787
|
|
747
|
+
frpsctl web serve --bind 127.0.0.1:9000 # 换端口
|
|
748
|
+
FRPSCTL_WEB_PASSWORD=my-pw frpsctl web serve # 指定口令(默认自动生成并打印一次)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
```console
|
|
752
|
+
$ frpsctl web serve
|
|
753
|
+
Web 管理台:http://127.0.0.1:8787/
|
|
754
|
+
登录口令(仅显示这一次):Ih2x...(24 字符)
|
|
755
|
+
Ctrl-C 停止。
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
打开浏览器即可——单文件前端(暗色主题),零外部资源(不加载任何 CDN):
|
|
759
|
+
|
|
760
|
+
| 页面 | 内容 |
|
|
761
|
+
|------|------|
|
|
762
|
+
| 仪表盘 | 实例状态 / 三层健康 / 客户端与代理列表 / 近 7 天流量柱状图 / 会话内实时流量曲线 / 日志(5 秒自动刷新) |
|
|
763
|
+
| 配置 | 逐字段表单(敏感值不回显,留空表示不改)→ 预览打码 diff → 确认应用(一次事务、一次重启)→ 一键回滚 |
|
|
764
|
+
|
|
765
|
+
**安全设计**(比 frp 自带 dashboard 更严——它正是本项目安全决策的来源):
|
|
766
|
+
|
|
767
|
+
- 默认只绑回环;绑非回环必须显式 `--allow-non-loopback`(建议再套反向代理 + TLS);
|
|
768
|
+
- **不允许空口令**:自动生成(仅打印一次)或显式指定,会话 Cookie 带 `HttpOnly` + `SameSite=Strict`;
|
|
769
|
+
- 一切变更请求要求 `X-CSRF-Token`(登录时下发,仅存浏览器内存);
|
|
770
|
+
- 登录失败按来源限速(防爆破),错误口令与限速的响应完全一致;
|
|
771
|
+
- **配置原文绝不回显**:界面与 API 只返回打码值;要明文用 `config get --reveal`;
|
|
772
|
+
- 响应带 CSP(`default-src 'none'`)与 `no-store`。
|
|
773
|
+
|
|
774
|
+
用 systemd 托管(生成 0600 口令文件,unit 只引用路径,明文不落 unit):
|
|
775
|
+
|
|
776
|
+
```bash
|
|
777
|
+
sudo frpsctl web service install # 体检 → 渲染 frpsctl-web@.service → enable
|
|
778
|
+
sudo frpsctl web service status
|
|
779
|
+
sudo frpsctl web service uninstall
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
> `web service install` 同样需要 `frpsctl` 位于系统路径(不能被 `ProtectHome`
|
|
783
|
+
> 挡住)——与插件服务的部署要求一致。
|
|
784
|
+
|
|
679
785
|
## 用 systemd 托管
|
|
680
786
|
|
|
681
787
|
```bash
|
|
@@ -794,7 +900,7 @@ frpsctl install --version 0.71.0 && frpsctl restart
|
|
|
794
900
|
| 4 | 二进制缺失 / 不可执行 / 版本不受支持 | 未 install,或版本 `< 0.70.0` |
|
|
795
901
|
| 5 | 实例未运行 | `stop` 时无进程 |
|
|
796
902
|
| 6 | 实例已在运行 | 重复 `start` |
|
|
797
|
-
| 7 | dashboard 不可达 / 未启用 | `
|
|
903
|
+
| 7 | dashboard 不可达 / 未启用 | `clients` / `proxies` / `prune` 时 `webServer.port = 0`;v2 API 缺失 |
|
|
798
904
|
| 8 | 权限不足 | 需要 root 的操作 |
|
|
799
905
|
| 9 | 变更已自动回滚 | 配置写入后启动/健康检查失败,已恢复上一版 |
|
|
800
906
|
| 10 | 启动失败 / 进程停不下来 | 启动即退出(附 frp 原始报错);SIGKILL 后仍存在 |
|
|
@@ -827,6 +933,7 @@ esac
|
|
|
827
933
|
| `FRPSCTL_ADMIN_PASSWORD` | dashboard 口令(优先于配置文件) |
|
|
828
934
|
| `FRPSCTL_PLUGIN_POLICY` | 插件策略文件路径 |
|
|
829
935
|
| `FRPSCTL_MIRROR` | frps 下载镜像(逗号分隔;`install --mirror` 优先于它) |
|
|
936
|
+
| `FRPSCTL_WEB_PASSWORD` | Web 管理台登录口令(`web serve --password` 优先于它) |
|
|
830
937
|
| `FRPSCTL_TRACEBACK` | 设为 `1` 时打印完整回溯(排查未分类错误用) |
|
|
831
938
|
|
|
832
939
|
全局选项(写在子命令前后都可以):
|
|
@@ -1062,9 +1169,9 @@ FRPSCTL_TRACEBACK=1 frpsctl status
|
|
|
1062
1169
|
|
|
1063
1170
|
```bash
|
|
1064
1171
|
uv venv && uv pip install -e ".[dev]"
|
|
1065
|
-
.venv/bin/pytest # 全部
|
|
1066
|
-
.venv/bin/pytest -m "not contract" # 快速回归(
|
|
1067
|
-
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前
|
|
1172
|
+
.venv/bin/pytest # 全部 435 条(契约层缺二进制时自动 skip)
|
|
1173
|
+
.venv/bin/pytest -m "not contract" # 快速回归(405 条)
|
|
1174
|
+
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前 83%)
|
|
1068
1175
|
.venv/bin/ruff check src/ tests/ # 静态分析
|
|
1069
1176
|
```
|
|
1070
1177
|
|