feat(container): per-user Docker sandbox + mapped per-user filesystem
Realizes blueprint §6: each user gets a permanent Docker container
(skald-{userid}, our own skald-runtime image with python+node) as their
execution sandbox. Docker is now a hard requirement — a missing daemon fails
Skald::new and the process exits at boot.
- ContainerManager (crates/skald-core/src/container/): docker availability
check, builds skald-runtime from the embedded Dockerfile, reconciles one
running container per active user at boot, stops them at shutdown, and
ensure/remove on user create/delete. Shells the docker CLI (no client crate).
- UserFs (core-api): pure value type carried in ToolContext, mapping the agent's
single namespace — ~/ → homes/{userid}, shared/{X}/ → shared/{X} (membership),
user-memory/ + shared-memory/ → SQLite — to host and container paths.
- execute_cmd now runs inside the caller's container via `docker exec`.
- fs-tools resolve every physical path through UserFs to the per-user host
workspace, host-side, with fail-closed symlink/`..` containment
(resolve_host_path: canonicalize + prefix-check). grep_files resolves its root
the same way but stays disk-only.
- shared_folders + shared_folder_members (registry, junction table with
can_write) back the shared-folder membership that drives both the container
mounts and the shared/{X} routing.
- Threading: UserContext.fs → ChatSessionManager → handler → ToolContext.fs.
Per-user MCP servers do not yet run in the container (next round).
This commit is contained in:
@@ -19,6 +19,7 @@ pub mod roles;
|
||||
pub mod scheduled_jobs;
|
||||
pub mod scratchpad;
|
||||
pub mod session_mcp_grants;
|
||||
pub mod shared_folders;
|
||||
pub mod sources;
|
||||
pub mod stack_mcp_grants;
|
||||
pub mod tool_permission_groups;
|
||||
@@ -390,6 +391,34 @@ async fn create_registry_tables(pool: &SqlitePool) -> Result<()> {
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// Shared on-disk folders (blueprint §6/§0.1): a named directory
|
||||
// `{WD}/shared/{folder_name}` bind-mounted into the container of each member.
|
||||
// Registry tables — instance-wide config, readable without any user key. The
|
||||
// membership is a **junction table** (not a JSON array) so a member can be
|
||||
// read-only, and so the mount topology / fs routing can query it in both
|
||||
// directions. FK `user_id → users(id)` is registry→registry (same file):
|
||||
// allowed, unlike an owner→registry key.
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS shared_folders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
folder_name TEXT NOT NULL UNIQUE,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
"CREATE TABLE IF NOT EXISTS shared_folder_members (
|
||||
folder_id INTEGER NOT NULL REFERENCES shared_folders(id) ON DELETE CASCADE,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
can_write INTEGER NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (folder_id, user_id)
|
||||
)",
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user