From d448314e5e7c1964b50acc99813fa880a65046e9 Mon Sep 17 00:00:00 2001 From: leetcrypt Date: Sat, 6 Jun 2026 22:29:24 -0700 Subject: [PATCH] feat(sbx): VM dev-toolchain bootstrap + save/load parity across backends Reorganize the help menu into one VIRTUAL MACHINES cluster covering all backends, and bring docker/multipass/vbox to save+load parity: - Launch-time dev toolchain: sandbox-bootstrap.sh + editable sandbox-tools.json (vim/curl guaranteed), installed in docker AND multipass sandboxes at provision time. - Vbox load: vm_restore + `/sbx vmload [label]` (restore snapshot then boot the GUI). - Multipass load: `/sbx load` is now backend-aware (locate_snapshot + SnapKind), mp_restore re-attaches the shared shell; teardown stops (not purges) an instance that still has snapshots so they survive `/sbx stop`. Co-Authored-By: Claude Opus 4.6 --- hh/scripts/sandbox-bootstrap.sh | 47 +++++++++ hh/scripts/sandbox-tools.json | 28 +++++ hh/src/app.rs | 112 ++++++++++++++++++-- hh/src/sbx.rs | 182 +++++++++++++++++++++++++++++++- hh/src/ui.rs | 63 ++++++----- 5 files changed, 385 insertions(+), 47 deletions(-) create mode 100644 hh/scripts/sandbox-bootstrap.sh create mode 100644 hh/scripts/sandbox-tools.json diff --git a/hh/scripts/sandbox-bootstrap.sh b/hh/scripts/sandbox-bootstrap.sh new file mode 100644 index 0000000..b84d96a --- /dev/null +++ b/hh/scripts/sandbox-bootstrap.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# sandbox-bootstrap.sh — install a baseline dev toolchain inside a hack-house +# Docker sandbox. +# +# It is piped to `bash -s` (as root) inside the container at provision time, so +# fresh sandboxes come up usable instead of bare. Idempotent + sentinel-guarded: +# a re-provision (new member joins) or a snapshot load is a fast no-op. +# +# When launched by hh, the package list comes from scripts/sandbox-tools.json +# (the canonical, editable schema) via $HH_SBX_PKGS. DEFAULT_PKGS below is only +# the fallback for running this script standalone. Per-launch override: +# HH_SBX_PKGS="vim tmux ripgrep" ./host-house.sh ... +set -uo pipefail + +SENTINEL=/var/lib/hh-bootstrap.done +[[ -f "$SENTINEL" ]] && exit 0 # this container is already provisioned + +# Baseline dev tools: editors, fetchers, vcs, net + inspect utilities, json, +# archives. Keep names valid for the base image (ubuntu:24.04) — an unknown +# package would otherwise abort the whole batch install. +DEFAULT_PKGS="vim nano less curl wget ca-certificates git \ +build-essential pkg-config \ +procps iproute2 iputils-ping net-tools \ +jq unzip zip tree htop file ripgrep \ +python3 python3-pip python3-venv" + +PKGS="${HH_SBX_PKGS:-$DEFAULT_PKGS}" + +export DEBIAN_FRONTEND=noninteractive + +# Refresh the index first (base images ship without /var/lib/apt/lists). If the +# update fails (e.g. no network) we bail WITHOUT writing the sentinel, so the +# next launch retries instead of leaving a half-provisioned shell. +apt-get update -qq || exit 0 + +# --no-install-recommends keeps the image lean. apt is atomic on resolution, so +# if one name is unavailable the batch aborts — fall back to one-by-one so a +# single bad/missing package can't deprive the shell of everything else. +# shellcheck disable=SC2086 +if ! apt-get install -y --no-install-recommends $PKGS; then + for p in $PKGS; do + apt-get install -y --no-install-recommends "$p" || true + done +fi + +mkdir -p "$(dirname "$SENTINEL")" +date -u +%FT%TZ > "$SENTINEL" # mark done; skip on the next provision pass diff --git a/hh/scripts/sandbox-tools.json b/hh/scripts/sandbox-tools.json new file mode 100644 index 0000000..f8a09c0 --- /dev/null +++ b/hh/scripts/sandbox-tools.json @@ -0,0 +1,28 @@ +{ + "_comment": "Packages apt-get installs in every hack-house Docker sandbox at launch. Edit `packages` to add dev tools, then relaunch the sandbox. `vim` and `curl` are ALWAYS installed even if absent here. Names must be valid for the base image (ubuntu:24.04); unknown names are skipped, not fatal. Per-launch override: HH_SBX_PKGS=\"vim tmux\".", + "packages": [ + "vim", + "curl", + "wget", + "ca-certificates", + "git", + "less", + "nano", + "build-essential", + "pkg-config", + "procps", + "iproute2", + "iputils-ping", + "net-tools", + "jq", + "unzip", + "zip", + "tree", + "htop", + "file", + "ripgrep", + "python3", + "python3-pip", + "python3-venv" + ] +} diff --git a/hh/src/app.rs b/hh/src/app.rs index a706341..61303e7 100644 --- a/hh/src/app.rs +++ b/hh/src/app.rs @@ -1680,27 +1680,80 @@ fn handle_command( } } Some("load") => match p.next() { - None => app.sys("usage: /sbx load