feat(sbx): create fresh VirtualBox VMs via cloud-init (/sbx launch vbox new)
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
The existing vbox paths only open pre-made images; there was no way to build one from scratch. Add `/sbx launch vbox new [name]` backed by scripts/vbox-new.sh: download an Ubuntu cloud image, convert it to a VDI, build a cloud-init NoCloud seed that creates a sudo login user and installs the sandbox-tools.json toolchain on first boot, then create + boot the VM. VirtualBox guests can't be exec'd into like docker/multipass, so cloud-init is the provisioning channel. Generates a one-time login password (or takes --pass) and authorizes a local SSH key if present. Runs off-thread; rolls back a half-built VM on failure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ const ENSURE_VBOX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/ensure-v
|
||||
const SBX_BOOTSTRAP: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/sandbox-bootstrap.sh");
|
||||
/// Editable package schema the bootstrap installs — vim+curl always added.
|
||||
const SBX_TOOLS_JSON: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/sandbox-tools.json");
|
||||
/// Creates a fresh, cloud-init-provisioned Ubuntu VirtualBox VM (hh/scripts/).
|
||||
const VBOX_NEW: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/vbox-new.sh");
|
||||
|
||||
/// Is the Docker daemon accepting connections? (`docker info` succeeds.)
|
||||
pub fn docker_daemon_up() -> bool {
|
||||
@@ -186,6 +188,43 @@ pub fn gui_launch(name: &str) -> Result<String> {
|
||||
Ok(format!("launched {name} (GUI)"))
|
||||
}
|
||||
|
||||
/// Create + boot a *fresh* Ubuntu VirtualBox VM pre-provisioned with the dev
|
||||
/// toolchain, by driving `scripts/vbox-new.sh` (downloads a cloud image, builds
|
||||
/// a cloud-init NoCloud seed installing scripts/sandbox-tools.json, then boots
|
||||
/// the GUI). Unlike `/sbx launch vbox <vm>`, which opens an *existing* image,
|
||||
/// this builds one from scratch — the only path that doesn't need a pre-made VM.
|
||||
///
|
||||
/// Blocking and slow on first run (~600MB image download). Runs the script
|
||||
/// non-interactively (`--yes`); returns its final status line(s) — including the
|
||||
/// generated login password, which is shown exactly once.
|
||||
pub fn vbox_new(name: &str, user: &str) -> Result<String> {
|
||||
let out = Command::new("bash")
|
||||
.arg(VBOX_NEW)
|
||||
.args(["--yes", "--name", name, "--user", user])
|
||||
.output()
|
||||
.context("running vbox-new.sh (is bash available?)")?;
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
if !out.status.success() {
|
||||
anyhow::bail!(
|
||||
"vbox-new failed: {}",
|
||||
stderr.lines().rev().find(|l| !l.trim().is_empty()).unwrap_or("").trim()
|
||||
);
|
||||
}
|
||||
// The script narrates progress on stderr. Surface the final success line and,
|
||||
// if one was generated, the one-time login password.
|
||||
let last = stderr
|
||||
.lines()
|
||||
.rev()
|
||||
.find(|l| l.trim_start().starts_with('✓'))
|
||||
.unwrap_or("VM created")
|
||||
.trim();
|
||||
let pw = stderr.lines().find(|l| l.contains("login password"));
|
||||
Ok(match pw {
|
||||
Some(p) => format!("{last} · {}", p.trim()),
|
||||
None => last.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// A running hypervisor that holds the CPU's VT-x/VMX root mode. While one is
|
||||
/// live, VirtualBox can't boot a *hardware* VM (it aborts with
|
||||
/// `VERR_VMX_IN_VMX_ROOT_MODE`). We only mark holders we know how to stop
|
||||
|
||||
Reference in New Issue
Block a user