diff --git a/README.md b/README.md index d2983c7..0708e95 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,29 @@ ssh yourserver -t "devbox shell --name shared-api" --- +## IDE Integration + +Use your local VS Code, Cursor, or Windsurf to edit code inside the sandbox — full IntelliSense, extensions, and debugging, all running in the isolated VM. + +```bash +devbox code # Open VS Code into the sandbox +devbox code --editor cursor # Use Cursor instead +devbox code --editor windsurf # Use Windsurf +devbox code myapp # Open a specific sandbox +devbox code --path /workspace/src # Open a specific directory +``` + +Devbox automatically: +1. Configures `~/.ssh/config` for the sandbox VM +2. Refreshes the overlay layer (clears stale file handles) +3. Launches the editor with Remote SSH pointed at `/workspace` + +Works with any editor that supports [Remote SSH](https://code.visualstudio.com/docs/remote/ssh) — VS Code, Cursor, Windsurf, and others. + +> **NixOS compatibility:** Devbox enables `nix-ld` in the VM so VS Code Server and other dynamically linked binaries run without issues. + +--- + ## Quick Start ### Prerequisites @@ -309,6 +332,7 @@ All layer operations are also available in the **DevBox Management Panel** insid | `devbox destroy` | Remove a sandbox | | `devbox list` | List all sandboxes | | `devbox status` | Show detailed sandbox status | +| `devbox code` | Open VS Code / Cursor into sandbox via Remote SSH | | `devbox use ` | Switch sandbox to current directory | | `devbox upgrade --tools ` | Add tools to a running sandbox | | `devbox packages` | Open TUI package manager | diff --git a/nix/devbox-module.nix b/nix/devbox-module.nix index 97eb73f..597fe71 100644 --- a/nix/devbox-module.nix +++ b/nix/devbox-module.nix @@ -50,6 +50,11 @@ in { virtualisation.docker.enable = lib.mkDefault (sets.container or false); services.tailscale.enable = lib.mkDefault (sets.network or false); + # ── Dynamic linker compat ───────────────────────── + # Required for VS Code Server, Cursor, and other dynamically linked + # binaries that expect a standard FHS layout (ld-linux, libc, etc.). + programs.nix-ld.enable = true; + # ── Shell ────────────────────────────────────────── programs.zsh.enable = true; security.sudo.wheelNeedsPassword = lib.mkDefault false; diff --git a/src/cli/code.rs b/src/cli/code.rs index 347ea94..98d7714 100644 --- a/src/cli/code.rs +++ b/src/cli/code.rs @@ -4,6 +4,7 @@ use clap::Args; use crate::runtime::cmd::run_cmd; use crate::runtime::SandboxStatus; use crate::sandbox::SandboxManager; +use crate::sandbox::overlay; #[derive(Args, Debug)] pub struct CodeArgs { @@ -36,6 +37,14 @@ pub async fn run(args: CodeArgs, manager: &SandboxManager) -> Result<()> { SandboxStatus::Unknown(s) => bail!("Sandbox '{name}' is in unknown state: {s}"), } + // Refresh overlay before opening editor to avoid stale file handles + if state.mount_mode != "writable" { + println!("Refreshing overlay layer..."); + if let Err(e) = overlay::refresh(runtime.as_ref(), &name).await { + eprintln!("Warning: overlay refresh failed: {e}"); + } + } + let vm_name = format!("devbox-{name}"); let ssh_host = format!("devbox-{name}"); @@ -67,7 +76,9 @@ async fn open_via_lima( ); } - let ssh_config = result.stdout.trim().to_string(); + // Lima's output has its own Host line (e.g. "Host lima-devbox-test2"). + // Replace it with our ssh_host so VS Code can find the right config entry. + let ssh_config = rewrite_ssh_host(ssh_host, result.stdout.trim()); write_ssh_config(ssh_host, &ssh_config)?; launch_editor(editor, ssh_host, path) @@ -139,6 +150,21 @@ fn extract_incus_ip(json_output: &str) -> Result { bail!("Could not find IP address for Incus VM. Is it running?") } +/// Replace the `Host` line in an SSH config block with our desired host alias. +fn rewrite_ssh_host(desired_host: &str, config: &str) -> String { + config + .lines() + .map(|line| { + if line.trim_start().starts_with("Host ") { + format!("Host {desired_host}") + } else { + line.to_string() + } + }) + .collect::>() + .join("\n") +} + /// Write or update an SSH config block in ~/.ssh/config for the devbox host. fn write_ssh_config(host: &str, config_block: &str) -> Result<()> { let home = dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Cannot determine home directory"))?; @@ -199,9 +225,11 @@ fn launch_editor(editor: &str, ssh_host: &str, path: &str) -> Result<()> { println!("Opening {editor} → {ssh_host}:{path}"); - let remote_arg = format!("--remote=ssh-remote+{ssh_host}{path}"); + let remote_arg = format!("ssh-remote+{ssh_host}"); let status = std::process::Command::new(editor) + .arg("--remote") .arg(&remote_arg) + .arg(path) .status() .map_err(|e| anyhow::anyhow!("Failed to launch {editor}: {e}"))?;