1panel-toolkit 0.3.0__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.
Files changed (78) hide show
  1. 1panel_toolkit-0.3.0/.gitignore +10 -0
  2. 1panel_toolkit-0.3.0/CHANGELOG.md +148 -0
  3. 1panel_toolkit-0.3.0/LICENSE +21 -0
  4. 1panel_toolkit-0.3.0/PKG-INFO +407 -0
  5. 1panel_toolkit-0.3.0/README.md +373 -0
  6. 1panel_toolkit-0.3.0/_debug_resolve.err +0 -0
  7. 1panel_toolkit-0.3.0/_debug_resolve.log +21 -0
  8. 1panel_toolkit-0.3.0/_debug_resolve.py +18 -0
  9. 1panel_toolkit-0.3.0/docs/generation-depth.md +122 -0
  10. 1panel_toolkit-0.3.0/docs/patterns.md +505 -0
  11. 1panel_toolkit-0.3.0/examples/demo-db-spec.json +44 -0
  12. 1panel_toolkit-0.3.0/examples/demo-spec.json +29 -0
  13. 1panel_toolkit-0.3.0/pyproject.toml +45 -0
  14. 1panel_toolkit-0.3.0/skill/SKILL.md +154 -0
  15. 1panel_toolkit-0.3.0/src/p1toolkit/__init__.py +3 -0
  16. 1panel_toolkit-0.3.0/src/p1toolkit/checks/__init__.py +21 -0
  17. 1panel_toolkit-0.3.0/src/p1toolkit/checks/compose.py +188 -0
  18. 1panel_toolkit-0.3.0/src/p1toolkit/checks/forms.py +129 -0
  19. 1panel_toolkit-0.3.0/src/p1toolkit/checks/hygiene.py +73 -0
  20. 1panel_toolkit-0.3.0/src/p1toolkit/checks/i18n.py +140 -0
  21. 1panel_toolkit-0.3.0/src/p1toolkit/checks/lessons.py +111 -0
  22. 1panel_toolkit-0.3.0/src/p1toolkit/checks/structure.py +140 -0
  23. 1panel_toolkit-0.3.0/src/p1toolkit/checks/substance.py +55 -0
  24. 1panel_toolkit-0.3.0/src/p1toolkit/cli.py +890 -0
  25. 1panel_toolkit-0.3.0/src/p1toolkit/config.py +114 -0
  26. 1panel_toolkit-0.3.0/src/p1toolkit/dependencies.py +218 -0
  27. 1panel_toolkit-0.3.0/src/p1toolkit/deploy.py +302 -0
  28. 1panel_toolkit-0.3.0/src/p1toolkit/depth.py +414 -0
  29. 1panel_toolkit-0.3.0/src/p1toolkit/errors.py +18 -0
  30. 1panel_toolkit-0.3.0/src/p1toolkit/evidence.py +523 -0
  31. 1panel_toolkit-0.3.0/src/p1toolkit/fix.py +394 -0
  32. 1panel_toolkit-0.3.0/src/p1toolkit/gen.py +728 -0
  33. 1panel_toolkit-0.3.0/src/p1toolkit/lifecycle.py +143 -0
  34. 1panel_toolkit-0.3.0/src/p1toolkit/loader.py +110 -0
  35. 1panel_toolkit-0.3.0/src/p1toolkit/logo.py +174 -0
  36. 1panel_toolkit-0.3.0/src/p1toolkit/model.py +71 -0
  37. 1panel_toolkit-0.3.0/src/p1toolkit/net.py +81 -0
  38. 1panel_toolkit-0.3.0/src/p1toolkit/panel.py +154 -0
  39. 1panel_toolkit-0.3.0/src/p1toolkit/patterns/__init__.py +250 -0
  40. 1panel_toolkit-0.3.0/src/p1toolkit/patterns/catalog.py +271 -0
  41. 1panel_toolkit-0.3.0/src/p1toolkit/patterns/data/patterns.json +724 -0
  42. 1panel_toolkit-0.3.0/src/p1toolkit/patterns/extract.py +402 -0
  43. 1panel_toolkit-0.3.0/src/p1toolkit/remote.py +554 -0
  44. 1panel_toolkit-0.3.0/src/p1toolkit/report.py +96 -0
  45. 1panel_toolkit-0.3.0/src/p1toolkit/resolve/__init__.py +554 -0
  46. 1panel_toolkit-0.3.0/src/p1toolkit/resolve/analyze.py +329 -0
  47. 1panel_toolkit-0.3.0/src/p1toolkit/resolve/sources.py +306 -0
  48. 1panel_toolkit-0.3.0/src/p1toolkit/rules/__init__.py +99 -0
  49. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/decisions.yaml +132 -0
  50. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/dependencies.yaml +113 -0
  51. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/envkeys.yaml +19 -0
  52. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/form-design.yaml +59 -0
  53. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/i18n.yaml +354 -0
  54. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/lessons.yaml +84 -0
  55. 1panel_toolkit-0.3.0/src/p1toolkit/rules/data/store.yaml +171 -0
  56. 1panel_toolkit-0.3.0/src/p1toolkit/selftest.py +211 -0
  57. 1panel_toolkit-0.3.0/tests/conftest.py +169 -0
  58. 1panel_toolkit-0.3.0/tests/test_checks.py +252 -0
  59. 1panel_toolkit-0.3.0/tests/test_config.py +86 -0
  60. 1panel_toolkit-0.3.0/tests/test_corpus.py +42 -0
  61. 1panel_toolkit-0.3.0/tests/test_dependencies.py +262 -0
  62. 1panel_toolkit-0.3.0/tests/test_deploy.py +159 -0
  63. 1panel_toolkit-0.3.0/tests/test_depth.py +134 -0
  64. 1panel_toolkit-0.3.0/tests/test_evidence.py +368 -0
  65. 1panel_toolkit-0.3.0/tests/test_fix.py +214 -0
  66. 1panel_toolkit-0.3.0/tests/test_forms.py +110 -0
  67. 1panel_toolkit-0.3.0/tests/test_gen.py +206 -0
  68. 1panel_toolkit-0.3.0/tests/test_github_source.py +137 -0
  69. 1panel_toolkit-0.3.0/tests/test_lifecycle.py +158 -0
  70. 1panel_toolkit-0.3.0/tests/test_net.py +45 -0
  71. 1panel_toolkit-0.3.0/tests/test_panel.py +125 -0
  72. 1panel_toolkit-0.3.0/tests/test_patterns.py +137 -0
  73. 1panel_toolkit-0.3.0/tests/test_regressions.py +151 -0
  74. 1panel_toolkit-0.3.0/tests/test_remote.py +200 -0
  75. 1panel_toolkit-0.3.0/tests/test_remote_cli.py +58 -0
  76. 1panel_toolkit-0.3.0/tests/test_resolve.py +305 -0
  77. 1panel_toolkit-0.3.0/tests/test_rules_data.py +105 -0
  78. 1panel_toolkit-0.3.0/tests/test_selftest.py +25 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .venv/
