frpsctl 0.2.2__tar.gz → 0.2.4__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.2 → frpsctl-0.2.4}/.github/workflows/ci.yml +25 -2
- {frpsctl-0.2.2 → frpsctl-0.2.4}/.github/workflows/release.yml +8 -1
- {frpsctl-0.2.2 → frpsctl-0.2.4}/CHANGELOG.md +122 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/PKG-INFO +73 -10
- {frpsctl-0.2.2 → frpsctl-0.2.4}/README.md +72 -9
- {frpsctl-0.2.2 → frpsctl-0.2.4}/frpsctl-/350/256/276/350/256/241/346/226/271/346/241/210.md +240 -32
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/__init__.py +1 -1
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/cli/__init__.py +462 -31
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/cli/ui.py +14 -21
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/admin.py +15 -2
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/config.py +119 -16
- frpsctl-0.2.4/src/frpsctl/core/diagnostics.py +38 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/doctor.py +29 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/health.py +8 -2
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/lifecycle.py +54 -24
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/lock.py +1 -7
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/release.py +1 -2
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/systemd.py +11 -3
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/transaction.py +184 -10
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/version.py +1 -4
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/server.py +7 -25
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/web/api.py +155 -22
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/web/auth.py +37 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/web/server.py +40 -2
- frpsctl-0.2.4/src/frpsctl/web/static/index.html +984 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_cli.py +519 -0
- frpsctl-0.2.4/tests/test_docs.py +174 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_facts.py +12 -5
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_integration.py +179 -1
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_units.py +322 -2
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_web.py +354 -2
- frpsctl-0.2.4/tests/test_web_frontend.py +137 -0
- frpsctl-0.2.2/src/frpsctl/web/static/index.html +0 -631
- {frpsctl-0.2.2 → frpsctl-0.2.4}/.gitignore +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/LICENSE +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/NOTICE +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/install.sh +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/pyproject.toml +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/__main__.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/cli/context.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/__init__.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/healthcheck.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/instance.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/logs.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/platform.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/core/schema.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/errors.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/__init__.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/audit.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/engine.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/policy.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/quota.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/plugin/types.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/py.typed +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/src/frpsctl/web/__init__.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/__init__.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/conftest.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/fake_frps.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/faults.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_faults.py +0 -0
- {frpsctl-0.2.2 → frpsctl-0.2.4}/tests/test_plugin.py +0 -0
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
strategy:
|
|
19
19
|
fail-fast: false
|
|
20
20
|
matrix:
|
|
21
|
-
python-version: ["3.11", "3.12", "3.13"]
|
|
21
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
22
22
|
|
|
23
23
|
steps:
|
|
24
24
|
- uses: actions/checkout@v4
|
|
@@ -112,14 +112,37 @@ jobs:
|
|
|
112
112
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
113
113
|
return json.load(resp)
|
|
114
114
|
|
|
115
|
+
def post(path, body):
|
|
116
|
+
req = urllib.request.Request(
|
|
117
|
+
base + path, method="POST",
|
|
118
|
+
data=json.dumps(body).encode(),
|
|
119
|
+
headers={"Content-Type": "application/json", "Cookie": cookie, "X-CSRF-Token": csrf},
|
|
120
|
+
)
|
|
121
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
122
|
+
return json.load(resp)
|
|
123
|
+
|
|
115
124
|
status = call("/api/status")
|
|
116
125
|
assert status["state"] == "STOPPED", status
|
|
117
126
|
assert call("/api/session")["csrf"] == csrf
|
|
118
127
|
config = call("/api/config")
|
|
119
128
|
assert any(item["key"] == "bindPort" for item in config["entries"]), config
|
|
129
|
+
history = call("/api/config/history")
|
|
130
|
+
assert isinstance(history["entries"], list), history
|
|
131
|
+
# 端到端冒烟里的 config set 会留下快照:history 必须真的读得到
|
|
132
|
+
# (只断言"是列表"的话,空列表也能骗过这层守卫)。
|
|
133
|
+
assert any(str(e.get("action", "")).startswith("set ") for e in history["entries"]), history
|
|
134
|
+
# 回滚前的"看差异":先经 API 改一个键(实例未运行 → 只写不重启),
|
|
135
|
+
# 再断言差里真的出现它。
|
|
136
|
+
preview = post("/api/config/preview", {"changes": [["maxPortsPerClient", "35"]]})
|
|
137
|
+
applied = post("/api/config/apply", {"preview_id": preview["preview_id"]})
|
|
138
|
+
assert applied["applied"] is True, applied
|
|
139
|
+
diff = call("/api/config/history/1/diff")
|
|
140
|
+
assert "maxPortsPerClient" in diff["diff"], diff
|
|
141
|
+
favicon = urllib.request.urlopen(base + "/favicon.ico", timeout=10)
|
|
142
|
+
assert favicon.status == 204
|
|
120
143
|
html = urllib.request.urlopen(base + "/", timeout=10).read().decode("utf-8")
|
|
121
144
|
assert "frpsctl 管理台" in html
|
|
122
|
-
print("web 冒烟通过:login / session / status / config / 静态页")
|
|
145
|
+
print("web 冒烟通过:login / session / status / config / history / diff / favicon / 静态页")
|
|
123
146
|
EOF
|
|
124
147
|
kill "$WEB_PID"
|
|
125
148
|
wait "$WEB_PID" 2>/dev/null || true
|
|
@@ -51,7 +51,14 @@ jobs:
|
|
|
51
51
|
|
|
52
52
|
wheel = next(pathlib.Path("dist").glob("*.whl"))
|
|
53
53
|
names = set(zipfile.ZipFile(wheel).namelist())
|
|
54
|
-
required = {
|
|
54
|
+
required = {
|
|
55
|
+
"frpsctl/py.typed",
|
|
56
|
+
"frpsctl/cli/__init__.py",
|
|
57
|
+
"frpsctl/core/lifecycle.py",
|
|
58
|
+
"frpsctl/core/diagnostics.py",
|
|
59
|
+
# 前端是 package data:丢它 = 管理台页面 500,而 CLI 测试全绿
|
|
60
|
+
"frpsctl/web/static/index.html",
|
|
61
|
+
}
|
|
55
62
|
missing = required - names
|
|
56
63
|
if missing:
|
|
57
64
|
raise SystemExit(f"wheel 缺少文件:{sorted(missing)}")
|
|
@@ -3,6 +3,128 @@
|
|
|
3
3
|
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## [0.2.4] - 2026-09-17
|
|
7
|
+
|
|
8
|
+
Web 管理台的体验迭代:把后端已有的能力全部交付到界面,并给单文件前端补上
|
|
9
|
+
自动化守卫。修复 5 条真实缺陷(含一条"白屏而 CI 全绿"的测试盲区)。
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
- **前端在 CI 里几乎零防护**:所有测试与冒烟都只走 HTTP、不执行 JS——一处
|
|
14
|
+
语法错误会让整站白屏而 CI 全绿。新增 `tests/test_web_frontend.py`:逐
|
|
15
|
+
`<script>` 块 `node --check`(无 node 时 skip)+ 静态纪律守卫(禁 innerHTML
|
|
16
|
+
家族、禁外部资源引用、CSS 变量双向对齐、亮色主题必须覆盖全部颜色变量)。
|
|
17
|
+
- **配置页切换视图会丢失未保存的修改**:每次切回配置页都重新拉取并清空草稿。
|
|
18
|
+
现在已加载过就不重载,"重新加载(丢弃修改)"是显式动作(有未保存修改时
|
|
19
|
+
二次确认)。
|
|
20
|
+
- **日志面板永远自动滚底**:`logScroll` 恒为 true,用户向上翻日志会被 5 秒
|
|
21
|
+
刷新拽回底部。现在按真实滚动位置维护"跟随/暂停"(显示在标题栏,回到底部
|
|
22
|
+
自动恢复)。
|
|
23
|
+
- **清理离线记录后不刷新列表**:用户看到条目还在,以为操作没生效。
|
|
24
|
+
- **认证会话表无上限**:持有口令的调用方反复登录可持续推高内存——与失败
|
|
25
|
+
来源表同类的"输入驱动的表必须有界",现在上限 32(驱逐最早到期者)。
|
|
26
|
+
- 发布前回归 review 追加修复:**`plan_change_many` 把字符串当列表逐字符迭代**
|
|
27
|
+
(`unsets="ab"` 会静默删掉 `a` 与 `b` 两个键;对抗性实测复现,现为用法错误,
|
|
28
|
+
输入归一化单点化在 core 并覆盖 `changes`/`unsets` 两种形状);`snapshot_diff`
|
|
29
|
+
与 `rollback_to` 的 `steps < 1` 曾被 `max(0, steps-1)` 静默归一成"一步"
|
|
30
|
+
(core 入口补防御,与 CLI 的 min=1 同一条纪律);代理柱状图的柱宽 clamp
|
|
31
|
+
(防御"负宽度静默不渲染")。
|
|
32
|
+
|
|
33
|
+
### 新增
|
|
34
|
+
|
|
35
|
+
- **回滚前"查看差异"**:新接口 `GET /api/config/history/{steps}/diff`(打码,
|
|
36
|
+
与 CLI `config diff --steps` 共用 core 的 `snapshot_diff`)——回滚从盲操作
|
|
37
|
+
变为可预览;历史表每行都有"查看差异"按钮。
|
|
38
|
+
- **Web 配置删除键**:配置表单每行"删除"按钮 → 与修改合并成一次事务
|
|
39
|
+
(`plan_change_many` + `apply_sets(unsets=…)`:一份快照、一次重启);
|
|
40
|
+
"删除不存在的键"照旧是配置错误。
|
|
41
|
+
- **Web 配置新增键**:此前只能改已有键,新增必须回到 CLI;现在表单底部可
|
|
42
|
+
添加任意键(预览/校验/危险组合拦截与 CLI 同一套)。
|
|
43
|
+
- **单代理流量曲线**:点击代理行展开该代理的 7 天曲线(数据源与 CLI
|
|
44
|
+
`traffic <name>` 相同)。
|
|
45
|
+
- **操作进行中状态**:启动/重启/停止/回滚/应用期间按钮禁用、状态徽章显示
|
|
46
|
+
"操作中…"(启动最长等 10 秒,此前完全无反馈);轮询与手动刷新不会叠加。
|
|
47
|
+
- **状态面板补齐**:state.json 损坏红色横幅(含处置指引)、0.70.x 版本告警、
|
|
48
|
+
L3 插件告警(`plugin_warning` 此前没有下发)、systemd unit 行、流量超 50
|
|
49
|
+
个代理时"已截断"提示(CLI 有、Web 曾静默)。
|
|
50
|
+
- **双主题**:`prefers-color-scheme` 自动 + 手动切换(本地持久化);图表
|
|
51
|
+
颜色改由 CSS 变量控制,切换即时生效。
|
|
52
|
+
- 界面打磨:diff 语法高亮(+绿/−红)、图表 hover 数值与合计、实时速率文本、
|
|
53
|
+
表格数字右对齐与状态 tag、错误 toast 常驻(手动关闭)、日志"跟随/暂停"、
|
|
54
|
+
内嵌 favicon(`/favicon.ico` 收尾返回 204)、窄屏"菜单"折叠。
|
|
55
|
+
|
|
56
|
+
### 工程
|
|
57
|
+
|
|
58
|
+
- `snapshot_diff` 下沉 core(CLI `config diff` 与 Web 差异接口的唯一实现);
|
|
59
|
+
`plan_change_many` 统一混合变更;快照动作文案 `set many:` → `edit many:`。
|
|
60
|
+
- 设计文档 §18.2 的 API 表补上 `/api/session`、`/api/config/history`、
|
|
61
|
+
`/api/config/history/{steps}/diff`(长期 drift),并把"API 表 ↔ 路由"
|
|
62
|
+
双向核对加入 `tests/test_docs.py`。
|
|
63
|
+
- 新增 35 条测试(510 → 545;非契约 515),覆盖率 85%;前端单文件 650 → 984 行。
|
|
64
|
+
其中前端守卫含 **JS 对 id 的引用与 HTML 定义的双向核对**——`node --check`
|
|
65
|
+
抓不到的"id 拼错 = 白屏"由此变成 CI 断言。
|
|
66
|
+
|
|
67
|
+
## [0.2.3] - 2026-09-17
|
|
68
|
+
|
|
69
|
+
全量通读后的根治性迭代:Web 参数边界、systemd 交叉状态、限速来源模型、分层
|
|
70
|
+
倒置,以及六项新能力(config unset / traffic / plugin user / web password /
|
|
71
|
+
配置历史回滚 / dry-run 与 stdin 输入通道)。
|
|
72
|
+
|
|
73
|
+
### 修复
|
|
74
|
+
|
|
75
|
+
- **Web 动作接口的负参数会立即 SIGKILL**:`/api/actions/stop` 的 `timeout` 走
|
|
76
|
+
宽松解析,`-1` 让等待循环一次都不执行、直接升级 SIGKILL——与 CLI 侧
|
|
77
|
+
`stop --timeout -1`(v0.2.0 修复)是同一缺陷的镜像。现在全部动作参数做范围
|
|
78
|
+
校验(越界 / 非数值 → 400),并有一条测试断言"进程必须仍存活"。
|
|
79
|
+
- **`status` 在 systemd 托管 + 损坏 state.json 时误报 STOPPED**:损坏分支完全
|
|
80
|
+
不探测 unit,而 systemd 下 state.json 本就不参与判定(direct → systemd 迁移
|
|
81
|
+
的残留即可触发)。现在 systemd 实例照常报告;非 systemd 仍报"不可判定"。
|
|
82
|
+
同时给 status 的"永不异常"承诺补上 TOCTOU 收口(损坏检查与读取之间的竞态)。
|
|
83
|
+
- **认证失败限速的来源模型**:来源表加上限(1024,防慢速内存放大);反代部署
|
|
84
|
+
下所有请求同源、攻击者 5 次失败即可连带锁住管理员 → 新增
|
|
85
|
+
`web serve --trusted-proxy`(默认关;开启后按 `X-Forwarded-For` 最后一跳
|
|
86
|
+
限速,`web service install` 亦可写入 unit)。
|
|
87
|
+
- **`config rollback -1` / `config diff --steps 0` 静默归一**:步数必须 ≥ 1
|
|
88
|
+
(用法错误 2),参数笔误不再变成另一个动作。
|
|
89
|
+
- 健康 detail 在 L2 与 L3 同时失败时只显示 L3 的信息 → 现在汇总两层,且
|
|
90
|
+
L2 失败时也会渲染失败原因(控制面是恢复顺序上的第一层)。
|
|
91
|
+
- 发布前回归 review 追加修复:Web 动作参数的**布尔值**(`{"timeout": true}`
|
|
92
|
+
曾被当作 1.0 秒静默接受);`plugin user set/remove` 的读-改-写**没有锁**
|
|
93
|
+
(两个并发调用互相覆盖——与第四轮 `config set` 并发丢失同形态,现与 config
|
|
94
|
+
写共用实例锁);**首尾空白**(`parse_scalar` 此前保留未 strip 的原文,现按
|
|
95
|
+
TOML 裸值语义去空白;纯空白的值一律拒绝);`--prompt` 在无输入时抛裸
|
|
96
|
+
`EOFError`(现为用法错误并提示改用 `--stdin`)。
|
|
97
|
+
|
|
98
|
+
### 新增
|
|
99
|
+
|
|
100
|
+
- `frpsctl config unset <key>`:删键回落 frp 默认值,与 `config set` 同一事务
|
|
101
|
+
闭环(校验 → 快照 → 重启 → 失败自动回滚);键不存在报配置错误(3)。
|
|
102
|
+
- `frpsctl config set --dry-run`:跑完全部真实校验(含 `frps verify` 与危险组合
|
|
103
|
+
拦截)但零落盘、零快照——CLI 版的"预览";`config unset` 同样支持。
|
|
104
|
+
- `config set --stdin / --prompt`:敏感值不再必须走 argv(shell 历史与
|
|
105
|
+
`/proc/<pid>/cmdline`);三种值来源互斥。
|
|
106
|
+
- `frpsctl traffic [name]`:近 7 天流量历史(无参 = 全部代理逐日汇总;离线
|
|
107
|
+
代理 404 = 无数据,单代理失败不拖垮整体)。
|
|
108
|
+
- `frpsctl plugin user set|remove|list`:策略的结构化编辑(写入前同
|
|
109
|
+
`plugin check` 判据复验、0600 原子写、未知键原样保留)。
|
|
110
|
+
- `frpsctl web password show`:读回 `web service install` 生成的口令(权限
|
|
111
|
+
过宽时向 stderr 告警)。
|
|
112
|
+
- Web 管理台的"历史与回滚"卡片:列出快照(时间 / 动作 / 步数)并可回滚到
|
|
113
|
+
任意一份;新接口 `GET /api/config/history` 只读 meta.json,不下发快照原文。
|
|
114
|
+
|
|
115
|
+
### 工程
|
|
116
|
+
|
|
117
|
+
- **分层倒置根治**:`core/` 不再反向 import `cli/`(此前 8 处
|
|
118
|
+
`cli.ui.trace` / `mask_secret`)——诊断开关下沉为 `core/diagnostics.py`,
|
|
119
|
+
打码统一走 `config.mask_value`。
|
|
120
|
+
- 死代码清理(零调用即删):`lock.held_locks`、`plugin.server.serve` /
|
|
121
|
+
`wait_ready`、`WebSettings.password`(构造后从未被读,容易误以为生效)。
|
|
122
|
+
- 新增 `tests/test_docs.py`:README 命令 / 环境变量 / 退出码与设计文档命令面的
|
|
123
|
+
**双向一致性守卫**——文档 drift 从此在 CI 里直接暴露;同步刷新设计文档
|
|
124
|
+
§1.3 / §5 / §7.2。
|
|
125
|
+
- CI 矩阵加入 Python 3.14;Web 与插件的监听 backlog 设为 64(过载行为可预期)。
|
|
126
|
+
- 新增 75 条测试(435 → 510;非契约 480),覆盖率 82% → **85%**。
|
|
127
|
+
|
|
6
128
|
## [0.2.2] - 2026-09-17
|
|
7
129
|
|
|
8
130
|
Web 管理台(内置界面)与 `kick` 语义修正。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: frpsctl
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -411,6 +411,21 @@ alice.alice-ssh alice tcp 6000 online 0 0 B / 0 B
|
|
|
411
411
|
$ frpsctl proxies --type http # 只看某类型
|
|
412
412
|
```
|
|
413
413
|
|
|
414
|
+
近 7 天的流量历史(日粒度;数据源与 Web 趋势图相同):
|
|
415
|
+
|
|
416
|
+
```console
|
|
417
|
+
$ frpsctl traffic # 全部代理逐日汇总
|
|
418
|
+
date in out
|
|
419
|
+
2026-09-16 1.2 MiB 3.4 MiB
|
|
420
|
+
2026-09-17 0.4 MiB 1.1 MiB
|
|
421
|
+
合计 1.6 MiB 4.5 MiB
|
|
422
|
+
|
|
423
|
+
$ frpsctl traffic alice.alice-ssh # 单个代理的明细
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
离线或已删除的代理在数据源上返回 404 = 无数据(不是错误);单个代理查询失败
|
|
427
|
+
也不拖垮整体。
|
|
428
|
+
|
|
414
429
|
`prune` 清理离线代理记录;在线代理无法从服务端强制下线(frp 没有该 API,
|
|
415
430
|
见"清理离线记录"一节)。
|
|
416
431
|
|
|
@@ -450,6 +465,10 @@ $ frpsctl config set maxPortsPerClient 30
|
|
|
450
465
|
|
|
451
466
|
```bash
|
|
452
467
|
frpsctl config set bindPort 8000 --no-restart # 只写不重启(输出会提示"尚未生效")
|
|
468
|
+
frpsctl config set bindPort 8000 --dry-run # 只校验并展示 diff,不写入、不重启
|
|
469
|
+
frpsctl config set webServer.password --prompt # 敏感值隐藏输入(不进 argv / shell 历史)
|
|
470
|
+
frpsctl config set webServer.password --stdin # 或从管道读:echo -n "$PW" | frpsctl ...
|
|
471
|
+
frpsctl config unset maxPortsPerClient # 删键回落 frp 默认值(同一事务闭环)
|
|
453
472
|
frpsctl config list # 列出全部键(值自动打码)
|
|
454
473
|
frpsctl config list --prefix webServer # 只看某张表
|
|
455
474
|
frpsctl config list --tree # 按表分组缩进展示
|
|
@@ -462,6 +481,10 @@ frpsctl config rollback # 回滚到上一份
|
|
|
462
481
|
frpsctl config rollback 3 # 回滚到 3 份之前
|
|
463
482
|
```
|
|
464
483
|
|
|
484
|
+
> `--dry-run` 与 `--no-restart` 的区别:前者**什么都不写**(只校验+预览),
|
|
485
|
+
> 后者已经落盘、只是没有重启;`config unset` 删除不存在的键会报配置错误(3)
|
|
486
|
+
> ——拼错键名的"成功删除"会让人以为清掉了某个设置。
|
|
487
|
+
|
|
465
488
|
**机密保护**:`config get` 默认打码(`SU***56` 形式,保留首尾便于核对是不是同一个
|
|
466
489
|
值),`--json` 与所有 diff 输出同样打码。要看明文必须 `--reveal`。
|
|
467
490
|
|
|
@@ -606,6 +629,20 @@ frpsctl plugin check # 离线校验 + 试算典型裁决
|
|
|
606
629
|
frpsctl plugin serve # 启动(只允许绑回环)
|
|
607
630
|
```
|
|
608
631
|
|
|
632
|
+
策略里的用户可以用命令维护——不必手写 JSON(写入前用与 `plugin check` 相同的
|
|
633
|
+
判据复验,0600 原子写,`_comment` 等自定义字段原样保留):
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
frpsctl plugin user list # 用户与权限摘要(不显示策略级凭据)
|
|
637
|
+
frpsctl plugin user set alice --ports 6000-6010 --max-proxies 5
|
|
638
|
+
frpsctl plugin user set alice --no-random-port # 只改这一个字段,其余保持
|
|
639
|
+
frpsctl plugin user set bob --names "" # 空串 = 删除该字段(不限名称)
|
|
640
|
+
frpsctl plugin user remove alice
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
> 插件服务在启动时载入策略:改完记得重启它才生效
|
|
644
|
+
> (`systemctl restart frpsctl-plugin@<实例>`)。
|
|
645
|
+
|
|
609
646
|
`plugin check` 让你在部署前就看到"策略会怎么判":
|
|
610
647
|
|
|
611
648
|
```console
|
|
@@ -755,12 +792,15 @@ Web 管理台:http://127.0.0.1:8787/
|
|
|
755
792
|
Ctrl-C 停止。
|
|
756
793
|
```
|
|
757
794
|
|
|
758
|
-
|
|
795
|
+
打开浏览器即可——单文件前端(明暗双主题,跟随系统并可手动切换),零外部资源(不加载任何 CDN):
|
|
759
796
|
|
|
760
797
|
| 页面 | 内容 |
|
|
761
798
|
|------|------|
|
|
762
|
-
| 仪表盘 | 实例状态 / 三层健康 / 客户端与代理列表 / 近 7
|
|
763
|
-
| 配置 |
|
|
799
|
+
| 仪表盘 | 实例状态 / 三层健康 / 客户端与代理列表 / 近 7 天流量柱状图(悬停看数值,点代理行展开单代理曲线)/ 会话内实时流量曲线 / 日志(5 秒自动刷新,向上翻自动暂停跟随)。操作进行中有"操作中…"状态,清理离线记录后列表即时刷新 |
|
|
800
|
+
| 配置 | 逐字段表单(敏感值以打码形式提示"已设置",留空表示不改;可**删除**任意键回落默认值、也可**新增**键)→ 预览打码 diff(+绿/−红)→ 确认应用(一次事务、一次重启)→ 历史回滚(**先看差异再回滚**) |
|
|
801
|
+
|
|
802
|
+
状态异常不会藏在角落:state.json 损坏、0.70.x 缺安全修复、插件不可达(客户端
|
|
803
|
+
将无法登录)、流量超 50 个代理被截断——都会在页面顶部横幅或图表下方明确写出。
|
|
764
804
|
|
|
765
805
|
**安全设计**(比 frp 自带 dashboard 更严——它正是本项目安全决策的来源):
|
|
766
806
|
|
|
@@ -782,6 +822,23 @@ sudo frpsctl web service uninstall
|
|
|
782
822
|
> `web service install` 同样需要 `frpsctl` 位于系统路径(不能被 `ProtectHome`
|
|
783
823
|
> 挡住)——与插件服务的部署要求一致。
|
|
784
824
|
|
|
825
|
+
配置页里还有**历史与回滚**:列出最近 10 份快照(时间 / 动作 / 步数),每行可
|
|
826
|
+
先"查看差异"再决定是否回滚到那一份(差异接口 `GET /api/config/history/{steps}/diff`
|
|
827
|
+
与 `config diff --steps` 共用同一实现,同样打码;列表接口只读快照元数据,
|
|
828
|
+
**不下发配置原文**——快照是含 token 与口令的完整副本)。
|
|
829
|
+
|
|
830
|
+
生成的口令随时可以取回(权限过宽时会告警):
|
|
831
|
+
|
|
832
|
+
```bash
|
|
833
|
+
frpsctl web password show
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**在反向代理后运行**(`--allow-non-loopback` + Nginx 等)时加 `--trusted-proxy`:
|
|
837
|
+
登录失败限速按 `X-Forwarded-For` 的**最后一跳**区分来源。默认关闭时该头完全
|
|
838
|
+
不被读取;开启的前提是"前面确实有一层会重写该头的可信代理"——否则攻击者的
|
|
839
|
+
失败会与管理员同源,5 次失败就能把管理员锁在冷却之外。
|
|
840
|
+
`web service install --trusted-proxy` 会把该标志写进 unit。
|
|
841
|
+
|
|
785
842
|
## 用 systemd 托管
|
|
786
843
|
|
|
787
844
|
```bash
|
|
@@ -1169,16 +1226,19 @@ FRPSCTL_TRACEBACK=1 frpsctl status
|
|
|
1169
1226
|
|
|
1170
1227
|
```bash
|
|
1171
1228
|
uv venv && uv pip install -e ".[dev]"
|
|
1172
|
-
.venv/bin/pytest # 全部
|
|
1173
|
-
.venv/bin/pytest -m "not contract" # 快速回归(
|
|
1174
|
-
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前
|
|
1229
|
+
.venv/bin/pytest # 全部 545 条(契约层缺二进制时自动 skip)
|
|
1230
|
+
.venv/bin/pytest -m "not contract" # 快速回归(515 条)
|
|
1231
|
+
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前 85%)
|
|
1175
1232
|
.venv/bin/ruff check src/ tests/ # 静态分析
|
|
1176
1233
|
```
|
|
1177
1234
|
|
|
1178
|
-
CI(Linux,Python 3.11/3.12/3.13)还包含:ruff、覆盖率门禁、真 frp 0.71.0
|
|
1179
|
-
|
|
1235
|
+
CI(Linux,Python 3.11/3.12/3.13/3.14)还包含:ruff、覆盖率门禁、真 frp 0.71.0
|
|
1236
|
+
契约层、**真 frp 0.70.0 下界契约矩阵**、无二进制降级路径、端到端冒烟与
|
|
1237
|
+
Web 管理台冒烟(含配置差异接口)、**前端静态守卫**(逐 `<script>` 块
|
|
1238
|
+
`node --check` + 禁 innerHTML/外部资源)、**文档一致性守卫**(README/设计文档/
|
|
1239
|
+
API 表 vs 代码的交叉核对)。
|
|
1180
1240
|
|
|
1181
|
-
###
|
|
1241
|
+
### 测试分七层
|
|
1182
1242
|
|
|
1183
1243
|
| 层 | 文件 | 目标 |
|
|
1184
1244
|
|----|------|------|
|
|
@@ -1188,6 +1248,7 @@ CI(Linux,Python 3.11/3.12/3.13)还包含:ruff、覆盖率门禁、真 fr
|
|
|
1188
1248
|
| 契约 | `tests/test_facts.py` | **设计文档事实基线的自动化守卫**(需真 frps) |
|
|
1189
1249
|
| 故障注入 | `tests/test_faults.py` | 注入系统调用失败,验证异常路径的五项不变量 |
|
|
1190
1250
|
| 插件 | `tests/test_plugin.py` | 协议报文、裁决、审计、配额;含真 frpc 端到端契约 |
|
|
1251
|
+
| 前端与文档 | `tests/test_web_frontend.py`、`tests/test_docs.py` | 单文件前端的静态守卫(`node --check`、禁 innerHTML/外部资源、CSS 变量对齐)与 README / 设计文档 / API 表的双向一致性 |
|
|
1191
1252
|
|
|
1192
1253
|
让契约层跑起来(需要真实二进制):
|
|
1193
1254
|
|
|
@@ -1226,6 +1287,8 @@ fail)——它们是"插件真的接得住 frp 调用"的唯一证明,因此
|
|
|
1226
1287
|
| **`max_proxies` 计数需配 `admin_url`** | 不配时只在插件进程内计数(重启归零、多实例各算各的),`plugin check` 会告警 |
|
|
1227
1288
|
| **frps 没有热重载** | 改配置必然重启,因此 `config set` 的设计目标就是"失败了要能退回去" |
|
|
1228
1289
|
| **systemd 模式下 pid 文件不参与判定** | 所有权委托 systemctl;`install` 换版本后需 `systemctl restart` |
|
|
1290
|
+
| **Web 管理台不限制并发连接数** | 单机管理工具的取舍(请求线程随连接创建,监听 backlog 64);公网暴露请在前置反代上做限流 |
|
|
1291
|
+
| **`--trusted-proxy` 只信 X-Forwarded-For 的最后一跳** | 前提是前面确实有一层会重写该头的可信代理;直连部署不要开启 |
|
|
1229
1292
|
|
|
1230
1293
|
---
|
|
1231
1294
|
|
|
@@ -388,6 +388,21 @@ alice.alice-ssh alice tcp 6000 online 0 0 B / 0 B
|
|
|
388
388
|
$ frpsctl proxies --type http # 只看某类型
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
+
近 7 天的流量历史(日粒度;数据源与 Web 趋势图相同):
|
|
392
|
+
|
|
393
|
+
```console
|
|
394
|
+
$ frpsctl traffic # 全部代理逐日汇总
|
|
395
|
+
date in out
|
|
396
|
+
2026-09-16 1.2 MiB 3.4 MiB
|
|
397
|
+
2026-09-17 0.4 MiB 1.1 MiB
|
|
398
|
+
合计 1.6 MiB 4.5 MiB
|
|
399
|
+
|
|
400
|
+
$ frpsctl traffic alice.alice-ssh # 单个代理的明细
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
离线或已删除的代理在数据源上返回 404 = 无数据(不是错误);单个代理查询失败
|
|
404
|
+
也不拖垮整体。
|
|
405
|
+
|
|
391
406
|
`prune` 清理离线代理记录;在线代理无法从服务端强制下线(frp 没有该 API,
|
|
392
407
|
见"清理离线记录"一节)。
|
|
393
408
|
|
|
@@ -427,6 +442,10 @@ $ frpsctl config set maxPortsPerClient 30
|
|
|
427
442
|
|
|
428
443
|
```bash
|
|
429
444
|
frpsctl config set bindPort 8000 --no-restart # 只写不重启(输出会提示"尚未生效")
|
|
445
|
+
frpsctl config set bindPort 8000 --dry-run # 只校验并展示 diff,不写入、不重启
|
|
446
|
+
frpsctl config set webServer.password --prompt # 敏感值隐藏输入(不进 argv / shell 历史)
|
|
447
|
+
frpsctl config set webServer.password --stdin # 或从管道读:echo -n "$PW" | frpsctl ...
|
|
448
|
+
frpsctl config unset maxPortsPerClient # 删键回落 frp 默认值(同一事务闭环)
|
|
430
449
|
frpsctl config list # 列出全部键(值自动打码)
|
|
431
450
|
frpsctl config list --prefix webServer # 只看某张表
|
|
432
451
|
frpsctl config list --tree # 按表分组缩进展示
|
|
@@ -439,6 +458,10 @@ frpsctl config rollback # 回滚到上一份
|
|
|
439
458
|
frpsctl config rollback 3 # 回滚到 3 份之前
|
|
440
459
|
```
|
|
441
460
|
|
|
461
|
+
> `--dry-run` 与 `--no-restart` 的区别:前者**什么都不写**(只校验+预览),
|
|
462
|
+
> 后者已经落盘、只是没有重启;`config unset` 删除不存在的键会报配置错误(3)
|
|
463
|
+
> ——拼错键名的"成功删除"会让人以为清掉了某个设置。
|
|
464
|
+
|
|
442
465
|
**机密保护**:`config get` 默认打码(`SU***56` 形式,保留首尾便于核对是不是同一个
|
|
443
466
|
值),`--json` 与所有 diff 输出同样打码。要看明文必须 `--reveal`。
|
|
444
467
|
|
|
@@ -583,6 +606,20 @@ frpsctl plugin check # 离线校验 + 试算典型裁决
|
|
|
583
606
|
frpsctl plugin serve # 启动(只允许绑回环)
|
|
584
607
|
```
|
|
585
608
|
|
|
609
|
+
策略里的用户可以用命令维护——不必手写 JSON(写入前用与 `plugin check` 相同的
|
|
610
|
+
判据复验,0600 原子写,`_comment` 等自定义字段原样保留):
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
frpsctl plugin user list # 用户与权限摘要(不显示策略级凭据)
|
|
614
|
+
frpsctl plugin user set alice --ports 6000-6010 --max-proxies 5
|
|
615
|
+
frpsctl plugin user set alice --no-random-port # 只改这一个字段,其余保持
|
|
616
|
+
frpsctl plugin user set bob --names "" # 空串 = 删除该字段(不限名称)
|
|
617
|
+
frpsctl plugin user remove alice
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
> 插件服务在启动时载入策略:改完记得重启它才生效
|
|
621
|
+
> (`systemctl restart frpsctl-plugin@<实例>`)。
|
|
622
|
+
|
|
586
623
|
`plugin check` 让你在部署前就看到"策略会怎么判":
|
|
587
624
|
|
|
588
625
|
```console
|
|
@@ -732,12 +769,15 @@ Web 管理台:http://127.0.0.1:8787/
|
|
|
732
769
|
Ctrl-C 停止。
|
|
733
770
|
```
|
|
734
771
|
|
|
735
|
-
|
|
772
|
+
打开浏览器即可——单文件前端(明暗双主题,跟随系统并可手动切换),零外部资源(不加载任何 CDN):
|
|
736
773
|
|
|
737
774
|
| 页面 | 内容 |
|
|
738
775
|
|------|------|
|
|
739
|
-
| 仪表盘 | 实例状态 / 三层健康 / 客户端与代理列表 / 近 7
|
|
740
|
-
| 配置 |
|
|
776
|
+
| 仪表盘 | 实例状态 / 三层健康 / 客户端与代理列表 / 近 7 天流量柱状图(悬停看数值,点代理行展开单代理曲线)/ 会话内实时流量曲线 / 日志(5 秒自动刷新,向上翻自动暂停跟随)。操作进行中有"操作中…"状态,清理离线记录后列表即时刷新 |
|
|
777
|
+
| 配置 | 逐字段表单(敏感值以打码形式提示"已设置",留空表示不改;可**删除**任意键回落默认值、也可**新增**键)→ 预览打码 diff(+绿/−红)→ 确认应用(一次事务、一次重启)→ 历史回滚(**先看差异再回滚**) |
|
|
778
|
+
|
|
779
|
+
状态异常不会藏在角落:state.json 损坏、0.70.x 缺安全修复、插件不可达(客户端
|
|
780
|
+
将无法登录)、流量超 50 个代理被截断——都会在页面顶部横幅或图表下方明确写出。
|
|
741
781
|
|
|
742
782
|
**安全设计**(比 frp 自带 dashboard 更严——它正是本项目安全决策的来源):
|
|
743
783
|
|
|
@@ -759,6 +799,23 @@ sudo frpsctl web service uninstall
|
|
|
759
799
|
> `web service install` 同样需要 `frpsctl` 位于系统路径(不能被 `ProtectHome`
|
|
760
800
|
> 挡住)——与插件服务的部署要求一致。
|
|
761
801
|
|
|
802
|
+
配置页里还有**历史与回滚**:列出最近 10 份快照(时间 / 动作 / 步数),每行可
|
|
803
|
+
先"查看差异"再决定是否回滚到那一份(差异接口 `GET /api/config/history/{steps}/diff`
|
|
804
|
+
与 `config diff --steps` 共用同一实现,同样打码;列表接口只读快照元数据,
|
|
805
|
+
**不下发配置原文**——快照是含 token 与口令的完整副本)。
|
|
806
|
+
|
|
807
|
+
生成的口令随时可以取回(权限过宽时会告警):
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
frpsctl web password show
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**在反向代理后运行**(`--allow-non-loopback` + Nginx 等)时加 `--trusted-proxy`:
|
|
814
|
+
登录失败限速按 `X-Forwarded-For` 的**最后一跳**区分来源。默认关闭时该头完全
|
|
815
|
+
不被读取;开启的前提是"前面确实有一层会重写该头的可信代理"——否则攻击者的
|
|
816
|
+
失败会与管理员同源,5 次失败就能把管理员锁在冷却之外。
|
|
817
|
+
`web service install --trusted-proxy` 会把该标志写进 unit。
|
|
818
|
+
|
|
762
819
|
## 用 systemd 托管
|
|
763
820
|
|
|
764
821
|
```bash
|
|
@@ -1146,16 +1203,19 @@ FRPSCTL_TRACEBACK=1 frpsctl status
|
|
|
1146
1203
|
|
|
1147
1204
|
```bash
|
|
1148
1205
|
uv venv && uv pip install -e ".[dev]"
|
|
1149
|
-
.venv/bin/pytest # 全部
|
|
1150
|
-
.venv/bin/pytest -m "not contract" # 快速回归(
|
|
1151
|
-
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前
|
|
1206
|
+
.venv/bin/pytest # 全部 545 条(契约层缺二进制时自动 skip)
|
|
1207
|
+
.venv/bin/pytest -m "not contract" # 快速回归(515 条)
|
|
1208
|
+
.venv/bin/pytest --cov=frpsctl # 覆盖率(CI 门禁 80%,当前 85%)
|
|
1152
1209
|
.venv/bin/ruff check src/ tests/ # 静态分析
|
|
1153
1210
|
```
|
|
1154
1211
|
|
|
1155
|
-
CI(Linux,Python 3.11/3.12/3.13)还包含:ruff、覆盖率门禁、真 frp 0.71.0
|
|
1156
|
-
|
|
1212
|
+
CI(Linux,Python 3.11/3.12/3.13/3.14)还包含:ruff、覆盖率门禁、真 frp 0.71.0
|
|
1213
|
+
契约层、**真 frp 0.70.0 下界契约矩阵**、无二进制降级路径、端到端冒烟与
|
|
1214
|
+
Web 管理台冒烟(含配置差异接口)、**前端静态守卫**(逐 `<script>` 块
|
|
1215
|
+
`node --check` + 禁 innerHTML/外部资源)、**文档一致性守卫**(README/设计文档/
|
|
1216
|
+
API 表 vs 代码的交叉核对)。
|
|
1157
1217
|
|
|
1158
|
-
###
|
|
1218
|
+
### 测试分七层
|
|
1159
1219
|
|
|
1160
1220
|
| 层 | 文件 | 目标 |
|
|
1161
1221
|
|----|------|------|
|
|
@@ -1165,6 +1225,7 @@ CI(Linux,Python 3.11/3.12/3.13)还包含:ruff、覆盖率门禁、真 fr
|
|
|
1165
1225
|
| 契约 | `tests/test_facts.py` | **设计文档事实基线的自动化守卫**(需真 frps) |
|
|
1166
1226
|
| 故障注入 | `tests/test_faults.py` | 注入系统调用失败,验证异常路径的五项不变量 |
|
|
1167
1227
|
| 插件 | `tests/test_plugin.py` | 协议报文、裁决、审计、配额;含真 frpc 端到端契约 |
|
|
1228
|
+
| 前端与文档 | `tests/test_web_frontend.py`、`tests/test_docs.py` | 单文件前端的静态守卫(`node --check`、禁 innerHTML/外部资源、CSS 变量对齐)与 README / 设计文档 / API 表的双向一致性 |
|
|
1168
1229
|
|
|
1169
1230
|
让契约层跑起来(需要真实二进制):
|
|
1170
1231
|
|
|
@@ -1203,6 +1264,8 @@ fail)——它们是"插件真的接得住 frp 调用"的唯一证明,因此
|
|
|
1203
1264
|
| **`max_proxies` 计数需配 `admin_url`** | 不配时只在插件进程内计数(重启归零、多实例各算各的),`plugin check` 会告警 |
|
|
1204
1265
|
| **frps 没有热重载** | 改配置必然重启,因此 `config set` 的设计目标就是"失败了要能退回去" |
|
|
1205
1266
|
| **systemd 模式下 pid 文件不参与判定** | 所有权委托 systemctl;`install` 换版本后需 `systemctl restart` |
|
|
1267
|
+
| **Web 管理台不限制并发连接数** | 单机管理工具的取舍(请求线程随连接创建,监听 backlog 64);公网暴露请在前置反代上做限流 |
|
|
1268
|
+
| **`--trusted-proxy` 只信 X-Forwarded-For 的最后一跳** | 前提是前面确实有一层会重写该头的可信代理;直连部署不要开启 |
|
|
1206
1269
|
|
|
1207
1270
|
---
|
|
1208
1271
|
|