Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions src/runtime/incus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,34 +217,42 @@ impl Runtime for IncusRuntime {
let vm = Self::vm_name(name);

if interactive {
// For interactive sessions (shell attach, etc.), run as the
// non-root user with correct HOME and working directory.
let uid_str = Self::detect_vm_uid(&vm).await.unwrap_or("1000".to_string());
let home_env = Self::detect_vm_home(&vm, &uid_str).await;

let mut args = vec![
"exec".to_string(),
vm,
"--user".to_string(),
uid_str,
"--cwd".to_string(),
"/workspace".to_string(),
"--env".to_string(),
home_env,
"--".to_string(),
];
for c in cmd {
args.push(c.to_string());
}
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
run_interactive("incus", &arg_refs).await
let mut args = vec!["exec", &vm, "--"];
args.extend_from_slice(cmd);
run_interactive("incus", &args).await
} else {
let mut args = vec!["exec", &vm, "--"];
args.extend_from_slice(cmd);
run_cmd("incus", &args).await
}
}

/// Execute an interactive command as the non-root user.
/// Incus exec defaults to root, so we detect the first UID >= 1000
/// and set --user, HOME, and CWD for proper user sessions.
async fn exec_as_user(&self, name: &str, cmd: &[&str]) -> Result<ExecResult> {
let vm = Self::vm_name(name);
let uid_str = Self::detect_vm_uid(&vm).await.unwrap_or("1000".to_string());
let home_env = Self::detect_vm_home(&vm, &uid_str).await;

let mut args = vec![
"exec".to_string(),
vm,
"--user".to_string(),
uid_str,
"--cwd".to_string(),
"/workspace".to_string(),
"--env".to_string(),
home_env,
"--".to_string(),
];
for c in cmd {
args.push(c.to_string());
}
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
run_interactive("incus", &arg_refs).await
}

async fn destroy(&self, name: &str) -> Result<()> {
let vm = Self::vm_name(name);
// Stop first (ignore errors if already stopped)
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,11 @@ pub trait Runtime: Send + Sync {
/// Update mount points for an existing sandbox.
/// Stops the VM, updates mounts in the config, and restarts.
async fn update_mounts(&self, name: &str, mounts: &[Mount]) -> Result<()>;

/// Execute an interactive command as the non-root user.
/// Used for shell attach — defaults to exec_cmd with interactive=true.
/// Runtimes like Incus override this to set --user, HOME, and CWD.
async fn exec_as_user(&self, name: &str, cmd: &[&str]) -> Result<ExecResult> {
self.exec_cmd(name, cmd, true).await
}
}
9 changes: 4 additions & 5 deletions src/sandbox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ impl SandboxManager {
if layout == "plain" {
println!("Attaching to sandbox '{name}'...");
let shell = Self::probe_shell(runtime.as_ref(), name).await;
runtime.exec_cmd(name, &[&shell, "-l"], true).await?;
runtime.exec_as_user(name, &[&shell, "-l"]).await?;
return Ok(());
}

Expand All @@ -249,7 +249,7 @@ impl SandboxManager {
// No Zellij — fall back to raw shell
println!("Attaching to sandbox '{name}'...");
let shell = Self::probe_shell(runtime.as_ref(), name).await;
runtime.exec_cmd(name, &[&shell, "-l"], true).await?;
runtime.exec_as_user(name, &[&shell, "-l"]).await?;
return Ok(());
}

Expand Down Expand Up @@ -317,7 +317,7 @@ impl SandboxManager {
// Reattach to existing live session
println!("Reattaching to sandbox '{name}'...");
runtime
.exec_cmd(name, &["zellij", "attach", &session_name], true)
.exec_as_user(name, &["zellij", "attach", &session_name])
.await?;
} else {
// Create new named session with layout.
Expand All @@ -336,10 +336,9 @@ impl SandboxManager {

println!("Attaching to sandbox '{name}' (layout: {effective_layout})...");
runtime
.exec_cmd(
.exec_as_user(
name,
&["zellij", "--config", &config_path, "--layout", &layout_path],
true,
)
.await?;
}
Expand Down
Loading