5
+ .venv-*/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ corpus.local.toml
10
+ out/
@@ -0,0 +1,148 @@
1
+ # 变更记录
2
+
3
+ ## 0.3.0 — 2026-09-20
4
+
5
+ 补上 D09(生命周期脚本、首次启动配置)与 D10(依赖接入)两件"生成器一直在躲"的事。
6
+ 两边的形状都从官方语料量出来,不是照着印象写的。
7
+
8
+ ### D10:接面板托管的数据库 / 缓存
9
+
10
+ spec 里加一段 `dependencies`,`gen` 就会把安装表单和 compose 都接好:
11
+
12
+ ```json
13
+ "dependencies": [
14
+ {
15
+ "kind": "postgresql",
16
+ "kinds": ["postgresql", "mysql"],
17
+ "env": {
18
+ "DATABASE_HOST": "${PANEL_DB_HOST}",
19
+ "DATABASE_URL": "postgres://${PANEL_DB_USER}:${PANEL_DB_USER_PASSWORD}@${PANEL_DB_HOST}:${PANEL_DB_PORT}/${PANEL_DB_NAME}"
20
+ }
21
+ }
22
+ ]
23
+ ```
24
+
25
+ - **选项形状来自实测**(882 应用 / 1617 版本):两步式 `type: apps` +
26
+ `child.type: service` 72 个版本,一步式 `type: service` 53 个版本,默认走两步式;
27
+ Redis 只有一种实例类型,自动用一步式。数据放在 `rules/data/dependencies.yaml`。
28
+ - **只为真正引用到的面板变量生成表单字段**:官方包自己声明 PANEL_DB_NAME/USER/
29
+ USER_PASSWORD/PORT(90/92/92/49 个版本),但没被引用的一个都不生成——表单预算
30
+ 是 F 组规则在守的东西。HOST 由选择器提供,TYPE 由两步式选择器提供。
31
+ - **应用侧变量名由 spec 给**:生成器不发明变量名(那是 D01 证据的事);compose 里
32
+ 写的是字面量 `DATABASE_HOST=${PANEL_DB_HOST}`,不会被包成 `${VAR:-默认值}`——
33
+ 依赖值由面板渲染,猜个默认值只会指向不存在的地址。
34
+ - 未知 `kind`、跨变量族的 `kinds`、引用了面板没有的变量,都会带可用清单直接报错。
35
+ - `.env.sample` 会把 compose 引用到的面板变量补进去(H021 闭合),且不重复。
36
+ - `p1 resolve` 新增 `dependency_hints`:从上游 compose / README 里认出
37
+ `DB_HOST` / `DATABASE_URL` / `REDIS_*` 这类变量,按 host/port/user/password/name/url
38
+ 归类,并把 kind 猜成 postgresql / mysql / mariadb / redis,写进
39
+ `draft-spec.json` 与 `decision-report.md` 的「D10 线索」一节——人不必自己抄变量名。
40
+
41
+ ### D09:脚本与首次启动配置
42
+
43
+ - `scripts: {init, upgrade, uninstall}`:原样写入 `scripts/<name>.sh`(自动补
44
+ `#!/bin/bash`、统一 LF)。老的 `init_script` 仍然可用。
45
+ - `data_owner: "1000:1000"`:自动生成 `init.sh` / `upgrade.sh`,按挂载目录
46
+ `mkdir -p` + `chown -R`(hindsight 那种"镜像以非 root 跑、挂载目录不属于它"的包,
47
+ 以前要手写两个脚本)。
48
+ - `config_files: [{path, content}]`:把配置模板放进版本目录(例如 `data/.config.yaml`),
49
+ 并自动在 README 的中英文「首次启动 / First start」里提醒用户先改再启动。
50
+ lesson L001 的两种正解现在都能由 spec 表达。
51
+ - 证据审计跟着补了三条纪律检查:spec 声明了 `user` / `data_owner` 等运行时字段而
52
+ D07 没有上游证据 → warn(L004:凭空 chown 会让挂载目录权限出错);spec 带了依赖而
53
+ D10 的证据说"未检测到" → warn;spec 带了脚本/模板而 D09 无证据、或 D09 的间接结论
54
+ 是"暂定无" → warn。
55
+
56
+ ### 其它
57
+
58
+ - **`p1 resolve` 不再无声卡住**:`raw.githubusercontent.com` 在部分网络下不可达,
59
+ 以前每个候选文件都要等满 20s 超时,一次 resolve 能沉默好几分钟。现在有一份总的
60
+ 抓取预算(默认 60s,`p1 resolve --timeout <秒>` 可调,`0` 表示只取元数据),
61
+ 超预算就停止抓取并在报告/`--json` 的 notes 里写明跳过了哪些文件、怎么办。
62
+ - `SpecError` 移到 `p1toolkit.errors`(`from p1toolkit.gen import SpecError` 仍可用),
63
+ 让 `dependencies` / `lifecycle` 能在不反向依赖生成器的前提下拒绝坏 spec。
64
+ - i18n 标签字典补 11 条依赖相关标签(Database Service / Database Type / Redis Port …),
65
+ 10 种语言齐全,生成的字段不再回落到英文占位。
66
+ - 新增示例 `examples/demo-db-spec.json`;`p1 selftest` 增到 8 项,含"依赖接线与
67
+ 脚本/模板能生成可校验的包"。
68
+
69
+ ## 0.2.0 — 2026-09-20
70
+
71
+ 把 `resolve` 的证据纪律搬进 `gen`:**没有上游证据的决策不再能变成包。**
72
+
73
+ ### 生成前的证据审计
74
+
75
+ - `p1 gen` 现在先读 `p1 resolve` 写下的 `decisions` 账本,再决定要不要生成:
76
+ D03(容器内挂载点)、D05(容器内端口)、D06(镜像)缺证据时**直接拒绝生成**,
77
+ 退出码 2,并且**一个文件都不写**。此前 `gen` 只看 spec 的结构,
78
+ 一份 `"status": "missing"` 的 draft 照样能变成包装上就丢数据/端口不通的包。
79
+ - 阻塞范围从 `rules/data/decisions.yaml` 的 `blocking: true` 读取,`resolve`
80
+ 与 `gen` 共用同一份数据,不会再各写各的。
81
+ - 新增结构性拦截:声明了 `mount_host` 却没有容器内挂载路径的 service 会被拒
82
+ (这是"数据目录建了但没挂进去"的静默失败);账本说 D05 有端口而 spec 没有
83
+ `container_port` 同样被拒。
84
+ - 新增漂移检查(不阻塞,只提示):spec 的镜像 / 端口 / 挂载点 / 环境变量名
85
+ 与账本证据不一致时按 D06 / D05 / D03 / D01 报出来。变量名拼错是"容器起得来
86
+ 但不生效"的头号原因,现在会点名。
87
+ - 非阻塞决策缺证据时,把 `if_unknown` 的处理方式原样打印出来,不再静默填默认值。
88
+ - 人工可以显式承担决定:spec 里写 `"acknowledged": {"D03": "理由"}`,
89
+ 或命令行加 `--allow-unresolved`;两者都会被记进包内的 `decision-audit.json`。
90
+ - 生成的包里新增 `decision-audit.json`:每条决策的状态、答案、findings 与
91
+ 谁批准了缺口,评审时一眼可见。
92
+ - `p1 gen --json` 的输出带上 `audit` 字段,便于 CI 判断。
93
+
94
+ ### resolve
95
+
96
+ - `draft-spec.json` 现在把**间接证据(inferred)也带进 spec**:挂载点来自
97
+ `HALO_WORK_DIR` 这类环境变量默认值、镜像来自 README 的 `docker run` 时,
98
+ 答案会落到 `services[].mounts` / `services[].image`,而不是只停在
99
+ `decision-report.md` 里。实测 `halo-dev/halo` 的 draft 从"镜像空、无挂载"
100
+ 变成"`halohub/halo:2.26` + `/root/.halo2`"。
101
+ - 修掉 `draft_spec` 里 `for target in mounts` 覆盖同名参数 `target` 的问题
102
+ (halo 这类"挂载点来自环境变量提示"的仓库会直接抛 `SourceError`)。
103
+ - 没有挂载点时不再输出悬空的 `mount_host`;宿主路径只在真有挂载时出现。
104
+
105
+ ## 0.1.1 — 2026-09-20
106
+
107
+ 外部评审(只读校验)后的修复。评审确认了 2 个危险行为、4 个真 bug、1 条过严规则。
108
+
109
+ ### 危险行为
110
+
111
+ - `p1 uninstall` 不带参数会卸载面板上**所有**已安装应用。现在必须显式给出应用名、
112
+ `--install-id`,或 `--all --yes`。
113
+ - `deploy.py` 拼接 SSH 命令时没有做 shell 转义:包目录名含空格或 `;` 时,
114
+ `rm -rf` 会落到错误的路径。现在所有插值路径都过 `shlex.quote`。
115
+
116
+ ### Bug
117
+
118
+ - `remote smoke` 的 shell 优先级错误:`A && B || C && D` 会解析成
119
+ `((A && B) || C) && D`,兜底的 `up -d` 在 `--wait` 成功后仍会执行。已改为分组。
120
+ - `C030` 查错了对象:`version.services.get("network_mode")` 查的是"有没有一个叫
121
+ network_mode 的服务",而不是"有没有服务声明了 network_mode",导致误报。
122
+ - `gen` 生成的 `TZ` 表单字段没有接线:compose 里没有任何 `${TZ}` 引用,用户填了
123
+ 也不生效。现在会自动补 `TZ=${TZ}`,并新增 `F007` 规则抓"表单字段没人引用"。
124
+ - `p1 check <文件路径>` 直接抛 `NotADirectoryError` traceback,现在给干净报错。
125
+
126
+ ### 规则校准
127
+
128
+ - `I005` 过严:官方仓库里约 4% 的应用 `description` 是更长的句子而 `title` 与
129
+ `shortDescZh` 一致,且能过审。现在只有 `title != shortDescZh` 才 fail,
130
+ `description` 不一致降为 `I006` warn。
131
+
132
+ ### 其它
133
+
134
+ - 打包配置补上 `exclude`:`.venv-dist/` 曾被扫进 sdist(0.1.1 构建中途发现,
135
+ sdist 从 352KB 回到 132KB)。`.gitignore` 同步补 `.venv-*/`。
136
+ - `selftest` 与测试里硬编码的签名向量换成了假 key(原来用的是真实面板密钥的样子)。
137
+ - compose 变量闭合检测支持嵌套引用 `${A:-${B}}`;`gen` 写默认值时会转义 `$`。
138
+ - `patterns` 的 `generated_from` 不再携带本机绝对路径。
139
+ - `p1 patterns build` 在默认输出不可写时给出明确提示。
140
+ - `resolve` 的仓库元数据只请求一次(GitHub 未鉴权只有 60 次/小时);
141
+ 本地检出不再遍历 `node_modules`/`.venv`/`target` 等目录。
142
+ - `p1 panel ping` 现在会经 `connect()` 自动探测 v2/v1(此前该函数是死代码)。
143
+ - `config show` 不再把 `ssh.key`(一个路径)当密钥打码。
144
+
145
+ ## 0.1.0 — 2026-09-20
146
+
147
+ 首个版本:`resolve` / `gen` / `check` / `fix` / `depth` / `patterns` / `ports` /
148
+ `remote` / `deploy` / `uninstall` / `config` / `panel` / `rules` / `selftest`。
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 idkan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,407 @@
1
+ Metadata-Version: 2.5
2
+ Name: 1panel-toolkit
3
+ Version: 0.3.0
4
+ Summary: Generate, check, fix and runtime-verify 1Panel app packages from a single spec.
5
+ Author: idkan
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 idkan
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ License-File: LICENSE
28
+ Keywords: 1panel,appstore,docker,packaging,self-hosted
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: pyyaml>=6.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # 1panel-toolkit
36
+
37
+ 一个 spec 进,一个经过验证的 1Panel 应用包出。
38
+
39
+ `p1` 把 1Panel 应用打包里重复的部分(10 语言 label、README 骨架、`.env.sample`
40
+ 闭合、LF/BOM 归一化、strict-store 结构校验)变成代码,把真正需要判断的部分
41
+ (上游端口、运行用户、依赖拓扑)留给人或 agent,并且把踩过的坑变成可复用的
42
+ 前置检查。
43
+
44
+ 它不绑定任何 agent 宿主:CLI 可以独立使用,也可以由 skill 包装后交给 coding
45
+ agent 调用。
46
+
47
+ ## 安装
48
+
49
+ ```bash
50
+ uv tool install 1panel-toolkit
51
+ p1 --version
52
+ p1 selftest # 验收:规则数据、写法库、生成→校验链路是否都正常
53
+ ```
54
+
55
+ 从源码:
56
+
57
+ ```bash
58
+ uv venv .venv
59
+ uv pip install -e . --python .venv/Scripts/python.exe
60
+ ```
61
+
62
+ 从构建产物:
63
+
64
+ ```bash
65
+ uv build --out-dir dist
66
+ uv venv .venv-dist && uv pip install dist/*.whl --python .venv-dist/Scripts/python.exe
67
+ .venv-dist/Scripts/p1.exe selftest
68
+ ```
69
+
70
+ ## 用法
71
+
72
+ ```bash
73
+ p1 check <app-package-dir>
74
+ p1 check <dir> --strict
75
+ p1 check <dir> --json
76
+ p1 rules lessons
77
+ p1 ports 47.113.177.168 10000,15000,8080
78
+ p1 patterns list
79
+ p1 patterns show compose/panel-db-env
80
+ p1 patterns find 数据库
81
+ p1 depth --corpus <官方apps目录> # 哪些维度是应用特有的
82
+ p1 depth <包目录> --corpus <官方apps目录> # 这个包是适配过的还是套模板
83
+ p1 resolve <仓库URL|本地检出目录> # 抽取上游证据,回答 10 个生成决策
84
+ p1 resolve halo-dev/halo --out ./out/halo # 同时写出报告/spec草稿/证据
85
+ p1 remote check # 目标主机的事实与空闲端口
86
+ p1 remote smoke <包目录> --ports 10000 # 上传→起容器→探针→抓日志→清理
87
+ p1 gen examples/demo-spec.json --out ./out --check # 从 spec 生成包并立即校验
88
+ p1 gen examples/demo-db-spec.json --out ./out --check # 含面板数据库依赖与 init.sh 的示例
89
+ p1 gen ./out/app/draft-spec.json --allow-unresolved # 明知缺证据也要生成(会记账)
90
+ p1 deploy ./out/p1-demo --param PANEL_APP_PORT_HTTP=15000 # 走面板 API 正式安装
91
+ p1 fix <包目录> --check # 把 check 的结果幂等修掉
92
+ p1 selftest # 验收:这个安装能不能用
93
+ ```
94
+
95
+ `p1 ports` 区分三种状态:`open`(已有服务)、`closed`(主机可达但无人监听,
96
+ 说明安全组已放行,可以直接用)、`filtered`(超时,被上游拦掉)。云服务器上做
97
+ 冒烟测试前先跑一次,能避免把安全组问题当成打包问题。
98
+
99
+ ## 它检查什么
100
+
101
+ 规则组 `P0xx` 管包结构:根/版本 `data.yml` 的键位层级、必备元数据、
102
+ `architectures` 位置、tag 白名单、logo 尺寸。
103
+
104
+ 规则组 `I0xx` 管 i18n:`description` 与 `formFields[].label` 的语言覆盖、
105
+ `values[].label` 必须是纯字符串、bool label 必须加引号。
106
+
107
+ 规则组 `C0xx` 管 compose:顶层 `version:`、image 引号、`${VAR}` 与表单 envKey
108
+ 闭合、`createdBy` 标签、端口 envKey 前缀、外部网络、同名服务 DNS 冲突。
109
+
110
+ 规则组 `H0xx` 管文件卫生:LF/BOM、README store 风格、`.env.sample` 闭合。
111
+
112
+ 规则组 `L0xx` 是经验检查:静态校验通过但容器启动就崩的那类问题,条目见
113
+ `rules/data/lessons.yaml`。
114
+
115
+ 规则本身是数据,放在 `src/p1toolkit/rules/data/*.yaml`:store 政策变化是改数据,
116
+ 不是改代码。
117
+
118
+ ## 写法库(patterns)
119
+
120
+ `p1 patterns` 是一套**从官方应用包里抽取的真实写法**,不是手写的示例:
121
+ 当前语料是 **882 个官方应用 / 1617 个版本**,覆盖 data.yml 声明、各类表单字段、
122
+ 数据库与 Redis 接入、compose 拓扑、init/upgrade/uninstall 脚本、README 与
123
+ `.env.sample` 约定。
124
+
125
+ 每条 pattern 带四样东西:**用途**、**何时使用**、**官方采用数**(多少个应用真的
126
+ 这么写,用来区分主流做法和个例)、以及**真实代码片段 + 来源包路径**。
127
+
128
+ ```bash
129
+ p1 patterns list # 22 条写法,带采用数
130
+ p1 patterns show form-field/port # 展开一条,含完整示例与来源
131
+ p1 patterns find 数据库 # 关键词检索
132
+ p1 patterns build --corpus <apps目录> # 重新从语料构建(官方 store 更新后跑一次)
133
+ p1 patterns export --markdown docs/patterns.md # 生成教程文档
134
+ ```
135
+
136
+ 构建产物 `patterns/data/patterns.json`(约 37 KB)随包分发,所以离线也能查;
137
+ 教程文档见 `docs/patterns.md`。
138
+
139
+ ## 生成深度(depth)
140
+
141
+ 模板化生成会做出高度重复的包——因为每个应用真正不同的那些维度(容器内挂载点 642 种
142
+ 取值、容器内端口 359 种、应用环境变量名 3197 个)在模板里全被压成了一个默认值。
143
+
144
+ `p1 depth` 用 882 个官方应用的实测分布量化这件事:哪些维度可以模板化(重启策略 5 种
145
+ 取值,`always` 占 82%),哪些必须逐应用决定。它还给出**单包判定**——把包的应用特有
146
+ 特征做成指纹,和官方语料比对,识别"是适配过的"还是"套模板套出来的"。
147
+
148
+ 生成器必须逐项回答的 10 个问题在 `rules/data/decisions.yaml`,说明见
149
+ `docs/generation-depth.md`。
150
+
151
+ ## 上游情报(resolve)
152
+
153
+ `gen` 的每一笔都必须是能指回上游文件的值,所以先生成证据:
154
+
155
+ ```bash
156
+ p1 resolve halo-dev/halo --out ./out/halo
157
+ ```
158
+
159
+ 它从 GitHub API(或本地检出目录)拉 Dockerfile / compose / `.env.example` /
160
+ README,抽取出端口、挂载点、环境变量、运行用户、额外运行时字段、依赖等信息,
161
+ **每条都带 `文件:行号`**,然后逐条回答 D01–D10:
162
+
163
+ `raw.githubusercontent.com` 在部分网络下不可达(`api.github.com` 通、raw 不通是常见
164
+ 组合)。这时 `resolve` 不会无声地卡住:抓取有一份总预算(默认 60 秒),超了就停下来
165
+ 并在报告里写明跳过了哪些文件——`p1 resolve <仓库> --timeout 120` 放宽,
166
+ `--timeout 0` 只取仓库元数据,或者干脆 `cd` 到本地检出目录跑(`p1 resolve .` 同样有效)。
167
+
168
+ - ✅ `evidenced` —— 上游文件里明写了
169
+ - ⚠️ `inferred` —— 有较弱的合法来源(例如路径来自 `HALO_WORK_DIR=/root/.halo2`
170
+ 这类环境变量默认值,镜像名来自 README 的 `docker run`),需要人工确认
171
+ - ❌ `missing` —— 什么都没有
172
+
173
+ **D03(容器内挂载点)、D05(容器内端口)、D06(镜像)没有证据时,`resolve` 直接
174
+ 返回非零退出码并列出阻塞项**,不猜、不填默认值。间接证据(inferred)会连同答案一起
175
+ 落进 draft spec——人工确认过的值必须能到达生成器,而不是停在报告里。产出三份文件:
176
+ `decision-report.md`、`draft-spec.json`、`resolve.json`。
177
+
178
+ 实测:`halo-dev/halo` 的挂载点在 Dockerfile 里没有 `VOLUME`,但 `HALO_WORK_DIR`
179
+ 默认值是 `/root/.halo2`;镜像只在 README 里出现——它把两条都标成 `inferred`
180
+ 并给出出处,而不是停下来或者瞎猜。
181
+
182
+ ## 运行时验证(remote)
183
+
184
+ ### 面板安装(deploy / uninstall)
185
+
186
+ ```bash
187
+ p1 deploy <包目录> [--param K=V] [--uninstall] [--cleanup-local] [--no-wait]
188
+ p1 uninstall <安装名> # 或 --install-id N
189
+ ```
190
+
191
+ 和 `remote smoke`(SSH 直接起容器)不同,`deploy` 走的是**真实用户路径**:
192
+
193
+ 1. 把包放到面板的本地应用目录 `/opt/1panel/resource/apps/local/<key>`
194
+ 2. `POST /apps/sync/local` 让面板重新扫描本地应用
195
+ 3. `POST /apps/search` 找到应用(本地应用注册成 `local<key>`)
196
+ 4. `GET /apps/detail/{appId}/{version}/{type}` 取 `appDetailId`
197
+ 5. `POST /apps/install` 提交安装,参数以**对象**形式放进 `params`
198
+ 6. 轮询 `POST /apps/installed/search`,看面板自己记的 `status` 是否 `Running`
199
+
200
+ 这几步的端点与字段都是从运行中的 v2.2.4 上探测出来的(给接口发空 body,让面板的
201
+ 参数校验器报出必填项),不是猜的。实测一轮:安装 → 容器 `status=Running` → 卸载,
202
+ 约 10 秒(镜像已缓存)。
203
+
204
+ 为什么两个都要有:`remote smoke` 验证"这个 compose 能不能起来",
205
+ `deploy` 验证"面板能不能把它装出来"——表单渲染、依赖注入、防火墙放行这些环节
206
+ 只有走面板才会被走到。
207
+
208
+ ### 1Panel v2 的 API 鉴权(踩过的坑)
209
+
210
+ 1Panel v2.2.4 的接口约定和直觉不一样,而且**三处错误返回同一条**
211
+ `{"code":401,"message":"API 接口密钥错误"}`,从响应上完全看不出问题在哪:
212
+
213
+ | | 正确做法 | 直觉上会写成 |
214
+ | --- | --- | --- |
215
+ | 路由前缀 | `/api/v2/...` | `/api/v1/...`(v2 里已不存在) |
216
+ | 时间戳头 | `1Panel-Timestamp` | `1Panel-Time` |
217
+ | Token 头 | `md5("1panel" + api_key + 时间戳)` | 明文 api_key |
218
+
219
+ 依据是上游 `backend/middleware/session.go`:
220
+ `panelToken == GenerateMD5("1panel" + global.CONF.System.ApiKey + panelTimestamp)`。
221
+ `p1 config set panel.*` + `p1 panel ping` 已按这个实现(保留 v1 回退),
222
+ lesson 记作 `L007`。
223
+
224
+ ## 修复(fix)
225
+
226
+ ```bash
227
+ p1 fix <包目录> --dry-run # 先看会改什么
228
+ p1 fix <包目录> --check # 改完立刻重新校验
229
+ ```
230
+
231
+ `fix` 是幂等的:第二次跑会告诉你"没有需要修复的内容"。它做两类事:
232
+
233
+ **机械修复**(永远安全):CRLF/BOM 归一化为 LF、`image` 与端口映射加引号、
234
+ 移除顶层 `version:`、补 `labels.createdBy`、补根 `data.yml` 的 `recommend` /
235
+ `memoryRequired`、对齐 `title`/`description`/`shortDescZh`、补齐 `.env.sample`
236
+ 缺失的变量、把 logo 等比缩放到 180×180 透明底(**不放大**,小图居中)。
237
+
238
+ **纪律修复**(改变安装表单):把"可选且有可用默认值"的字段从表单里移出去,
239
+ 同时在 compose 里把 `${VAR}` 改成 `${VAR:-默认值}`,让变量仍然有效。这就是
240
+ "三十个输入框、六个必填"那个反模式的解法。
241
+
242
+ 不做的事:需要判断的(哪些字段用户真的必须决定、标签该怎么翻译)会列入
243
+ `需要人工处理` 而不是替你猜。
244
+
245
+ 实测效果(`_recon/measure_fix.py` 的原始输出):
246
+
247
+ | 包 | 修复前 | 修复后 | 改动 |
248
+ | --- | --- | --- | --- |
249
+ | hindsight | 2 fail + 8 warn | **0 fail + 2 warn** | 7 处 |
250
+ | openviking | 1 fail + 6 warn | **0 fail + 1 warn** | 8 处 |
251
+ | xiaozhi-esp32-server | 1 fail + 13 warn | **0 fail + 1 warn** | 14 处 |
252
+
253
+ 静态校验和生成的 YAML 都不能告诉你"容器起不起得来、端口通不通、应用找不找得到配置文件"。
254
+
255
+ ```bash
256
+ p1 remote check # 主机事实(内存/磁盘/已用端口/已有本地应用)
257
+ p1 remote smoke <包目录> --ports 10000 # 完整一轮冒烟
258
+ p1 remote smoke <包目录> --env OV_VLM_API_KEY=sk-… # 用真实密钥覆盖表单默认值
259
+ ```
260
+
261
+ 它把某个版本目录上传到 `/tmp/p1-smoke/<key>`(**不是** `/opt/1panel/resource/apps/local`,
262
+ 不会碰到你已装的包),按安装表单的默认值渲染 `.env`,`docker compose up -d`,
263
+ 然后轮询到"HTTP 有应答"或"healthcheck 明确 healthy"为止,抓日志、清理。
264
+
265
+ 判定分三类,退出码不同,便于接 CI:
266
+
267
+ | 结果 | 退出码 | 含义 |
268
+ | --- | --- | --- |
269
+ | `PASS` | 0 | 容器 running 且端口有 HTTP 应答(或 healthcheck healthy) |
270
+ | `PENDING` | 3 | 日志显示缺少用户密钥/凭据 → **是表单待填,不是包的缺陷** |
271
+ | `FAIL` | 1 | 其他失败;自动匹配 lesson 并给出根因 |
272
+
273
+ 两个设计细节值得说明:**端口被监听不等于就绪**(应用可能先绑端口再花几十秒初始化),
274
+ 所以主判据是 HTTP 应答,纯 TCP 监听只在 healthcheck 认可时才算通过;失败时保留远端目录
275
+ 以便 `p1 remote logs <dir>` 复看,成功则连同容器和数据卷一起清掉。
276
+
277
+ ## 生成(gen)
278
+
279
+ ```bash
280
+ p1 resolve <仓库> --out ./out/app # 1. 上游证据 → draft-spec.json
281
+ $EDITOR ./out/app/draft-spec.json # 2. 人工确认证据、补一句简介
282
+ p1 gen ./out/app/draft-spec.json --out ./out --check # 3. 生成 + 校验
283
+ p1 remote smoke ./out/app --ports 10000 # 4. 真机验证
284
+ ```
285
+
286
+ **核心纪律:安装表单是"决策清单",不是"配置转储"。**
287
+
288
+ 一个上游变量只有在两种情况下才出现在表单里:用户必须决定它(端口、对外 URL),
289
+ 或者用户必须提供它(密钥)。其余全部写进 compose 默认值(`${VAR:-default}`),
290
+ 并在 README 的「高级配置」里列出来供需要时修改。
291
+
292
+ 这不是审美偏好,是实测差距:官方 1617 个版本的表单**中位数 5 个字段、88% 标为必填**;
293
+ 本项目早期的 hindsight 包有 30 个字段、必填只占 20%,用户反馈"有的要填有的不用填,
294
+ 非常麻烦"。现在 `gen` 会把这个纪律强制执行,`check` 的 F 组规则负责事后把关。
295
+
296
+ 生成物包含:根 `data.yml`(`name` 放产品名,`title`/`description`/`shortDescZh`
297
+ 同一句简介 —— 这是 739/771 个官方应用的写法)、版本 `data.yml`、`docker-compose.yml`
298
+ (引号、`createdBy`、`1panel-network`、上游 runtime 开关原样保留)、`.env.sample`
299
+ (包含所有隐藏变量)、中英 README、logo 占位图、`source-evidence.json`。
300
+
301
+ 生成过程会明确告诉你哪些东西是**它替你决定的**:哪些变量被收进默认值、哪些翻译是
302
+ 占位、logo 需要替换。
303
+
304
+ ### 证据纪律:没有证据就不生成
305
+
306
+ `gen` 会先审计 `resolve` 写下的决策账本,再决定要不要写文件。**D03(容器内挂载点)、
307
+ D05(容器内端口)、D06(镜像)没有证据时直接拒绝生成**,退出码 2,且一个文件都不写:
308
+
309
+ ```
310
+ FAIL D03 D03 数据要挂到容器内哪个路径? 没有上游证据
311
+ → 补证据,或在 spec 里写 "acknowledged": {"D03": "理由"};停止,不要猜挂载点
312
+ FAIL D06 app: 没有 image
313
+ → 镜像必须来自上游证据(README 的 docker run / 官方镜像页)
314
+ ```
315
+
316
+ 这三条是实测出来的分界线:它们的取值分别有 642 / 359 / 19 种,抄模板抄错时的表现是
317
+ "装得上、跑得起来、就是不对"——数据升级即丢、端口不通、镜像来路不明。阻塞范围存在
318
+ `rules/data/decisions.yaml` 的 `blocking` 字段里,`resolve` 与 `gen` 读同一份数据。
319
+
320
+ 其余检查不阻塞,但会点名:
321
+
322
+ - **漂移**:spec 的镜像 / 端口 / 挂载点不在账本证据里,或环境变量名不在 D01/D02 里
323
+ (变量名拼错是最难查的一类:容器照常启动,值被静默忽略)。
324
+ - **结构性缺口**:声明了 `mount_host` 却没有容器内挂载路径的 service 会被拒——
325
+ 这种包"有数据目录"但什么都没挂进去。
326
+ - **非阻塞决策缺证据**:把 `decisions.yaml` 里 `if_unknown` 的处理方式原样打印出来。
327
+
328
+ 人工可以显式承担决定,两种方式都会记进包内的 `decision-audit.json`:
329
+
330
+ ```bash
331
+ # 1. 写进 spec(推荐:评审时能看到理由)
332
+ # "acknowledged": {"D03": "上游 Dockerfile 没有 VOLUME,这个应用不写盘"}
333
+ # 2. 一次性放行
334
+ p1 gen ./out/app/draft-spec.json --allow-unresolved
335
+ ```
336
+
337
+ `decision-audit.json` 与包一起交付:每条决策的状态、答案、证据缺口、谁批准了哪条,
338
+ 评审的人不必再去翻对话记录。`p1 gen --json` 里也有同一份数据,方便接 CI。
339
+
340
+ ### 依赖接入(D10)与脚本(D09)
341
+
342
+ **依赖是"接线",不是"多几个输入框"。** 官方 106 个版本用依赖选择器,主流形状是两步式
343
+ `type: apps` + `child.type: service`(72 个版本),一步式 `type: service` 有 53 个
344
+ (Redis 只有一种实例,天然用一步式)。两种形状都从语料量出来,放在
345
+ `rules/data/dependencies.yaml`。
346
+
347
+ ```json
348
+ "dependencies": [
349
+ {
350
+ "kind": "postgresql",
351
+ "kinds": ["postgresql", "mysql"],
352
+ "env": {
353
+ "DATABASE_HOST": "${PANEL_DB_HOST}",
354
+ "DATABASE_URL": "postgres://${PANEL_DB_USER}:${PANEL_DB_USER_PASSWORD}@${PANEL_DB_HOST}:${PANEL_DB_PORT}/${PANEL_DB_NAME}"
355
+ }
356
+ }
357
+ ]
358
+ ```
359
+
360
+ 生成器据此产出选择器字段、把 `${PANEL_DB_*}` 映射成应用自己的变量名,并**只为真正被
361
+ 引用到的面板变量生成输入框**(`PANEL_DB_HOST` 由选择器提供,其余没用到的一个都不加)。
362
+ 应用侧的变量名必须由你给出——那是 D01 证据,不是生成器能发明的东西。
363
+
364
+ `p1 resolve` 会先给线索:上游 compose / README 里像数据库接线的变量(`DB_HOST`、
365
+ `DATABASE_URL`、`REDIS_*`)会按 host / port / user / password / name / url 归类,
366
+ 写进 `draft-spec.json` 的 `dependency_hints` 和决策报告的「D10 线索」一节。
367
+
368
+ **脚本与首次启动配置**(lesson L001:静态全绿、容器一起来就退出)有了两条正解:
369
+
370
+ ```json
371
+ "data_owner": "1000:1000",
372
+ "config_files": [
373
+ { "path": "data/app.conf", "content": "database_url=${DATABASE_URL}\n" }
374
+ ]
375
+ ```
376
+
377
+ `data_owner` 生成 `init.sh` / `upgrade.sh`,按挂载目录 `mkdir -p` + `chown -R`;
378
+ `config_files` 把模板放进版本目录,并在中英文 README 的「首次启动 / First start」里
379
+ 提醒用户先编辑。更复杂的逻辑用 `scripts: {init, upgrade, uninstall}` 原样写入
380
+ (自动补 `#!/bin/bash`、统一 LF)。
381
+
382
+ 这两件事同样受证据约束:声明 `user` / `data_owner` 却拿不出 D07 证据、带了依赖而
383
+ D10 说"未检测到"、带了脚本而 D09 无证据——都会在证据审计里被点名(lesson L004)。
384
+
385
+ ## 当前状态
386
+
387
+ 完整流程已经闭环:
388
+
389
+ ```bash
390
+ p1 resolve <仓库> --out ./out/app # 1. 上游证据 + 10 项生成决策
391
+ # 人工确认 draft-spec.json
392
+ p1 gen ./out/app/draft-spec.json --check # 2. 生成 + 静态校验
393
+ p1 depth ./out/app --corpus <官方apps> # 3. 确认不是模板复制品
394
+ p1 remote smoke ./out/app --ports 10000 # 4. SSH 冒烟:起容器、探针、抓日志
395
+ p1 deploy ./out/app --param ... --uninstall # 5. 走面板 API 正式安装并验证
396
+ ```
397
+
398
+ 修复已有包:`p1 fix <包目录> --check`(幂等,见上文)。
399
+
400
+ 命令一览:`resolve` / `gen` / `check` / `fix` / `depth` / `patterns` / `ports` /
401
+ `remote` / `deploy` / `uninstall` / `config` / `panel` / `rules` / `selftest`。
402
+
403
+ ## 设计约束
404
+
405
+ 没有官方 Docker 证据就不猜镜像、端口、卷、UID/GID。高风险运行时权限先保留并
406
+ 标注风险,不为了扫描器好看而删。云服务器上做运行时验证时只用已确认放行的端口
407
+ 段,避免把安全组问题误判成打包问题。