Skip to content

Capbox CLI #44

Description

@fwerkor

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 只实现四大能力:

  1. 安装 .cpk 应用包

  2. 删除应用

  3. 列出本用户应用

  4. 查询某应用信息(含 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 → 不输出 JSON

  • WebPanel/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.yml

  • capbox 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 先支持 http

  • port:容器内端口

  • 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 命令:

  1. capbox install <pkg.cpk>

  2. capbox remove <appId>

  3. capbox list

  4. capbox info <appId>

注意:虽然 WebPanel /app/ 路由会用“解析目标”,但 v0.2 把解析能力融合进 info(见 9.2),不单独新增 resolve 命令也可以;如果你仍希望保留 resolve(便于脚本),可以作为 info --quiet access 的等价别名。


8. capbox list 规范

默认(人类输出)

  • 表格:APP ID / STATUS / ACCESS / MANAGE

  • access/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

  • 计算方式:

    1. 以当前用户的 compose project/网络定位容器

    2. inspect 获取该容器在 capnet-<slug> 上的 IP

    3. target = <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=true

    • networkMode=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️⃣ 总体原则(先定死)

  1. 所有状态都落盘(capbox 本身无 daemon、无内存状态)

  2. 用户级隔离靠目录 + compose project + network 名

  3. compose 文件是“真实运行态的唯一来源”

  4. manifest ≠ state

    • manifest:应用“是什么”

    • state:应用“现在怎样”

  5. 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: jellyfin

networks:
  - 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
  1. 获取用户锁

  2. 解包 cpk → ~/.capos/tmp/install-XXXX

  3. 校验 manifest.json(appId、entry、runtime)

  4. 若 appId 已存在 → 错误

  5. 写入:

    • apps/<appId>/manifest.json

    • 初始化 state.json(status=installing)

  6. 更新 compose:

    • 若 capnet 不存在 → 加 networks

    • 新增 service(appId)

  7. 执行:

    caprunner compose -f ~/.capos/caprunner-compose.yml up -d <appId>
    
  8. inspect 容器:

    • 拿 containerId

    • 拿 capnet 上 IP

  9. 更新 state.json:

    • status=running

    • 填 entry.access/manage.target

  10. 释放锁


8️⃣ capbox remove 实现流程

capbox remove <appId>
  1. 获取用户锁

  2. 若 app 不存在 → exit 3

  3. 更新 state.json:status=removing

  4. 执行:

    caprunner compose -f ~/.capos/caprunner-compose.yml rm -sf <appId>
    
  5. 从 compose 中删除该 service

  6. 删除:

    • apps/<appId>/manifest.json

    • apps/<appId>/state.json

  7. 保留 data/<appId>/

  8. 释放锁


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️⃣ 下一步你可以直接做什么

你现在可以直接:

  1. 新建 capbox repo

  2. 写 CLI 参数解析(install/remove/list/info)

  3. 先 stub 掉 caprunner(echo 命令)

  4. 把目录/compose/state 跑通

  5. 再接真 docker / caprunner

如果你愿意,下一步我可以帮你 直接写一个 capbox 的最小骨架代码结构(C++ or Go or Shell),包括:

  • 参数解析

  • 锁

  • compose 读写

  • state.json 读写

你只要选语言即可。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

  • Status
    In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions