本页目录
  1. 1. 前提与构建
  2. 2. 固定示例路径与临时路径
  3. 3. 配置与可信定义
  4. 4. 启动主 daemon
  5. 5. 日常管理流程
  6. 6. bwrap 弱隔离示例
  7. 7. 关闭、重启与恢复边界
  8. 8. M5 完整流程实测摘要
  9. 9. 已知限制

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 d detach 后 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 独立验收后宣布。