-
-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Describe the feature
以下内容由伟大的GPT整理:
下面是《Capbox CLI 全部规划要求(v0.2 定稿)》。这份就是后续实现、联调、文档的唯一依据;我把你已确认的点(双 entry、.cpk、四大功能、人机双用、网络简化、username→slug、resolve 输出 IP:PORT)全部收拢并写成可执行规格。
0. 总目标与范围
Capbox 是 CapOS 的容器应用管理器(App Manager),同时面向:
👤 终端用户:在 shell 中管理自己账号下的容器应用
🤖 WebPanel/WebDesktop:通过
--json获取稳定结构化输出做 UI/路由
Capbox 的 v0.2 只实现四大能力:
安装
.cpk应用包删除应用
列出本用户应用
查询某应用信息(含 entry、存储占用等)
认证不由 capbox 负责;capbox 不接收
--user:什么用户执行命令就是谁。
1. 与 caprunner 的关系(底层对接)
后续会实现 caprunner:它是 docker 的“别名”,CLI 用法完全等同 docker,但与系统 docker 互不干扰。
capbox 内部使用 caprunner 来完成容器生命周期、网络、inspect 等操作。
capbox 不需要暴露 docker 级别的所有功能;它只做“应用”抽象的 install/remove/list/info/resolve。
2. 人机双用输出模型(重要)
2.1 默认输出给人看
不加参数时,stdout 输出人类可读信息(表格/简短描述)
错误信息输出到 stderr
2.2 机器可读必须显式请求
--json:输出稳定 JSON(供 WebPanel/WebDesktop 解析)--quiet:仅输出关键结果(例如 resolve 的IP:PORT单行)
规则:
没有
--json→ 不输出 JSONWebPanel/WebDesktop 永远只用
--json/--quiet,不解析人类输出。
3. 命名与隔离规则(username 不限制,内部做 slug)
3.1 username 不做任何限制
Linux 用户名可能包含各种字符;capbox 不改变也不限制它。
3.2 资源命名统一使用 slug(username)
为保证网络/compose project 等资源名合法且稳定,capbox 内部定义:
user = 当前执行用户名(从 UID→username 获取)slug(user):用于网络名、compose project 名、容器/卷前缀等
推荐生成方式(稳定且不会冲突):
slug = "u-" + hex(sha1(username))[0:10]
可选美观增强(不强制):在 slug 前加一个可读前缀再带 hash。
4. 网络与 compose 策略(按你最终确定的“简化版”)
4.1 每用户一个 compose
每个用户维护一个:
~/.capos/caprunner-compose.ymlcapbox install/remove 通过修改该文件实现幂等编排(像 compose 一样)
4.2 每用户一个 bridge network(不指定子网)
网络名:
capnet-<slug(username)>(你希望“capnet-username 美观”,但为了合法与稳定,使用 slug)不配置 subnet / ipam:完全交给 caprunner/dockerd 自动分配 IP / DNS
用户间网络隔离来自“不同 network”,不依赖固定网段
4.3 hostname
每个 app 容器在该用户网络内的 hostname/别名用
appId即使不同用户都装同一个 app(如 jellyfin),也不会冲突:网络不同。
5. 应用(App)抽象:双 entry
一个应用可定义两个入口(都可为空):
entry.access:访问点(桌面/日常使用入口)entry.manage:管理点(“应用设置”入口,WebDesktop 可在设置中统一入口,不必放桌面)
每个 entry 推荐字段:
type:v0.2 先支持httpport:容器内端口path:可选,默认/
6. .cpk 应用包规格(v0.2)
6.1 .cpk 包含内容
必须:
manifest.json可选:icon、readme、离线镜像(image.tar)、模板片段(compose.tpl.yml)等
6.2 manifest.json 必须支持的元数据字段(覆盖你提到的)
必须覆盖:
介绍、图标
access/manage 入口端口
容器权限
挂载点
额外端口映射
privileged 与否
是否要求 host 网络(注意 v0.2 仍允许字段存在,但默认会安全限制,见第 11 节)
建议结构(v0.2):
{ "appId": "jellyfin", "name": "Jellyfin", "version": "1.0.0", "description": "...", "icon": "icon.svg","entry": {
"access": { "type":"http", "port": 8096, "path": "/" },
"manage": { "type":"http", "port": 8920, "path": "/" }
},
"runtime": {
"privileged": false,
"networkMode": "capnet",
"mounts": [
{ "source": "DATA", "target": "/data", "mode": "rw" }
],
"extraPorts": [
{ "containerPort": 1900, "proto": "udp", "expose": false }
],
"capabilities": [],
"devices": []
}
}
7. CLI 命令集合(v0.2)
全局语法:
capbox [--json] [--quiet] <command> [args...]
v0.2 命令:
capbox install <pkg.cpk>capbox remove <appId>capbox listcapbox info <appId>
注意:虽然 WebPanel
/app/路由会用“解析目标”,但 v0.2 把解析能力融合进info(见 9.2),不单独新增resolve命令也可以;如果你仍希望保留resolve(便于脚本),可以作为info --quiet access的等价别名。
8. capbox list 规范
默认(人类输出)
表格:
APP ID / STATUS / ACCESS / MANAGEaccess/manage 不存在或不可用显示
-
示例:
APP ID STATUS ACCESS MANAGE
webdesktop running http://(ip):2000/ http://(ip):2020/
jellyfin stopped - -
--json 输出(稳定)
{
"apps": [
{
"appId": "webdesktop",
"name": "Web Desktop",
"version": "0.1.0",
"status": "running",
"entry": {
"access": { "type":"http", "port":2000, "path":"/", "target":"10.0.5.12:2000" },
"manage": { "type":"http", "port":2020, "path":"/", "target":"10.0.5.12:2020" }
}
}
]
}
9. capbox info <appId> 规范(你要求的“查询应用信息”)
9.1 info 必须包含的信息
app 基本信息:name/version/description/icon
状态:running/stopped/error/installing/removing
entry 两类(access/manage):
容器内端口/路径
解析后的 target(IP:PORT)(仅当可访问)
存储占用:
用户数据目录大小(例如
)/.capos/data/<appId>可选:容器/镜像占用(caprunner inspect 能拿到就给)
运行配置摘要:
privileged / networkMode / mounts / extraPorts / capabilities / devices
运行实体标识:
compose project、service name、container id(可选但利于调试)
9.2 target 的计算规则(解决“两个用户都装 jellyfin”)
target 不允许输出
jellyfin:8096这类歧义值给宿主网关target 必须是
container_ip:port计算方式:
以当前用户的 compose project/网络定位容器
inspect 获取该容器在
capnet-<slug>上的 IPtarget =
<ip>:<entry.port>
--json 输出示例
{
"appId": "jellyfin",
"name": "Jellyfin",
"version": "1.0.0",
"status": "running",
"entry": {
"access": { "type":"http", "port":8096, "path":"/", "target":"10.0.8.23:8096" },
"manage": { "type":"http", "port":8920, "path":"/", "target":"10.0.8.23:8920" }
},
"storage": {
"dataBytes": 123456789,
"otherBytes": 0
},
"runtime": {
"networkName": "capnet-u-3fa2c1d9ab",
"project": "capos-u-3fa2c1d9ab",
"privileged": false,
"networkMode": "capnet",
"mounts": [{"source":"DATA","target":"/data","mode":"rw"}],
"extraPorts": [],
"capabilities": [],
"devices": []
}
}
--quiet(关键值输出)
建议支持:
capbox info <appId> --quiet access→ 输出IP:PORT或空(非 0)capbox info <appId> --quiet manage→ 同上
(如果你更喜欢单独命令,也可以加 capbox resolve <appId> [access|manage] --quiet,但 v0.2 用 info 就够。)
10. capbox install <pkg.cpk> 规范
10.1 行为要求
解析
.cpk的 manifest.json校验 appId 合法(
[a-z0-9._-]{1,64})将应用注册进本用户应用集合
更新
/.capos/caprunner-compose.yml使用 caprunner 启动/更新服务(创建网络、拉取/导入镜像、up)
10.2 输出
默认:过程性人类输出(类似 docker)
--json:返回稳定结果
{ "ok": true, "appId": "xxx", "version": "..." }
11. capbox remove <appId> 规范
11.1 行为要求
停止并移除该 app 对应的 service/container(通过 caprunner/compose down 单服务或等价方式)
从 compose 文件中移除该 service 配置
从
~/.capos/apps/<appId>/移除元数据用户数据目录是否删除:
v0.2 默认 不删除数据(安全与可恢复)
以后可加
--purge才彻底删除
11.2 输出
默认:人类可读
--json:{ "ok": true }
12. 退出码(稳定约定)
| code | 含义 |
|---|---|
| 0 | 成功 |
| 2 | 参数/用法错误 |
| 3 | app 不存在 / 不可访问(例如 info 找不到、target 无法解析) |
| 4 | caprunner/dockerd 错误 |
| 5 | .cpk/manifest 错误 |
| 10+ | 内部错误 |
13. 安全底线(capbox 侧)
即使网络策略简化,也必须守住:
capbox 永远只操作当前执行用户的资源(按目录/compose project/网络名隔离)
target只从运行态推导,不可由用户输入控制manifest 的高风险字段默认要有安全策略(v0.2 推荐默认拒绝):
privileged=truenetworkMode=host挂载敏感路径(
/,/etc,/proc,/sys,/var/run/*等)设备直通(devices)
-(若后续允许)必须加显式“管理员开关”或白名单机制
14. WebPanel/WebDesktop 对接约定(简洁明确)
列出应用:
capbox list --json查询信息/入口:
capbox info <appId> --json路由反代时取:
日常访问:
entry.access.target设置入口:
entry.manage.target
桌面应用选择:只能从 list/info 的 appId 中选择
两个用户都装 jellyfin:不会冲突(每用户 project/network 独立,target 不同)
如果你愿意,我可以把这份规格再补一页“内部落盘结构”(~/.capos/apps/<appId>/manifest.json、state.json、compose 生成规则、并发锁文件),这样你写 capbox 时不会在“状态放哪、compose 怎么幂等”上反复纠结。
Additional info
太好了 👍
下面就是 《Capbox 内部落盘结构与实现规范(v0.2)》。
这份是“写代码时直接照着放文件、读文件、改文件”的级别,目的是让你在实现 capbox 时 不再纠结状态放哪、compose 怎么维护、并发怎么处理。
1️⃣ 总体原则(先定死)
所有状态都落盘(capbox 本身无 daemon、无内存状态)
用户级隔离靠目录 + compose project + network 名
compose 文件是“真实运行态的唯一来源”
manifest ≠ state
manifest:应用“是什么”
state:应用“现在怎样”
capbox 可被并发调用,但同一用户同一时间只允许一个写操作
2️⃣ 用户级目录结构(~/.capos)
对每个 Linux 用户,在其 HOME 下维护一个 .capos:
~/.capos/
├── caprunner-compose.yml # ⭐ 每用户唯一 compose(真实运行态)
├── lock # 文件锁(flock)
├── apps/
│ └── <appId>/
│ ├── manifest.json # 从 .cpk 解包而来(只读)
│ ├── state.json # capbox 维护的运行态
│ └── meta.json # 可选:安装时间、来源等
├── data/
│ └── <appId>/ # 用户数据目录(默认不删)
└── tmp/
└── install-XXXX/ # install 临时目录
这个结构满足:
list/info 只读 manifest/state
install/remove 修改 compose + state
webpanel 只通过 capbox 间接访问
3️⃣ compose 文件规范(caprunner-compose.yml)
这是 capbox 的“真相来源”。
3.1 文件头(固定结构)
version: "3.9"name: capos-<userSlug>
networks:
capnet:
driver: bridge
name:→ compose project name(避免容器名冲突)network 名 统一叫
capnet(用户级隔离已经靠 project)
实际 docker network 全名会是:
capos-<userSlug>_capnet(docker 自动加前缀)
3.2 service 生成规则(每个 app 一个 service)
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: capos-<userSlug>-jellyfin
hostname: jellyfinnetworks: - capnet volumes: - ~/.capos/data/jellyfin:/data:rw restart: unless-stopped
硬性规则:
| 项目 | 规则 |
|---|---|
| service 名 | appId |
| hostname | appId |
| network | 永远是 capnet |
| volumes | 只能映射到 ~/.capos/data/(除非后续放宽) |
| privileged | v0.2 默认禁止 |
| network_mode | v0.2 默认禁止 host |
install/remove 时:
先拿锁
修改 compose
调 caprunner
更新 state
再释放锁
7️⃣ capbox install 实现流程(逐步)
capbox install xxx.cpk
获取用户锁
解包 cpk →
~/.capos/tmp/install-XXXX校验 manifest.json(appId、entry、runtime)
若 appId 已存在 → 错误
写入:
apps/<appId>/manifest.json初始化
state.json(status=installing)
更新 compose:
若 capnet 不存在 → 加 networks
新增 service(appId)
执行:
caprunner compose -f ~/.capos/caprunner-compose.yml up -d <appId>inspect 容器:
拿 containerId
拿 capnet 上 IP
更新 state.json:
status=running
填 entry.access/manage.target
释放锁
8️⃣ capbox remove 实现流程
capbox remove <appId>
获取用户锁
若 app 不存在 → exit 3
更新 state.json:status=removing
执行:
caprunner compose -f ~/.capos/caprunner-compose.yml rm -sf <appId>从 compose 中删除该 service
删除:
apps/<appId>/manifest.jsonapps/<appId>/state.json
保留
data/<appId>/释放锁
9️⃣ capbox list 实现逻辑
遍历
~/.capos/apps/*/读取 manifest.json + state.json
status 以 state.json 为准
entry.target 若缺失或 status!=running → 置空
🔟 capbox info / resolve 实现逻辑
info
读取 manifest + state
若 status=running:
可重新 inspect IP(可选,或信任 state)
计算 data 目录大小
输出 access/manage 两个 entry
quiet access/manage
若不存在或 status!=running → exit 3
stdout 输出
IP:PORT
1️⃣1️⃣ 为什么这套结构“稳”
compose 是真相:容器真实状态由 caprunner 保证
state.json 是缓存 + UI 友好层
manifest 是声明式、不被污染
用户隔离天然成立
两个用户装 jellyfin 永不冲突
1️⃣2️⃣ 下一步你可以直接做什么
你现在可以直接:
新建
capboxrepo写 CLI 参数解析(install/remove/list/info)
先 stub 掉 caprunner(echo 命令)
把目录/compose/state 跑通
再接真 docker / caprunner
如果你愿意,下一步我可以帮你 直接写一个 capbox 的最小骨架代码结构(C++ or Go or Shell),包括:
参数解析
锁
compose 读写
state.json 读写
你只要选语言即可。
Metadata
Metadata
Assignees
Labels
Projects
Status