From c0f13d6ce521a073e8c737091f29454a09cd1dcf Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 11 Mar 2026 12:13:35 -0700 Subject: [PATCH] fix: three Linux-only issues (dead sessions, cache network, SSH auth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Zellij dead session cleanup now uses exec_as_user instead of exec_cmd — on Incus, exec_cmd runs as root which can't see the non-root user's Zellij sessions. 2. post_cache_setup no longer calls wait_for_network — cached images only need local file operations (git config, devbox binary), not internet access. 3. SSH auth for `devbox code`: - Fixed nixbld user detection in code.rs (same /home/ filter) - Added services.openssh with PasswordAuthentication=false to devbox-module.nix (was missing, only in standalone config) - Forces key-based auth, preventing password prompts Co-Authored-By: Claude Opus 4.6 --- nix/devbox-module.nix | 7 +++++++ src/cli/code.rs | 4 ++-- src/sandbox/mod.rs | 14 +++++++------- src/sandbox/provision.rs | 5 +---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/nix/devbox-module.nix b/nix/devbox-module.nix index 7d46cdd..3c715e2 100644 --- a/nix/devbox-module.nix +++ b/nix/devbox-module.nix @@ -47,6 +47,13 @@ in { ++ (lib.optionals (langs.ruby or false) devboxSets.lang_ruby); # ── Services ─────────────────────────────────────── + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + }; + }; virtualisation.docker.enable = lib.mkDefault (sets.container or false); services.tailscale.enable = lib.mkDefault (sets.network or false); diff --git a/src/cli/code.rs b/src/cli/code.rs index 769b2bd..06ada2f 100644 --- a/src/cli/code.rs +++ b/src/cli/code.rs @@ -104,11 +104,11 @@ async fn open_via_incus( let ip = extract_incus_ip(&result.stdout)?; - // Detect actual username in the VM + // Detect actual username in the VM (filter to /home/ users to skip nixbld*) let uid_result = run_cmd( "incus", &["exec", vm_name, "--", "bash", "-lc", - "awk -F: '$3 >= 1000 && $3 < 65534 { print $1; exit }' /etc/passwd"], + "awk -F: '$3 >= 1000 && $3 < 65534 && $6 ~ /^\\/home\\// { print $1; exit }' /etc/passwd"], ).await?; let username = uid_result.stdout.trim(); let username = if username.is_empty() { "dev" } else { username }; diff --git a/src/sandbox/mod.rs b/src/sandbox/mod.rs index 7cb8391..98ef125 100644 --- a/src/sandbox/mod.rs +++ b/src/sandbox/mod.rs @@ -318,25 +318,25 @@ impl SandboxManager { let layout_path = format!("/tmp/devbox-layout-{effective_layout}.kdl"); let session_name = format!("devbox-{name}"); - // Always clean up dead sessions first, then check for alive ones. - // `zellij delete-all-sessions` removes only dead (EXITED) sessions. - // Use bash -lc for NixOS PATH compatibility. + // Clean up dead sessions and check for live ones. + // Must run as the non-root user because Zellij sessions are per-user. + // On Incus, exec_cmd runs as root which can't see the user's sessions. let _ = runtime - .exec_cmd(name, &["bash", "-lc", "zellij delete-all-sessions -y"], false) + .exec_as_user(name, &["bash", "-lc", "zellij delete-all-sessions -y 2>/dev/null; true"]) .await; if force_new_session { // Kill the live session so we can start fresh let kill_cmd = format!("zellij kill-session {session_name} 2>/dev/null; true"); let _ = runtime - .exec_cmd(name, &["bash", "-lc", &kill_cmd], false) + .exec_as_user(name, &["bash", "-lc", &kill_cmd]) .await; } - // Check if a live session exists + // Check if a live session exists (must run as user to see user sessions) let list_cmd = format!("zellij list-sessions 2>/dev/null | grep -q '{session_name}'"); let session_alive = runtime - .exec_cmd(name, &["bash", "-lc", &list_cmd], false) + .exec_as_user(name, &["bash", "-lc", &list_cmd]) .await .map(|r| r.exit_code == 0) .unwrap_or(false); diff --git a/src/sandbox/provision.rs b/src/sandbox/provision.rs index a77fb27..7251149 100644 --- a/src/sandbox/provision.rs +++ b/src/sandbox/provision.rs @@ -269,10 +269,7 @@ pub async fn post_cache_setup( ) -> Result<()> { let username = whoami(); - // Wait for VM to be reachable - wait_for_network(runtime, name).await?; - - // Detect VM user/home + // Detect VM user/home (no network needed — just reading /etc/passwd) let vm_user = detect_vm_username(runtime, name).await; let vm_home = detect_vm_home(runtime, name, &vm_user).await;