Skip to content

Capbox CLI #44

@fwerkor

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.jsonstate.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 读写

你只要选语言即可。

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