Kuruwa 部署与 M5 演练
本文面向首次部署 Kuruwa 的人工管理员。它说明如何构建固定的 RMUX 后端、启动主 daemon(默认后台,也可前台)、开放租户入口、使用终端与原生 attach,并给出 M5 完整流程的实测摘要。范围是 Linux 上的本地 Unix socket 控制面;不包含网络监听、agent 框架集成或崩溃恢复。
1. 前提与构建
已验证环境:Linux、bubblewrap 0.12、项目提供的 Rust/devenv 环境。其他 Linux 发行版和 UID/namespace 组合需要部署者自行验证。
固定输入:
- 仓库根目录:
/path/to/kuruwa - RMUX 版本:0.8.0 + listener 补丁
- 固定产物:
target/rmux/bin/rmux、target/rmux/bin/rmux-daemon - 产品二进制:
target/debug/kuruwa(或 release 构建)
cd /path/to/kuruwa
scripts/build-rmux.sh --fresh # 首次或固定输入变化时;需要网络下载固定源码
cargo build
scripts/build-rmux.sh 不读取 /tmp/rmux-src,输出固定在 target/rmux/。生产命令行使用 target/rmux/bin/rmux,不要用其他旧的 RMUX 二进制冒充。
2. 固定示例路径与临时路径
仓库中的固定示例:
examples/kuruwa.toml
examples/init-host.sh
examples/init-bwrap.sh
examples/kuruwa.toml 中的 init 路径是相对配置文件所在目录的。它假定配置放在仓库根目录;直接以 --config examples/kuruwa.toml 使用时,配置目录是 examples/,路径会变成 examples/examples/...。因此首次部署应复制到项目根目录,或按实际位置调整路径:
cp examples/kuruwa.toml ./kuruwa.toml
M5 自动化测试使用 $M5_ROOT 下的临时配置、runtime 和 cleanup 日志(例如 /tmp/kuruwa-.../)。这些临时路径与固定示例路径不同,测试结束会删除自己的临时目录。不要把临时 ID、cleanup 日志或测试目录当成可发布的固定路径。
3. 配置与可信定义
示例:
[[definitions]]
name = "host"
init = ["bash", "examples/init-host.sh"]
[[definitions]]
name = "bwrap"
init = ["bash", "examples/init-bwrap.sh"]
[[tenants]]
name = "alice"
definitions = ["bwrap"]
[[tenants]]
name = "bob"
definitions = []
init 和可选 cleanup 是非空 argv,原样 exec,不经过 shell 拼接。配置目录是它们的工作目录:
-
init以可信程序建立最终环境。它可以用任意语言/程序,最终必须exec最终环境中的 patched RMUX:exec "$KURUWA_RMUX_BIN" --__internal-daemon "$KURUWA_INTERNAL_SOCKET" \ --listener-fd "$KURUWA_LISTENER_FD" -
KURUWA_LISTENER_FD只传给init,是外侧预绑定的监听 FD;cleanup不接收它,也不接收 listener/已接受连接 FD。 -
KURUWA_INTERNAL_SOCKET是逻辑 endpoint,不等于外侧真实 socket 路径。真实外侧路径不会进入初始化环境。 -
cleanup在正常销毁、--force销毁、shutdown 和创建失败时最多调用一次;失败必须报告,不能宣称已清理。 -
定义和 init/cleanup 脚本是可信配置。只有管理员可以写这些文件;普通租户不应获得修改权限。
租户声明是持久的;实例、终端、输出和临时 grant 只属于当前主 daemon 进程,重启后不恢复。
4. 启动主 daemon
cd /path/to/project
export KURUWA_RMUX_BIN=/path/to/kuruwa/target/rmux/bin/rmux
# 默认:项目根为 $PWD/.kuruwa,后台启动,父 CLI 就绪后返回
/path/to/kuruwa/target/debug/kuruwa serve
# 前台常驻(调试或交给进程管理器时)
/path/to/kuruwa/target/debug/kuruwa serve --foreground
# 显式配置和 runtime 仍可用
/path/to/kuruwa/target/debug/kuruwa serve \
--config /srv/kuruwa/kuruwa.toml --runtime /srv/kuruwa/run
首次使用缺省配置时,serve 创建 0700 项目根与 init/,只写缺失的
config.toml、init/init-host.sh、init/init-bwrap.sh,不覆盖已有文件,也
不创建实例、终端或 grant。显式 --config 不存在时直接失败,不会替用户生成任意
指定配置。
后台模式用当前可执行文件启动一个 --foreground 子进程,stdin 为 null,
stdout/stderr 追加到 <root>/daemon.log,并在新 session 中运行。父进程等待
管理入口可完成无副作用请求后返回 status: started;已有健康 daemon 时返回
already_running 且不重复启动。失败时只终止自己启动的子进程并有有限超时;没有
pid 文件、锁、token 或恢复系统。
daemon 的日志由 tracing subscriber 统一写 stderr(后台即 daemon.log),默认包含
时间、级别与模块;可用标准 RUST_LOG 调整过滤,未设置时默认 info。事件只带已有
实例/终端 ID、操作、阶段、耗时、安全错误类别/errno、退出码/信号和清理结果,不记录
argv、环境、PTY screen/transcript、凭据或完整 SDK 对象。日志只追加,不做内建轮转。
默认根布局:
<root>/
config.toml 0644(内容含可信定义)
daemon.log 0600(后台追加日志)
init/init-host.sh 0755
init/init-bwrap.sh 0755
admin.sock 0600
tenants/alice.sock 0600
tenants/bob.sock 0600
instances/<id>/ 0700
runtime 和子目录为 0700,入口 socket 为 0600。这些权限只限制文件系统访问;同一 UID 的进程仍可能通过 /proc、ptrace 或直接读取开放 FD 旁路控制面。如果需要不同 OS 用户之间的隔离,部署者必须自行安排目录所有权、mount namespace/容器边界和入口暴露;Kuruwa 不声称 0600 文件权限能替代 UID 隔离。
管理入口与租户入口分开:
R=/srv/kuruwa/run # 显式 --runtime 时
ADMIN="--socket $R/admin.sock"
ALICE="--socket $R/tenants/alice.sock"
BOB="--socket $R/tenants/bob.sock"
使用默认根时,管理入口是 $PWD/.kuruwa/admin.sock。客户端入口选择:
--socket <path> > KURUWA_SOCKET > <root>/admin.sock
KURUWA_ROOT 未设置时为 $PWD/.kuruwa,相对值相对当前目录解析,不向父目录搜索。普通客户端只解析入口,不创建 root,也不自动启动主 daemon 或 RMUX。显式入口(--socket 或非空 KURUWA_SOCKET)连接失败直接报错,不回退默认入口。租户进程只应拿到自己的租户 socket 路径(通常通过环境变量 KURUWA_SOCKET),不应拿到管理 socket 或 runtime 的实例目录。
5. 日常管理流程
以下命令均在管理员 shell 中执行;示例配置中的租户是 alice 和 bob:
# 查看持久租户与获准定义
./target/debug/kuruwa $ADMIN tenant list
# 管理端创建 host 高权限实例(或者由 alice 使用 bwrap 定义创建)
./target/debug/kuruwa $ADMIN instance create --definition host --name ops
./target/debug/kuruwa $ADMIN instance list
# alice 在获准的 bwrap 定义中创建自己的实例
./target/debug/kuruwa $ALICE instance create --definition bwrap --name alice-lab
# alice 创建两个终端。客户端退出不会结束终端。
./target/debug/kuruwa $ALICE start --instance alice-lab --name build -- \
sh -c 'printf "BUILD_READY\n"; sleep 300'
./target/debug/kuruwa $ALICE run --instance alice-lab --name hello --interval 0 -- \
sh -c 'printf "HELLO\n"; sleep 300'
./target/debug/kuruwa $ALICE status build
./target/debug/kuruwa $ALICE screen build
./target/debug/kuruwa $ALICE tail build --lines 20
./target/debug/kuruwa $ALICE transcript build
授权与撤回:
# 管理端只把一个终端 T 授予 alice
./target/debug/kuruwa $ADMIN start --instance ops --name ops-shell -- bash
./target/debug/kuruwa $ADMIN grant --tenant alice --terminal ops-shell
# alice 只能对该终端读/写/等待/调整尺寸,不能创建新终端或销毁实例
./target/debug/kuruwa $ALICE status ops-shell
./target/debug/kuruwa $ALICE send ops-shell -- echo FROM_ALICE
./target/debug/kuruwa $ALICE send ops-shell --key -- Enter
./target/debug/kuruwa $ALICE resume ops-shell --interval 60
./target/debug/kuruwa $ADMIN revoke --tenant alice --terminal ops-shell
grant 只给予指定终端的访问权,不含实例生命周期、终端创建、clean/cancel/gc 或 attach。撤回会立即结束相关受限入口上的等待请求,但不会终止终端;管理员仍可接管。旧 grant 绑定终端内部身份;同名新对象不会继承旧 grant,重启后 grant 也不会恢复。
管理端或实例 owner 的原生 attach:
./target/debug/kuruwa $ADMIN attach ops-shell
# 原生 RMUX 客户端接管同一终端;使用默认 C-b d detach
attach 只允许管理入口或目标实例 owner,CLI 使用服务端解析的 patched RMUX、真实 endpoint 和 session 执行:
rmux -N -S <endpoint> attach-session -t <session>
它不自动启动服务,不向仅有终端 grant 的租户暴露 endpoint/session,也不接受用户附加的 rmux 参数。
其他生命周期命令:
./target/debug/kuruwa $ALICE cancel build
./target/debug/kuruwa $ALICE clean build
./target/debug/kuruwa $ALICE gc --older-than 3600
./target/debug/kuruwa $ADMIN resize ops-shell --cols 100 --rows 40
./target/debug/kuruwa $ADMIN instance destroy ops --force
./target/debug/kuruwa $ADMIN shutdown
非空实例默认拒绝销毁;--force 会结束对应终端和实例 daemon,并保留终端诊断记录。gc 只回收 owner/admin 范围内已结束且达到年龄的记录,running 永不回收。
6. bwrap 弱隔离示例
examples/init-bwrap.sh 是可信定义示例,不是内置安全策略:
- 把 patched RMUX 暂存到实例私有目录,并在进入 namespace 后映射为
/opt/rmux; - 给终端一个可写工作目录
/mnt,清空主 daemon 环境后只设置必要变量; - 用私有
tmpfs隐藏/tmp,并隐藏 runtime 的调用路径和物理路径(包括管理入口和真实 listener); - 最终在 namespace 内
exec/opt/rmux,导入继承的 listener FD。
这能隐藏示例中的 runtime 路径,但不是同 UID 安全边界:同 UID 进程仍可能检查 /proc/<pid>/fd、/proc/net/unix、ptrace 或 namespace 外路径。需要更强隔离时,应由部署者额外配置用户、PID/mount namespace、cgroup/seccomp、只读定义和入口挂载;不要把这个示例描述成完整隔离。
host 定义没有额外隔离:host 实例中的 shell 具有主 daemon 启动环境的实际权限。需要高权限终端时使用 host 实例,并把终端只 grant 给受信租户;不要把 host 实例与弱隔离实例混为一谈。
7. 关闭、重启与恢复边界
正常关闭:
./target/debug/kuruwa $ADMIN shutdown
它会停止接受新请求,通过各实例 RMUX 终止活动终端,销毁受管实例并 best effort 调用一次 cleanup,删除本次运行的入口 socket 和实例目录,同时保留 config.toml、daemon.log 与 init/。cleanup 失败会使 shutdown exit 1,并保留可识别残留;显式 destroy 已发生的 cleanup 失败也会被之后的 shutdown 继续报告(残留目录已被人工删除时视为已解决)。下一次 serve 会拒绝该 runtime,要求人工检查,不会盲目删除或杀进程。
重启:
# 默认根
/path/to/kuruwa/target/debug/kuruwa serve
# 或显式路径
/path/to/kuruwa/target/debug/kuruwa serve \
--config ./kuruwa.toml --runtime /srv/kuruwa/run
已有健康 daemon 时,后台 serve 幂等返回 already_running,不替换现有 socket 或进程;前台 serve 仍会报告冲突。配置中的定义和租户重新加载,但实例、终端、输出、临时 grant 和旧连接都不恢复。新运行使用新代际内部 ID;同名公共名称可以再次创建,但它们是新对象,旧租户不会自动获得访问。
实例 daemon 异常退出时,实例从 ready 列表移除,running 终端转为 orphaned,不会自动新建替代实例。崩溃孤儿的自动回收不是本阶段功能;需要人工检查和清理。
transcript 读取后端当前保留的 raw PTY 输出;受 RMUX 输出环容量限制,不承诺自创建以来的完整日志。已知截断会报告,不伪装成完整记录。输出、终端和授权不在重启后恢复。
8. M5 完整流程实测摘要
本次实测命令:
cargo fmt --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
python3 scripts/test-m2.py
python3 scripts/test-m3.py
python3 scripts/test-m3-blocked.py
python3 scripts/test-m3-write.py
python3 scripts/test-m4.py
python3 scripts/test-m5.py
scripts/test-m5.py 使用真实 CLI、真实 bwrap/host 初始化、真实 PTY 和固定 target/rmux/bin/rmux,本次结果:
M5_PASS assertions=297
本次流程实际使用的命令形式如下($M5_ROOT 是测试运行时临时目录,不是固定示例路径;运行结束已清理)。这是 M5 当时 serve 只有前台模式的历史记录;现行测试已将子进程改为显式 --foreground,默认后台行为另见 scripts/test-root.py:
target/debug/kuruwa serve --config $M5_ROOT/conf/kuruwa.toml --runtime $M5_ROOT/run
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json tenant list
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
instance create --definition bwrap --name alice-lab
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
start --instance alice-lab --name build -- sh -c '...'
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
run --instance alice-lab --name hello --interval 0 -- sh -c '...'
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json \
instance create --definition host --name ops
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json \
start --instance ops --name ops-shell -- bash
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json \
grant --tenant alice --terminal ops-shell
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
send ops-shell -- echo FROM_ALICE_M5
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock attach ops-shell
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
resume ops-shell --interval 60
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json \
revoke --tenant alice --terminal ops-shell
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
instance destroy alice-lab
target/debug/kuruwa --socket $M5_ROOT/run/tenants/alice.sock --json \
instance destroy alice-lab --force
target/debug/kuruwa --socket $M5_ROOT/run/admin.sock --json shutdown
测试确认:
- Alice 在 bwrap 实例中创建两个终端,start/run 客户端退出后终端仍 running;Bob 对实例、终端和
ls都没有可见性。 - 管理端 host 实例的 T 只 grant 给 Alice;Alice 的输入产生真实输出和 host 文件副作用;Bob 看不到 T。
- 原生 PTY attach 观察到 Alice 的输出,输入产生真实副作用,resize 后 pane 为
34x120,C-b ddetach 后 T 仍 running。 - Alice 的 60 秒 resume 在 revoke 后立即以错误返回;T 继续 running,管理员可再次 attach 并输入。
- 非空
instance destroy被拒绝且终端/实例保持运行;--force后对应终端 orphaned、bwrap 实例 daemon 结束。 - shutdown 后重启同一配置:租户仍可加载,旧实例/终端/grant 不恢复;新同名
ops-extra对 Alice 仍不可见。 - 自定义 cleanup 对每个受管实例各调用一次,无 listener FD;失败 cleanup 返回
cleanup错误并留下残留;独立非受管进程在所有 destroy/shutdown 后仍存活。 - 实例 daemon 被 SIGKILL 后终端 orphaned,实例不自动替换;需要显式 clean/destroy 诊断记录。
- M2 的 init 失败/readiness 超时清理由
scripts/test-m2.py覆盖,本流程只复跑,不重复实现。
9. 已知限制
- Linux 验证范围;bwrap 是示例弱隔离,不是安全边界。
- 同一 OS UID 下的
/proc、ptrace、FD 检查可能旁路;需要部署者配置更强的 UID/namespace/cgroup 边界。 host实例的 shell 具有主 daemon 的宿主权限。- 不保证崩溃孤儿自动回收,不实现运行态/输出/grant 的持久恢复。
transcript受 RMUX retained-output 环限制;不是持久日志。- 原生 attach 是管理入口或目标实例 owner 的能力,依赖宿主提供兼容的 patched RMUX 客户端。
- 租户入口的实际暴露方式由部署者负责;Kuruwa 不自动把 socket 挂载进沙盒,也不把管理入口交给租户。
M6 的统一验收入口是 scripts/check.sh,环境前提、分阶段证据、干净 checkout 实测和限制汇总见 M6 交付验收。本文件是 M5 部署与演练记录;最终 MVP 完成由主 agent 独立验收后宣布。