Skip to content

Commit 4c5470a

Browse files
Merge pull request #3 from ethannortharc/fix/devbox-code-remote-arg
fix: devbox code Remote SSH fixes + nix-ld + docs
2 parents 667f9a2 + bc93d34 commit 4c5470a

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,29 @@ ssh yourserver -t "devbox shell --name shared-api"
142142

143143
---
144144

145+
## IDE Integration
146+
147+
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.
148+
149+
```bash
150+
devbox code # Open VS Code into the sandbox
151+
devbox code --editor cursor # Use Cursor instead
152+
devbox code --editor windsurf # Use Windsurf
153+
devbox code myapp # Open a specific sandbox
154+
devbox code --path /workspace/src # Open a specific directory
155+
```
156+
157+
Devbox automatically:
158+
1. Configures `~/.ssh/config` for the sandbox VM
159+
2. Refreshes the overlay layer (clears stale file handles)
160+
3. Launches the editor with Remote SSH pointed at `/workspace`
161+
162+
Works with any editor that supports [Remote SSH](https://code.visualstudio.com/docs/remote/ssh) — VS Code, Cursor, Windsurf, and others.
163+
164+
> **NixOS compatibility:** Devbox enables `nix-ld` in the VM so VS Code Server and other dynamically linked binaries run without issues.
165+
166+
---
167+
145168
## Quick Start
146169

147170
### Prerequisites
@@ -309,6 +332,7 @@ All layer operations are also available in the **DevBox Management Panel** insid
309332
| `devbox destroy` | Remove a sandbox |
310333
| `devbox list` | List all sandboxes |
311334
| `devbox status` | Show detailed sandbox status |
335+
| `devbox code` | Open VS Code / Cursor into sandbox via Remote SSH |
312336
| `devbox use <name>` | Switch sandbox to current directory |
313337
| `devbox upgrade --tools <set>` | Add tools to a running sandbox |
314338
| `devbox packages` | Open TUI package manager |

nix/devbox-module.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ in {
5050
virtualisation.docker.enable = lib.mkDefault (sets.container or false);
5151
services.tailscale.enable = lib.mkDefault (sets.network or false);
5252

53+
# ── Dynamic linker compat ─────────────────────────
54+
# Required for VS Code Server, Cursor, and other dynamically linked
55+
# binaries that expect a standard FHS layout (ld-linux, libc, etc.).
56+
programs.nix-ld.enable = true;
57+
5358
# ── Shell ──────────────────────────────────────────
5459
programs.zsh.enable = true;
5560
security.sudo.wheelNeedsPassword = lib.mkDefault false;

src/cli/code.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use clap::Args;
44
use crate::runtime::cmd::run_cmd;
55
use crate::runtime::SandboxStatus;
66
use crate::sandbox::SandboxManager;
7+
use crate::sandbox::overlay;
78

89
#[derive(Args, Debug)]
910
pub struct CodeArgs {
@@ -36,6 +37,14 @@ pub async fn run(args: CodeArgs, manager: &SandboxManager) -> Result<()> {
3637
SandboxStatus::Unknown(s) => bail!("Sandbox '{name}' is in unknown state: {s}"),
3738
}
3839

40+
// Refresh overlay before opening editor to avoid stale file handles
41+
if state.mount_mode != "writable" {
42+
println!("Refreshing overlay layer...");
43+
if let Err(e) = overlay::refresh(runtime.as_ref(), &name).await {
44+
eprintln!("Warning: overlay refresh failed: {e}");
45+
}
46+
}
47+
3948
let vm_name = format!("devbox-{name}");
4049
let ssh_host = format!("devbox-{name}");
4150

@@ -67,7 +76,9 @@ async fn open_via_lima(
6776
);
6877
}
6978

70-
let ssh_config = result.stdout.trim().to_string();
79+
// Lima's output has its own Host line (e.g. "Host lima-devbox-test2").
80+
// Replace it with our ssh_host so VS Code can find the right config entry.
81+
let ssh_config = rewrite_ssh_host(ssh_host, result.stdout.trim());
7182
write_ssh_config(ssh_host, &ssh_config)?;
7283

7384
launch_editor(editor, ssh_host, path)
@@ -139,6 +150,21 @@ fn extract_incus_ip(json_output: &str) -> Result<String> {
139150
bail!("Could not find IP address for Incus VM. Is it running?")
140151
}
141152

153+
/// Replace the `Host` line in an SSH config block with our desired host alias.
154+
fn rewrite_ssh_host(desired_host: &str, config: &str) -> String {
155+
config
156+
.lines()
157+
.map(|line| {
158+
if line.trim_start().starts_with("Host ") {
159+
format!("Host {desired_host}")
160+
} else {
161+
line.to_string()
162+
}
163+
})
164+
.collect::<Vec<_>>()
165+
.join("\n")
166+
}
167+
142168
/// Write or update an SSH config block in ~/.ssh/config for the devbox host.
143169
fn write_ssh_config(host: &str, config_block: &str) -> Result<()> {
144170
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<()> {
199225

200226
println!("Opening {editor} → {ssh_host}:{path}");
201227

202-
let remote_arg = format!("--remote=ssh-remote+{ssh_host}{path}");
228+
let remote_arg = format!("ssh-remote+{ssh_host}");
203229
let status = std::process::Command::new(editor)
230+
.arg("--remote")
204231
.arg(&remote_arg)
232+
.arg(path)
205233
.status()
206234
.map_err(|e| anyhow::anyhow!("Failed to launch {editor}: {e}"))?;
207235

0 commit comments

Comments
 (0)