Major rebranding, i18n, dashboard, shared folders, role capabilities
- Rebrand: new app/agent icons, SKALD.md, warm "paper" CSS palette (terracotta accent, --radius tokens, WCAG contrast, reduced-motion), updated favicon, tray icon, skaldkonur asset - i18n: backend crate (i18n.rs, locale column, ui_locale config), frontend library (web/lib/i18n.js, I18nMixin, t(key)), translation files (web/i18n/), every component wired - Dashboard: <dashboard-page> replaces old home-page content; <app-copilot> becomes the landing page (full/dock layout modes) - Shared folders: API endpoints (shared_folders.rs), frontend page, can_write membership, container mount topology, user_fs routing - Role capabilities: new db table & authorization seam (data not enums), roles.attrs JSON for ui_mode / interface select - Setup: skald-setup prompts for language + password, sets ui_locale - General: components migrated to CSS variables, Lit conventions cleanup, connectors/catalog/marketplace/approval refactoring
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use sqlx::SqlitePool;
|
||||
@@ -39,6 +40,9 @@ pub const HOMES_DIR: &str = "homes";
|
||||
pub const SHARED_DIR: &str = "shared";
|
||||
/// Home mount point inside the container.
|
||||
pub const CONTAINER_HOME: &str = "/root";
|
||||
/// Grace window `docker stop` gives in-container processes (SIGTERM → SIGKILL)
|
||||
/// before force-killing — enough for a shell or MCP `docker exec` child to exit.
|
||||
const STOP_GRACE: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The deterministic container name for a user — derivable without any manager,
|
||||
/// so `UserFs` can carry it and `execute_cmd` can exec into it directly.
|
||||
@@ -203,6 +207,38 @@ impl ContainerManager {
|
||||
let _ = docker(&["rm", "-f", &name]).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gracefully shuts down a user's container: `docker stop` sends SIGTERM to the
|
||||
/// in-container processes and waits up to `STOP_GRACE` before SIGKILL, so an
|
||||
/// in-flight `execute_cmd` shell (and any per-user MCP `docker exec` child) gets
|
||||
/// a window to exit cleanly instead of vanishing mid-write. Best-effort: a
|
||||
/// missing or already-stopped container is fine.
|
||||
pub async fn stop(&self, user_id: &str) -> Result<()> {
|
||||
let name = container_name(user_id);
|
||||
let secs = STOP_GRACE.as_secs().to_string();
|
||||
if let Err(e) = docker(&["stop", "-t", &secs, &name]).await {
|
||||
tracing::debug!(container = %name, error = %e, "container stop (ignored)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanly recreates a user's container so it picks up a changed mount topology
|
||||
/// — e.g. a shared-folder membership change (§6), whose mounts are fixed at
|
||||
/// `docker create` time and cannot be altered on a live container. Graceful
|
||||
/// [`stop`](Self::stop) → remove → [`ensure`](Self::ensure) (which rebuilds the
|
||||
/// mount set from the current memberships and recreates the host dirs). The
|
||||
/// container holds no durable state — everything lives in the bind mounts — so a
|
||||
/// recreate is safe by construction. A no-op-safe `rm` (the container is already
|
||||
/// stopped) precedes `ensure`, which then finds it absent and creates it fresh.
|
||||
///
|
||||
/// Caveat (caller's concern, not this method's): the per-user MCP runtime and a
|
||||
/// logged-in user's `UserFs` snapshot are both bound to the old container/
|
||||
/// membership and are NOT refreshed here — see the shared-folders remount wiring.
|
||||
pub async fn recreate(&self, user_id: &str) -> Result<()> {
|
||||
self.stop(user_id).await?;
|
||||
let _ = docker(&["rm", &container_name(user_id)]).await;
|
||||
self.ensure(user_id).await
|
||||
}
|
||||
}
|
||||
|
||||
// ── docker CLI helpers ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user