本页目录
  1. 准备
  2. 1. 启动后台主 daemon
  3. 2. 创建 host 实例并读取任务输出
  4. 3. 从租户入口创建弱隔离实例
  5. 4. 关闭
  6. 权限与隔离边界
  7. 下一步

快速开始

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。

下一步