Catalog PyPI
Back to packageRELEASE DETAILS

0.22.0

PyPI

1panel-toolkit

Generate, check, fix and runtime-verify 1Panel app packages from a single spec.

1panelappstoredockerpackagingself-hosted
Published7 Oct 2026
Direct dependencies2
Transitive dependenciesNot calculated

README

Version 0.22.0 documentation

1panel-toolkit

一个 spec 进,一个经过验证的 1Panel 应用包出。

p1 把 1Panel 应用打包里重复的部分(10 语言 label、README 骨架、.env.sample 闭合、LF/BOM 归一化、strict-store 结构校验)变成代码,把真正需要判断的部分 (上游端口、运行用户、依赖拓扑)留给人或 agent,并且把踩过的坑变成可复用的 前置检查。

它不绑定任何 agent 宿主:CLI 可以独立使用,也可以由 skill 包装后交给 coding agent 调用。

核心路径不需要面板、不需要 SSH、不需要任何密钥:resolve → recipe → gen → check → depth → fix 全部离线跑,输入是仓库地址(或本地检出),输出是一个包。 deploy / uninstall / remote / panel 是可选的附加能力,用面板 API key 帮你检查部署情况、把失败解释清楚——不配也行,核心不依赖它们。

安装

uv tool install 1panel-toolkit     # 更新到 PyPI 上最新版
p1 --version
p1 selftest        # 验收:规则数据、写法库、生成→校验链路是否都正常

从源码:

uv venv .venv
uv pip install -e . --python .venv/Scripts/python.exe

从构建产物:

