快速开始
Kuruwa 为一个项目提供常驻主 daemon、可定制的执行环境和终端访问。管理员在一个项目目录中启动 daemon,按持久配置开放租户入口;租户只能看到并操作自己被授权的实例和终端。本文按已实现行为给出最短可照抄流程:Nix 构建 → 默认后台 serve → host 实例与任务输出 → 租户入口与 grant → 关闭。
准备
需要 Linux x86_64、Nix flakes,以及可用的 bubblewrap(仅 bwrap 弱隔离示例需要)。已实现并验证的发布产物是 x86_64 Linux Nix 包;其他发行版和 UID/namespace 组合需要自行验证。
在 Kuruwa 仓库根目录构建。flake 文件尚未被 Git 跟踪时,用 path: 形式避免为了构建而暂存文件:
nix build .#kuruwa
# 或
nix build path:$PWD#kuruwa
export PATH="$(readlink -f result)/bin:$PATH"
kuruwa --version
kuruwa --help
包内的 kuruwa 已带补丁版 RMUX、bash、coreutils 和 bubblewrap。显式设置 KURUWA_RMUX_BIN 时以显式值为准,无效路径会明确报错,不会静默回退。
1. 启动后台主 daemon
在任意项目目录执行,而不是必须留在 Kuruwa 仓库:
mkdir -p ~/kuruwa-demo
cd ~/kuruwa-demo
kuruwa serve
kuruwa --json tenant list
默认项目根是 $PWD/.kuruwa,不会向父目录搜索。首次 serve 会创建 0700 根目录、config.toml、daemon.log 与 init/init-host.sh、init/init-bwrap.sh,只写缺失文件,不覆盖已有内容,也不自动创建实例、终端或 grant。
默认配置包含:
- 定义
host:直接使用主 daemon 的权限环境; - 定义
bwrap:bubblewrap 弱隔离示例; - 租户
alice:获准创建bwrap实例; - 租户
bob:没有实例创建权,但仍可接收指定终端的 grant; host定义只供管理入口使用。
serve 默认后台启动:父 CLI 等到管理入口真正可完成无副作用请求后才返回 status: started;已有健康 daemon 时返回 already_running,不会重复启动。日志追加到 .kuruwa/daemon.log。需要前台常驻或交给进程管理器时使用:
kuruwa serve --foreground
普通客户端只解析入口,不会创建项目根,也不会自动启动 daemon。入口选择顺序是 --socket、KURUWA_SOCKET、<root>/admin.sock;显式入口失效直接报错,不回退默认入口。
2. 创建 host 实例并读取任务输出
kuruwa --json instance create --definition host --name ops
kuruwa --json instance list
kuruwa --json run --instance ops --name task --interval 5 -- \
/bin/sh -c 'echo TASK_OK'
kuruwa --json transcript task
instance create 返回时实例已经可用。run 在指定实例中创建终端并等待一个有界窗口;上面的命令正常退出后,transcript 输出类似:
{"content":"TASK_OK\r\n","name":"task"}
run 的 --interval 只是本次等待窗口,不是任务寿命或自动清理阈值。终端退出码保留在数据里;命令本身不会因为终端退出非零而失败。
也可以创建一个持续运行的交互 shell:
kuruwa --json start --instance ops --name shell -- bash
kuruwa --json send shell -- echo hello
kuruwa --json send shell --key -- Enter
kuruwa --json screen shell
kuruwa --json tail shell --lines 20
send 的文本模式只把 argv 用单个 ASCII 空格连接,不追加换行;要执行输入必须另外发送 Enter。screen 读取当前视口,tail 渲染历史最后 N 行,transcript 保留后端当前可提供的原始 PTY 输出。后端输出环有容量限制,不承诺自创建以来的完整日志。
3. 从租户入口创建弱隔离实例
租户身份来自连接进入哪个 socket,而不是请求里自报的名字。租户进程应只拿到自己的入口:
ALICE_SOCKET="$PWD/.kuruwa/tenants/alice.sock"
kuruwa --json --socket "$ALICE_SOCKET" instance create \
--definition bwrap --name alice-lab
kuruwa --json --socket "$ALICE_SOCKET" run \
--instance alice-lab --name probe --interval 5 -- \
/bin/sh -c 'echo TASK_OK_FROM_ALICE; echo KURUWA_BWRAP=$KURUWA_BWRAP'
kuruwa --json --socket "$ALICE_SOCKET" transcript probe
预期能在原始输出中看到 TASK_OK_FROM_ALICE,且 KURUWA_BWRAP=1。租户也可以在自己的实例里继续 start、run 终端,但看不到管理入口,也不能创建未获准定义。
管理员可以只把一个终端交付给另一个租户:
kuruwa --json grant --tenant bob --terminal shell
kuruwa --json --socket "$PWD/.kuruwa/tenants/bob.sock" status shell
kuruwa --json revoke --tenant bob --terminal shell
grant 只包含该终端的等待、状态、读、写和 resize 权限,不包含实例控制权、创建新终端或 attach。撤回立即拒绝后续访问并关闭相关等待请求,但不会终止终端,也不会回滚已经产生的副作用。
4. 关闭
kuruwa --json shutdown
shutdown 停止接受新请求,结束受管终端的会话,销毁实例并尽力调用一次 cleanup,删除本次运行创建的 socket 和实例目录。config.toml、daemon.log 与 init/ 保留,下一次 serve 会重新加载配置。实例、终端、输出和临时 grant 都属于本次运行,不会跨重启恢复。
如果 cleanup 失败,shutdown 会以 exit 1 报告失败并保留可识别残留;下一次 serve 会拒绝该 runtime,要求人工检查,不会盲目删除或杀进程。实例 daemon 异常退出时也不会自动重建,相关终端转为 orphaned 供诊断。
权限与隔离边界
- **host 实例没有额外隔离。**其中的 shell 拥有主 daemon 启动环境的实际权限。高权限终端应放在管理端拥有、只 grant 给受信租户的实例中。
- **bwrap 示例是弱隔离。**它用 mount namespace 隐藏 runtime 路径并提供独立工作目录,但不是同 UID 安全边界;同 UID 进程仍可能通过
/proc、ptrace 或开放 FD 旁路。需要更强隔离时,应额外配置用户、PID/mount namespace、cgroup/seccomp 和入口挂载。 - **0600 socket 权限只限制文件系统访问。**不同 OS 用户之间的隔离需要部署者安排目录所有权、namespace/容器边界和入口暴露。
- **授权只属于当前运行。**grant 绑定终端的内部身份;同名新对象不会继承旧 grant。
下一步
- 核心概念:主 daemon、沙盒实例、终端、租户与临时授权的准确含义。
- Nix 安装:flake 输入、包内布局、安装配置和 bwrap 内的命令路径。
- 项目根与后台服务:完整日常管理流程、初始化脚本、弱隔离示例和重启边界。
- CLI 与授权契约:命令表、JSON 结构、退出码、权限矩阵和初始化/清理约定。
- 下载示例:host 初始化脚本、bwrap 初始化脚本、示例配置。