uv build --out-dir dist
uv venv .venv-dist && uv pip install dist/*.whl --python .venv-dist/Scripts/python.exe
.venv-dist/Scripts/p1.exe selftest

用法

p1 check <app-package-dir>
p1 check <dir> --strict
p1 check <dir> --json
p1 rules lessons
p1 ports 203.0.113.10 10000,15000,8080
p1 patterns list
p1 patterns show compose/panel-db-env
p1 patterns find 数据库
p1 depth --corpus <官方apps目录>          # 哪些维度是应用特有的
p1 depth <包目录> --corpus <官方apps目录>  # 这个包是适配过的还是套模板
p1 resolve <仓库URL|本地检出目录>          # 抽取上游证据,回答 10 个生成决策
p1 resolve halo-dev/halo --out ./out/halo  # 同时写出报告/spec草稿/证据
p1 remote check                            # 目标主机的事实与空闲端口
p1 remote smoke <包目录>                    # 上传→起容器→探针→抓日志→清理(不传端口就用包自己的默认值)
p1 gen examples/demo-spec.json --out ./out --check   # 从 spec 生成包并立即校验
p1 gen examples/demo-db-spec.json --out ./out --check # 含面板数据库依赖与 init.sh 的示例
p1 gen ./out/app/draft-spec.json --allow-unresolved  # 明知缺证据也要生成(会记账)
p1 recipe list                          # 5 条"成套写法":面板 DB / Redis / Mongo / MinIO / 组合
p1 recipe apply panel-postgres ./out/app/draft-spec.json --write  # 一键接线
p1 deploy ./out/p1-demo --param PANEL_APP_PORT_HTTP=15000   # 走面板 API 正式安装
p1 deploy ./out/p1-demo --param PANEL_APP_PORT_HTTP=15000 --probe   # 装完再敲一下端口
p1 panel info                               # 仪表盘:版本 / 负载 / 各类资源计数
p1 panel apps                               # 已安装应用(websites / ssl / databases / containers 同理)
p1 panel request GET /dashboard/base/os     # 调任意 v2 端点,看面板原始应答(API 测试)
p1 panel api 数据库                          # 离线端点地图,配合 request 用
p1 fix <包目录> --check                    # 把 check 的结果幂等修掉
p1 form audit <包目录> --plan plan.json    # 表单里哪些输入框用户真的必须填?
p1 fix <包目录> --plan plan.json --check   # 按你改过的计划执行
p1 selftest                                # 验收:这个安装能不能用

p1 ports 区分三种状态:open(已有服务)、closed(主机可达但无人监听, 说明安全组已放行,可以直接用)、filtered(超时,被上游拦掉)。云服务器上做 冒烟测试前先跑一次,能避免把安全组问题当成打包问题。

它检查什么

规则组 P0xx 管包结构:根/版本 data.yml 的键位层级、必备元数据、 architectures 位置、tag 白名单、logo 尺寸;P030 查根 key 与目录名是否一致 (官方 882/882 一致),P017 提示 v 前缀的版本目录(官方 1617 个里只有 6 个这样), P031/P032 查根元数据的类型(limit/recommend/memoryRequired 必须是整数, crossVersionUpdate 等必须是布尔——写错会让面板同步直接失败: cannot unmarshal !!str \512M` into int),P033` 提示官方语料没用过的键。

规则组 I0xx 管 i18n:description 与 formFields[].label 的语言覆盖、 values[].label 必须是纯字符串、bool label 必须加引号。I005 对"title 与 shortDescZh 不是同一句"只报 warn:官方 882 个应用里 811 个写成同一句、 69 个把 title 当产品名,两种都能上架。

规则组 C0xx 管 compose:顶层 version:、image 引号、${VAR} 与表单 envKey 闭合、createdBy 标签、端口 envKey 前缀、外部网络、同名服务 DNS 冲突、 healthcheck 必须是映射(C005,healthcheck: true 会让 Docker 直接解析失败)、 service 是否有 restart 策略(C006,官方 1954/1958 个服务都写了)、 以及 compose 里到底有没有 service(C031:services: {} 以前能全绿通过, 但那样装上去什么都不会跑)。

规则组 H0xx 管文件卫生:LF/BOM、README store 风格、.env.sample 闭合、 scripts/*.sh 必须有 shebang(H030,官方 2416/2416 个脚本都有)。

规则组 F0xx 管安装表单本身:字段预算(官方中位数 5 个、上限 20)、必填比例、 密钥字段类型、字段是否真被引用(F007),以及 type/rule 是否落在官方用过的枚举里 (F008/F009——表单就是靠这两个值渲染的)、每个字段键的 YAML 类型 (F010/F011:description/label/child 必须是字典、values/params 必须是列表, 写成字符串面板会直接同步失败)。

规则组 L0xx 是经验检查:静态校验通过但容器启动就崩的那类问题,条目见 rules/data/lessons.yaml。

规则本身是数据,放在 src/p1toolkit/rules/data/*.yaml:store 政策变化是改数据, 不是改代码。

写法库(patterns)

p1 patterns 是一套从官方应用包里抽取的真实写法,不是手写的示例: 当前语料是 882 个官方应用 / 1617 个版本,覆盖 data.yml 声明、各类表单字段、 数据库与 Redis 接入、compose 拓扑、init/upgrade/uninstall 脚本、README 与 .env.sample 约定。

每条 pattern 带四样东西:用途、何时使用、官方采用数(多少个应用真的 这么写,用来区分主流做法和个例)、以及真实代码片段 + 来源包路径。

p1 patterns list                       # 22 条写法,带采用数
p1 patterns show form-field/port       # 展开一条,含完整示例与来源
p1 patterns find 数据库                 # 关键词检索
p1 patterns build --corpus <apps目录>   # 重新从语料构建(官方 store 更新后跑一次)
p1 patterns export --markdown docs/patterns.md   # 生成教程文档

构建产物 patterns/data/patterns.json(约 37 KB)随包分发,所以离线也能查; 教程文档见 docs/patterns.md。

生成深度(depth)

模板化生成会做出高度重复的包——因为每个应用真正不同的那些维度(容器内挂载点 642 种 取值、容器内端口 359 种、应用环境变量名 3197 个)在模板里全被压成了一个默认值。

p1 depth 用 882 个官方应用的实测分布量化这件事:哪些维度可以模板化(重启策略 5 种 取值,always 占 82%),哪些必须逐应用决定。它还给出单包判定——把包的应用特有 特征做成指纹,和官方语料比对,识别"是适配过的"还是"套模板套出来的"。

生成器必须逐项回答的 10 个问题在 rules/data/decisions.yaml,说明见 docs/generation-depth.md。

上游情报(resolve)

gen 的每一笔都必须是能指回上游文件的值,所以先生成证据:

p1 resolve halo-dev/halo --out ./out/halo

它从 GitHub API(或本地检出目录)拉 Dockerfile / compose / .env.example / README,抽取出端口、挂载点、环境变量、运行用户、额外运行时字段、依赖等信息, 每条都带 文件:行号,然后逐条回答 D01–D10。

monorepo(应用不在仓库根目录)直接把浏览器里复制的子目录 URL 交给它, 或用 --subdir 指明;证据只从该子目录采集,应用 key 仍取自仓库名:

p1 resolve https://github.com/immich-app/immich/tree/main/docker --out ./out/immich
p1 resolve immich-app/immich --subdir docker --out ./out/immich   # 等价写法

raw.githubusercontent.com 在部分网络下不可达,另一些网络下是极慢(实测 4–17 秒/文件)。 resolve 的第一次请求只当探路(4 秒上限),超时或失败就改用 GitHub 的 contents API 取文件(同环境实测 0.7 秒/文件),并在报告里说明原因;之后各文件并行抓取。 只有仍在用 raw 时才受抓取总预算(默认 60 秒)限制,--timeout 可调, --timeout 0 只取仓库元数据。实测一次远程 resolve 从 26 秒降到 7 秒左右; 本地检出则完全离线:p1 resolve <本地目录>。

  • ✅ evidenced —— 上游文件里明写了
  • ⚠️ inferred —— 有较弱的合法来源(例如路径来自 HALO_WORK_DIR=/root/.halo2 这类环境变量默认值,镜像名来自 README 的 docker run),需要人工确认
  • ❌ missing —— 什么都没有

D03(容器内挂载点)、D05(容器内端口)、D06(镜像)没有证据时,resolve 直接 返回非零退出码并列出阻塞项,不猜、不填默认值。间接证据(inferred)会连同答案一起 落进 draft spec——人工确认过的值必须能到达生成器,而不是停在报告里。产出三份文件: decision-report.md、draft-spec.json、resolve.json。

多服务 compose 按服务归属:compose 里每个服务在 draft spec 里各占一条,镜像、容器端口、 bind 挂载、环境变量、depends_on、运行时字段都只跟着自己的服务走。Dockerfile 里的变量归 主服务;sidecar(自带的数据库/缓存)的密码不会被并到应用服务上,反过来也一样。多服务时还会给 一条提示:如果面板上已经有对应的应用,考虑改用 dependencies 走面板托管(D10)。

实测:halo-dev/halo 的挂载点在 Dockerfile 里没有 VOLUME,但 HALO_WORK_DIR 默认值是 /root/.halo2;镜像只在 README 里出现——它把两条都标成 inferred 并给出出处,而不是停下来或者瞎猜。

运行时验证(remote)

SSH 冒烟(remote smoke)

静态校验和生成的 YAML 都不能告诉你"容器起不起来、端口通不通、应用找不找得到配置文件"。

p1 remote check                                    # 主机事实(内存/磁盘/已用端口/已有本地应用)
p1 remote smoke <包目录>                            # 完整一轮冒烟(用包自己的默认端口)
p1 remote smoke <包目录> --ports 10000              # 显式覆盖宿主端口
p1 remote smoke <包目录> --env OV_VLM_API_KEY=sk-…  # 用真实密钥覆盖表单默认值

它把某个版本目录上传到 /tmp/p1-smoke/<key>(不是 /opt/1panel/resource/apps/local, 不会碰到你已装的包),按安装表单的默认值渲染 .env,docker compose up -d, 然后轮询到"HTTP 有应答"或"healthcheck 明确 healthy"为止,抓日志、清理。

不传 --ports 时用的是包自己的表单默认端口——冒烟要验的就是这个包,不是 "在这台机器上换个端口能不能跑"。端口被占用时失败信息会点名 L008: 那不是包的缺陷,1Panel 安装时会提示换一个端口,SSH 冒烟用 --ports 指定即可。

判定分三类,退出码不同,便于接 CI:

结果退出码含义
PASS0容器 running 且端口有 HTTP 应答(或 healthcheck healthy)
PENDING3日志显示缺少用户密钥/凭据 → 是表单待填,不是包的缺陷
FAIL1其他失败;自动匹配 lesson 并给出根因

两个设计细节值得说明:端口被监听不等于就绪(应用可能先绑端口再花几十秒初始化), 所以主判据是 HTTP 应答,纯 TCP 监听只在 healthcheck 认可时才算通过;失败时保留远端目录 以便 p1 remote logs <dir> 复看,成功则连同容器和数据卷一起清掉。

面板安装(deploy / uninstall)

p1 deploy <包目录> [--param K=V] [--uninstall] [--cleanup-local] [--no-wait]
p1 uninstall <安装名>          # 或 --install-id N

和 remote smoke(SSH 直接起容器)不同,deploy 走的是真实用户路径:

  1. 把包放到面板的本地应用目录 /opt/1panel/resource/apps/local/<key>
  2. POST /apps/sync/local 让面板重新扫描本地应用
  3. POST /apps/search 找到应用(本地应用注册成 local<key>)
  4. GET /apps/detail/{appId}/{version}/{type} 取 appDetailId
  5. POST /apps/install 提交安装,参数以对象形式放进 params
  6. 轮询 POST /apps/installed/search,看面板自己记的 status 是否 Running

Running 只说明容器起来了,不代表服务能应答——端口映射写错、应用启动即崩、 初始化失败都会是 Running。加 --probe 就会在装完后从服务器本机敲一下端口: 不带值用安装参数里的 PANEL_APP_PORT_*(否则用表单默认值),也可以 --probe 15000 指定。判定与 remote smoke 共用:HTTP < 500 算有应答(401/403/404 也算), 否则回落到 TCP 监听判定;探针失败时 stage=probe、退出码 1,--uninstall 与 --cleanup-local 照样执行,不会在服务器上留坏安装或残留包。

这几步的端点与字段都是从运行中的 v2.2.4 上探测出来的(给接口发空 body,让面板的 参数校验器报出必填项),不是猜的。实测一轮:安装 → 容器 status=Running → 卸载, 约 10 秒(镜像已缓存)。

为什么两个都要有:remote smoke 验证"这个 compose 能不能起来", deploy 验证"面板能不能把它装出来"——表单渲染、依赖注入、防火墙放行这些环节 只有走面板才会被走到。

面板管理与 API 测试(panel)

打包之外,p1 panel 是一套全功能的 1Panel 管理台(2026-10 起按用户要求 解除"只读/够用即停"边界):1Panel v2 的 584 个 API 端点全部可调, 注册表由官方 OpenAPI 规范生成,不是逐条手抄。

p1 panel info                                  # 仪表盘:版本、负载、资源计数
p1 panel apps                                  # 已装应用(websites/ssl/databases/containers/cronjobs 同理)
p1 panel apps installed-search --param name=x  # 显式指定 action;--param 可叠加
p1 panel website create --data '{"primaryDomain": "x.com", "alias": "x"}'   # 写操作
p1 panel cronjobs del --param id=1 --yes       # 破坏性操作必须显式 --yes
p1 panel dashboard base --param ioOption=all --param netOption=all
p1 panel api [关键字]                           # 搜索 584 条端点注册表(级别/说明/验证标记)
p1 panel request GET /dashboard/base/os        # 原始调用(--raw 看完整信封)
  • 派发是数据驱动的:panel_api_data.yaml(来自 1Panel v2 OpenAPI 规范, _recon/panel_api_from_openapi.py 再生成),规范更新即跟着更新。
  • 三级安全:read / write / destructive。破坏性无 --yes 直接拒绝(不连面板); P1_PANEL_READONLY=1 锁掉一切非只读调用;write 默认放行——管理工具就该能管。
  • 列表端点自动分页;常用 7 个端点渲染成中文表格,其余 --table 通用网格或 JSON。
  • 路径参数用 --param key=value(同时填进路径模板与 payload),--data 直传完整 JSON。

1Panel v2 的 API 鉴权(踩过的坑)

1Panel v2.2.4 的接口约定和直觉不一样,而且三处错误返回同一条 {"code":401,"message":"API 接口密钥错误"},从响应上完全看不出问题在哪:

正确做法直觉上会写成
路由前缀/api/v2/.../api/v1/...(v2 里已不存在)
时间戳头1Panel-Timestamp1Panel-Time
Token 头md5("1panel" + api_key + 时间戳)明文 api_key

依据是上游 backend/middleware/session.go: panelToken == GenerateMD5("1panel" + global.CONF.System.ApiKey + panelTimestamp)。 p1 config set panel.* + p1 panel ping 已按这个实现(保留 v1 回退), lesson 记作 L007。

修复(fix)

p1 fix <包目录> --dry-run     # 先看会改什么
p1 fix <包目录> --check       # 改完立刻重新校验

fix 是幂等的:第二次跑会告诉你"没有需要修复的内容"。它做两类事:

机械修复(永远安全):CRLF/BOM 归一化为 LF、image 与端口映射加引号、 移除顶层 version:、补 labels.createdBy、补根 data.yml 的 recommend / memoryRequired、补空的 title/description、补齐 .env.sample 缺失的变量、把 logo 等比缩放到 180×180 透明底(不放大,小图居中, 支持官方语料里出现的全部位深、色彩类型与 tRNS 透明——567 个官方 logo 是 带透明背景的调色板 PNG(另有 2 个是带透明色的 RGB),漏掉它就是让透明底变成一块实色)。

它不会改写你已经写好的 title 和 description:title 是商店里的应用名, description 是人写的简介。官方 882 个应用里有 69 个(7.8%)把产品名放在 title、 一句话放在 shortDescZh,p1 check 对这种不一致只报 warn——替你把名字改掉才是破坏。

纪律修复(改变安装表单):把"可选且有可用默认值"的字段从表单里移出去, 同时在 compose 里把 ${VAR} 改成 ${VAR:-默认值},让变量仍然有效。这就是 "三十个输入框、六个必填"那个反模式的解法。

不做的事:需要判断的(哪些字段用户真的必须决定、标签该怎么翻译)会列入 需要人工处理 而不是替你猜。

那部分判断现在有个半自动出口:p1 form audit 把每个字段分成 keep(用户必须决定)/ hide(可收进 compose 默认值)/ review(表单超预算时, 必填带默认值但名字不属于官方约定的字段)/ unused(没人引用)/ must-fill (必填又没默认值),并写出计划骨架。判定依据是实测:官方 10640 个字段里 73% 就是 "必填 + 有默认值",所以那本身不是问题信号;名字命中官方约定(时区、路径、PUID/PGID、 端口、密钥、库名、引擎/模式、资源参数)的一律保留,判定规则在 rules/data/form-review.yaml, 改判断是改数据。 你只改 hide / keep 两个列表,p1 fix --plan plan.json 负责机械执行: 被隐藏的字段会在 compose 里拿到 ${VAR:-默认值},变量不会失效;必填且没有默认值的字段 会被拒绝隐藏(不会把"用户必须填"偷偷变成空值)。计划是幂等的,同一个 plan 跑两次等价。

实测效果(_recon/measure_fix.py 的原始输出):

包修复前修复后改动
应用 A1 fail + 9 warn0 fail + 3 warn5 处
应用 B0 fail + 7 warn0 fail + 2 warn6 处
应用 C0 fail + 15 warn0 fail + 2 warn12 处

(2026-09-20 在真实应用包上的实测记录,修复前后都用同一条命令复算 ——python _recon/measure_fix.py --baseline out/fix-backup-20260920。 数字按当时的规则版本记录,规则变化会让同一份包的数字变化,例如 I005 从 fail 降为 warn 之后,"修复前"那一列就少一个 fail、多一个 warn。 包名从略——这份 README 只需要证明"跑得通",不需要谁的包清单。)

生成(gen)

p1 resolve <仓库> --out ./out/app     # 1. 上游证据 → draft-spec.json
$EDITOR ./out/app/draft-spec.json     # 2. 人工确认证据、补一句简介
p1 gen ./out/app/draft-spec.json --out ./out --check   # 3. 生成 + 校验
p1 remote smoke ./out/app --ports 10000                # 4. 真机验证

核心纪律:安装表单是"决策清单",不是"配置转储"。

一个上游变量只有在两种情况下才出现在表单里:用户必须决定它(端口、对外 URL), 或者用户必须提供它(密钥)。其余全部写进 compose 默认值(${VAR:-default}), 并在 README 的「高级配置」里列出来供需要时修改。

这不是审美偏好,是实测差距:官方 1617 个版本的表单中位数 5 个字段、88% 标为必填; 而手写包很容易做出 30 个字段、必填只占 20% 的表单,用户反馈"有的要填有的不用填, 非常麻烦"。现在 gen 会把这个纪律强制执行,check 的 F 组规则负责事后把关。

生成物包含:根 data.yml(name 放产品名,title/description/shortDescZh 同一句简介 —— 这是 739/771 个官方应用的写法)、版本 data.yml、docker-compose.yml (引号、createdBy、1panel-network、上游 runtime 开关原样保留)、.env.sample (包含所有隐藏变量)、中英 README、logo 占位图、source-evidence.json, 以及 check 会提醒的 recommend / memoryRequired(默认 0,spec 可覆盖)—— 生成物本身就该是"不用立刻再 fix 一遍"的。

一个服务可以开多个端口:container_ports: [6099, 3001] 会生成两个 PANEL_APP_PORT_* 表单字段和两条端口映射(WebUI + 协议端口这类应用很常见), container_port 仍是单端口简写。

一个 spec 可以出多个版本目录:官方 882 个应用里 656 个(74%)是多版本, 版本之间的差别 709 对只是镜像标签。写成

"version": "latest",
"versions": [
  "latest",
  { "version": "1.4.2", "image": "example/app:1.4.2" },
  { "version": "1.3.0", "service": "cache", "image": "redis:6-alpine" }
]

清单第一项是商店默认展示的版本,根目录的 data.yml / README / logo 只写一份。 覆盖可用的键:image / container_port / container_ports / mounts / mount_targets / mount_host / env(与主版本合并)/ services(整组替换)。 多服务时覆盖默认落在第一个服务上;要改别的服务就写 "service": "<服务名>", 点错名字会被拒绝——不点名而 spec 又不止一个服务时,运行报告会写明这次改的是谁。

生成过程会明确告诉你哪些东西是它替你决定的:哪些变量被收进默认值、哪些翻译是 占位、logo 需要替换。

把"用户得自己做的事"写进 README:官方 882 份 README 里 54% 有参数说明表、30% 提到 用户自备的文件(模型、证书、插件)。spec 的 docs 块让生成器替你写这两节:

"docs": {
  "first_run": "启动前先把模型放进 ./models,否则容器会退出。",
  "parameters": {
    "PANEL_APP_PORT_HTTP": "面板入口端口,默认 8000;被占用时面板会提示换一个",
    "OV_VLM_API_KEY": "调用上游服务的密钥;留空则用本地模型"
  },
  "assets": [
    { "path": "./models/voice.pt", "target": "/app/models/model.pt",
      "why": "语音识别模型,容器不会自带", "source": "https://example.com/models" }
  ]
}

生成的 README 会多出「配置项」(变量|说明|默认值|必填)与「需要你自己准备的文件」 (宿主路径 → 容器内路径、为什么需要、从哪拿),first_run 覆盖默认的「首次启动」那句; 被收进默认值的隐藏变量在「高级配置」里逐条带说明,不再只是一串变量名。 三条校验兜底:变量名拼错、路径没被挂载、只给路径不给理由——都会在生成时报出来。

证据纪律:没有证据就不生成

gen 会先审计 resolve 写下的决策账本,再决定要不要写文件。D03(容器内挂载点)、 D05(容器内端口)、D06(镜像)没有证据时直接拒绝生成,退出码 2,且一个文件都不写:

FAIL D03    D03 数据要挂到容器内哪个路径? 没有上游证据
            → 补证据,或在 spec 里写 "acknowledged": {"D03": "理由"};停止,不要猜挂载点
FAIL D06    app: 没有 image
            → 镜像必须来自上游证据(README 的 docker run / 官方镜像页)

这三条是实测出来的分界线:它们的取值分别有 642 / 359 / 19 种,抄模板抄错时的表现是 "装得上、跑得起来、就是不对"——数据升级即丢、端口不通、镜像来路不明。阻塞范围存在 rules/data/decisions.yaml 的 blocking 字段里,resolve 与 gen 读同一份数据。

其余检查不阻塞,但会点名:

  • 漂移:spec 的镜像 / 端口 / 挂载点不在账本证据里,或环境变量名不在 D01/D02 里 (变量名拼错是最难查的一类:容器照常启动,值被静默忽略)。
  • 结构性缺口:声明了 mount_host 却没有容器内挂载路径的 service 会被拒—— 这种包"有数据目录"但什么都没挂进去。
  • 非阻塞决策缺证据:把 decisions.yaml 里 if_unknown 的处理方式原样打印出来。

人工可以显式承担决定,两种方式都会记进包内的 decision-audit.json:

# 1. 写进 spec(推荐:评审时能看到理由)
#    "acknowledged": {"D03": "上游 Dockerfile 没有 VOLUME,这个应用不写盘"}
# 2. 一次性放行
p1 gen ./out/app/draft-spec.json --allow-unresolved

decision-audit.json 与包一起交付:每条决策的状态、答案、证据缺口、谁批准了哪条, 评审的人不必再去翻对话记录。p1 gen --json 里也有同一份数据,方便接 CI。

依赖接入(D10)与脚本(D09)

依赖是"接线",不是"多几个输入框"。 官方 106 个版本用依赖选择器,主流形状是两步式 type: apps + child.type: service(72 个版本),一步式 type: service 有 53 个 (Redis 只有一种实例,天然用一步式)。两种形状都从语料量出来,放在 rules/data/dependencies.yaml。

"dependencies": [
  {
    "kind": "postgresql",
    "kinds": ["postgresql", "mysql"],
    "env": {
      "DATABASE_HOST": "${PANEL_DB_HOST}",
      "DATABASE_URL": "postgres://${PANEL_DB_USER}:${PANEL_DB_USER_PASSWORD}@${PANEL_DB_HOST}:${PANEL_DB_PORT}/${PANEL_DB_NAME}"
    }
  }
]

生成器据此产出选择器字段、把 ${PANEL_DB_*} 映射成应用自己的变量名,并只为真正被 引用到的面板变量生成输入框(PANEL_DB_HOST 由选择器提供,其余没用到的一个都不加)。 应用侧的变量名必须由你给出——那是 D01 证据,不是生成器能发明的东西。

p1 resolve 会先给线索:上游 compose / README 里像数据库接线的变量(DB_HOST、 DATABASE_URL、REDIS_*)会按 host / port / user / password / name / url 归类, 写进 draft-spec.json 的 dependency_hints 和决策报告的「D10 线索」一节。

脚本与首次启动配置(lesson L001:静态全绿、容器一起来就退出)有了两条正解:

"data_owner": "1000:1000",
"config_files": [
  { "path": "data/app.conf", "content": "database_url=${DATABASE_URL}\n" }
]

data_owner 生成 init.sh / upgrade.sh,按挂载目录 mkdir -p + chown -R; config_files 把模板放进版本目录,并在中英文 README 的「首次启动 / First start」里 提醒用户先编辑。更复杂的逻辑用 scripts: {init, upgrade, uninstall} 原样写入 (自动补 #!/bin/bash、统一 LF)。

这两件事同样受证据约束:声明 user / data_owner 却拿不出 D07 证据、带了依赖而 D10 说"未检测到"、带了脚本而 D09 无证据——都会在证据审计里被点名(lesson L004)。

配方(recipes):把成套写法变成一条命令

p1 resolve <仓库> --out ./out/app                                  # 上游证据 + D10 线索
p1 recipe apply panel-postgres ./out/app/draft-spec.json --write   # 一键接线
p1 gen ./out/app/draft-spec.json --check                           # 生成 + 校验

配方只填空的那部分是结构:面板变量怎么映射到应用变量、选择器什么形状、哪些变量得由 包自己声明。应用侧的变量名一律来自 resolve 的 dependency_hints(或 --bind 显式给出), 配方不发明变量名,也不替人决定要不要接依赖。

五条内置配方:panel-postgres(PostgreSQL / MySQL / MariaDB,两步式选择器)、 panel-redis、panel-mongo、panel-minio、panel-db-and-cache(复用前两条)。 p1 recipe show <id> 展开角色表; p1 recipe apply 默认只预览,加 --write 写回 spec;--bind host=DB_HOST 补 hints 没覆盖的 角色。hints 的 subjects 会区分 postgresql 与 redis 这类依赖主体,所以 REDIS_URL 不会被绑到 Postgres 的连接串模板上。

配方里的形状都有出处:p1 recipe show <id> 会列出这条配方依据的官方写法 (每条带"多少个官方应用采用"、示例来自哪个包、以及一小段真实片段), 例如 form-field/apps-child-service(41 个应用)就是两步式选择器的来源; p1 patterns show <id> 反过来会告诉你"这条写法被哪些配方用着"。

当前状态

完整流程已经闭环:

p1 resolve <仓库> --out ./out/app        # 1. 上游证据 + 10 项生成决策
# 人工确认 draft-spec.json
p1 gen ./out/app/draft-spec.json --check # 2. 生成 + 静态校验
p1 depth ./out/app --corpus <官方apps>    # 3. 确认不是模板复制品
p1 remote smoke ./out/app --ports 10000  # 4. SSH 冒烟:起容器、探针、抓日志
p1 deploy ./out/app --param ... --uninstall  # 5. 走面板 API 正式安装并验证

修复已有包:p1 fix <包目录> --check(幂等,见上文)。

命令一览(16 个):resolve / recipe / gen / check / form / fix / depth / patterns / ports / remote / deploy / uninstall / config / panel / rules / selftest——其中前 8 个(到 patterns)离线可用,后面几个是可选的面板/SSH 能力。

文档

文档内容
本文件用户视角:装什么、怎么用、为什么这么设计
docs/architecture.md接手先读:模块地图、证据链、扩展点、测试策略
docs/status.md工具状态:命令清单、规则与数据规模、实测数据、验证记录、已知限制、维护手册
docs/generation-depth.md为什么模板化生成会做出重复的包(实测分布 + 判定口径)
docs/patterns.md22 条官方真实写法的教程(p1 patterns export 生成)
CHANGELOG.md每个版本改了什么、为什么改

设计约束

没有官方 Docker 证据就不猜镜像、端口、卷、UID/GID。高风险运行时权限先保留并 标注风险,不为了扫描器好看而删。云服务器上做运行时验证时只用已确认放行的端口 段,避免把安全组问题误判成打包问题。

它不做什么

这条界线值得写下来,因为它划错时工具会开始骗人:

  • 不把在线步骤变成必需。 面板与 SSH 是可选的辅助;核心(进 GitHub 地址、出包) 完全离线可用,已经有一条测试盯着这件事(配置指向不存在的文件时, gen / check / form audit / depth 必须照常通过)。
  • 不替你挑宿主端口。 端口是包自己的决定(表单默认值),冲突由 1Panel 在安装表单里 提示、由 Compose 报错。工具不会为了"跑通"而在服务器上另找一个空闲端口来装—— 那样测的就不是这个包了。remote smoke --ports 是显式覆盖,不是自动兜底。
  • 不为某一台服务器调优。 端口区间、安全组放行情况、镜像缓存都随机器和时间变化; 工具只根据它当场读到的这台主机的事实做事(p1 remote check),不预置任何 服务器专属常量。
  • 不假装知道上游没写的东西。 镜像、容器端口、挂载点没有证据就拒绝生成; 应用自己的变量名要么来自证据、要么由你给。
  • 不做需要判断的事。 哪些字段用户真必须填、标签怎么翻译、要不要接面板依赖, 工具列清单、给依据,但不替你决定(p1 form audit + --plan 就是为这条设计的)。

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 2

Native requirements
2 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
pyyaml
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pyyaml>=6.0runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.10",
  "evaluation": "NOT_EVALUATED"
}
pytest>=8.0; extra == "dev"runtime

Distribution files

2 collected
1panel_toolkit-0.22.0-py3-none-any.whl

bdist_wheel · 206.61 KB · Python py3

Source file
1panel_toolkit-0.22.0.tar.gz

sdist · 299.05 KB · Python source

Source file

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 15:16 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:b95da7db3497125ac15bc4924d7f0aaf2955d958246bb38e46f2cd7a55c1257a

Other collected versions

View all collected versions →