diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1447970..743d515 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,7 @@ jobs: run: cargo build working-directory: hh - name: run cross-stack smoke test - run: bash hh/smoke-e2e.sh + run: bash hh/scripts/smoke-e2e.sh audit: name: dependency audit diff --git a/README.MD b/README.MD index c3a3447..6b1f933 100644 --- a/README.MD +++ b/README.MD @@ -50,8 +50,8 @@ Encrypted chat that runs in your terminal. You host the server, you control the | `cmd_chat/agent/` | The model-agnostic AI agent bridge (joins a room as an encrypted client) | | `models.toml` | Named provider profiles for `/ai start ` (see `docs/providers.md`) | | `docs/providers.md` | Connect any model — profiles, flags, discovery, bring-your-own-provider | -| `hh/lets-hack.sh` | Spin up a local test "clergy" in tmux (server + N client panes) | -| `bootstrap-ai.sh` | Optional: install Ollama + pull a model for the local `/ai` agent | +| `hh/scripts/lets-hack.sh` | Spin up a local test "clergy" in tmux (server + N client panes) | +| `hh/scripts/bootstrap-ai.sh` | Optional: install Ollama + pull a model for the local `/ai` agent | | `hh/direnv-autostart/` | `cd` into a directory to auto-launch a session (direnv) | ## Quick start @@ -61,42 +61,42 @@ git clone https://github.com/leetcrypt/hack-house.git cd hack-house ``` -### 1. One-shot setup (`bootstrap.sh`) +### 1. One-shot setup (`hh/scripts/bootstrap.sh`) Checks prerequisites, creates the Python venv, installs the server's dependencies, and builds the Rust client: ```bash -./bootstrap.sh # venv + deps + debug build -./bootstrap.sh --release # release build -./bootstrap.sh --check # report tooling only, change nothing +hh/scripts/bootstrap.sh # venv + deps + debug build +hh/scripts/bootstrap.sh --release # release build +hh/scripts/bootstrap.sh --check # report tooling only, change nothing ``` > `bootstrap.sh` does **not** touch direnv — the autostart in step 4 is a > separate, opt-in convenience. -**Optional AI layer (`bootstrap-ai.sh`).** Want the local `/ai` agent? This runs +**Optional AI layer (`hh/scripts/bootstrap-ai.sh`).** Want the local `/ai` agent? This runs the baseline setup first, then installs Ollama (if missing) and pulls a default model — nothing here changes the AI-free baseline above: ```bash -./bootstrap-ai.sh # baseline + Ollama + qwen2.5:3b -./bootstrap-ai.sh --check # report only, change nothing -HH_AI_MODEL=llama3 ./bootstrap-ai.sh # pull a different model +hh/scripts/bootstrap-ai.sh # baseline + Ollama + qwen2.5:3b +hh/scripts/bootstrap-ai.sh --check # report only, change nothing +HH_AI_MODEL=llama3 hh/scripts/bootstrap-ai.sh # pull a different model ``` -### 2. Try it in tmux (`lets-hack.sh`) +### 2. Try it in tmux (`scripts/lets-hack.sh`) The fastest way to see it working: builds the client, boots a fresh `--no-tls` server on `127.0.0.1:4173`, and opens a pane per user. ```bash cd hh -./lets-hack.sh # alice + bob, tiled in tmux -./lets-hack.sh neo trinity # custom users -./lets-hack.sh --theme neon # pick vestments -./lets-hack.sh --reuse # keep a live server (reconnect tests) -./lets-hack.sh --kill # tear it all down +./scripts/lets-hack.sh # alice + bob, tiled in tmux +./scripts/lets-hack.sh neo trinity # custom users +./scripts/lets-hack.sh --theme neon # pick vestments +./scripts/lets-hack.sh --reuse # keep a live server (reconnect tests) +./scripts/lets-hack.sh --kill # tear it all down ``` ### 3. Manual setup @@ -180,7 +180,7 @@ server only ever sees ciphertext (same trust model as chat). | Backend | Isolation | Notes | |---|---|---| | `local` | none | a `bash` shell on the host — fast, for dev/testing only | -| `docker` | container | `ubuntu:24.04` by default; `/sbx launch docker --start` boots the daemon (or run `./ensure-docker.sh`) | +| `docker` | container | `ubuntu:24.04` by default; `/sbx launch docker --start` boots the daemon (or run `hh/scripts/ensure-docker.sh`) | | `multipass` | full VM | `24.04` by default; strongest isolation, ~30 s to boot, the choice for real use | Tear it down with `/sbx stop` (purges the VM/container). @@ -247,7 +247,7 @@ when you quit). Pick a model at summon time with `/ai start `. - **Runs on *your* machine.** The default provider is [Ollama](https://ollama.com) — a local model (default `qwen2.5:3b`), no API key, nothing leaves your host. - Run `./bootstrap-ai.sh` once to install it and pull the model. + Run `hh/scripts/bootstrap-ai.sh` once to install it and pull the model. - **Addressed-only.** The agent reads room traffic like any client but forwards to the model *only* the messages that trigger it (`/ai …`) — no passive surveillance, no cost or noise when idle. @@ -301,8 +301,8 @@ to ~4000 lines of scrollback; the sandbox terminal keeps 2000. | Variable | Where | Effect | |---|---|---| | `CMD_CHAT_MAX_USERS` | server | Room capacity (default `4`) | -| `PORT` · `PW` · `HOST` | `lets-hack.sh` | Override the test server's port / password / bind host | -| `THEME` | `lets-hack.sh` | Vestments for every pane (`church` · `neon` · `crypt`) | +| `PORT` · `PW` · `HOST` | `scripts/lets-hack.sh` | Override the test server's port / password / bind host | +| `THEME` | `scripts/lets-hack.sh` | Vestments for every pane (`church` · `neon` · `crypt`) | | `HH_SESSION` · `HH_USER` | direnv autostart | tmux session name / your in-session name | ## Securing your connection diff --git a/docs/demo-save-load-poc.md b/docs/demo-save-load-poc.md index d203060..c705e7e 100644 --- a/docs/demo-save-load-poc.md +++ b/docs/demo-save-load-poc.md @@ -63,7 +63,7 @@ the chat provider is Ollama and a `qwen2.5-coder` is present (it is — pulled). ## Execution -`hh/demo-save-load.sh` drives the whole thing headlessly over tmux (per the +`hh/scripts/demo-save-load.sh` drives the whole thing headlessly over tmux (per the TUI-tmux test recipe): boots the server, runs client **session A**, injects the beats with `send-keys`, verifies via `capture-pane` + `docker exec`, then quits session A and opens client **session B** to load and confirm. It is a PoC / diff --git a/docs/spec-virtualbox-sandbox.md b/docs/spec-virtualbox-sandbox.md index 9d897a8..4ab98bc 100644 --- a/docs/spec-virtualbox-sandbox.md +++ b/docs/spec-virtualbox-sandbox.md @@ -87,7 +87,7 @@ documented fallback for images without sshd. Each headless VM needs a unique host loopback port for its SSH forward. Reuse the free-port discovery already used by the save/load PoC (see -`docs/demo-save-load-poc.md` / `hh/demo-save-load.sh`) so two sandboxes on one +`docs/demo-save-load-poc.md` / `hh/scripts/demo-save-load.sh`) so two sandboxes on one host don't collide. The owner is the only one who ever connects to it (`127.0.0.1:`), so it never leaves the host. @@ -146,7 +146,7 @@ desktop" become two ends of one workflow. ## 4. Installation handling — `ensure-vbox.sh` (detect first) -Mirror `hh/ensure-docker.sh` exactly in spirit: **a backend never installs +Mirror `hh/scripts/ensure-docker.sh` exactly in spirit: **a backend never installs anything silently.** ### 4.1 Detection (always first, zero side effects) @@ -166,7 +166,7 @@ loud with the remedy**, exactly like the Docker daemon message at `app.rs:1206`: ### 4.2 The installer script -`hh/ensure-vbox.sh`, invoked as `bash ensure-vbox.sh --yes` only when the user +`hh/scripts/ensure-vbox.sh`, invoked as `bash ensure-vbox.sh --yes` only when the user passed `--install` (matching how `prepare` shells `ensure-docker.sh --yes` at `sbx.rs:31`). It: @@ -229,7 +229,7 @@ sudo delegation, and save/load machinery are all backend-agnostic already. |---|---| | `hh/src/sbx.rs` | `Backend::VirtualBox` variant; arms in `parse`/`label`/`default_image`/`prepare`/`command_for`/`provision`/`set_sudo`/`save_state`/`list_snapshots`/`teardown`; `vbox_installed()`, `vbx()` SSH helper; `export_ova()` + `open_local()` (Mode B, local-only). | | `hh/src/app.rs` | accept `virtualbox`/`vbox` in `/sbx launch`; generalize `/sbx load` off hardcoded Docker (`:1282`) to the broker backend; new `/sbx export`, `/sbx open`, `/sbx gui` arms; extend usage string (`:1309`); install-missing error mirroring `:1206`. | -| `hh/ensure-vbox.sh` (new) | detect-first installer, per `§4`. | +| `hh/scripts/ensure-vbox.sh` (new) | detect-first installer, per `§4`. | | `hh/src/ui.rs` | add the three new commands to the clustered help menu. | | `README.MD` | backend table (`:175`) gains a `virtualbox` row; a short "share a VM, run it locally" subsection. | | `models.toml` / docs | none. | diff --git a/hh/direnv-autostart/.envrc b/hh/direnv-autostart/.envrc index c76df00..3035dee 100644 --- a/hh/direnv-autostart/.envrc +++ b/hh/direnv-autostart/.envrc @@ -31,5 +31,5 @@ if ! command -v tmux >/dev/null 2>&1; then elif tmux has-session -t "$SESSION" 2>/dev/null; then echo "'$SESSION' already live — tmux attach -t $SESSION (reveal its password in-app with /pw)" else - PW="${PW:-$(gen_pw)}" "$HH_DIR/lets-hack.sh" "$HH_USER" + PW="${PW:-$(gen_pw)}" "$HH_DIR/scripts/lets-hack.sh" "$HH_USER" fi diff --git a/bootstrap-ai.sh b/hh/scripts/bootstrap-ai.sh similarity index 96% rename from bootstrap-ai.sh rename to hh/scripts/bootstrap-ai.sh index c1aa6c6..d5513ab 100755 --- a/bootstrap-ai.sh +++ b/hh/scripts/bootstrap-ai.sh @@ -21,7 +21,7 @@ # OLLAMA_HOST daemon URL (default: http://localhost:11434) set -uo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" MODEL="${HH_AI_MODEL:-qwen2.5:3b}" OLLAMA_HOST="${OLLAMA_HOST:-http://localhost:11434}" INSTALLER_URL="https://ollama.com/install.sh" @@ -51,9 +51,9 @@ ollama_up() { curl -s --max-time 3 "$OLLAMA_HOST/api/tags" >/dev/null 2>&1; } # by default). --ai-only skips the baseline entirely (caller already ran it). if [[ $AI_ONLY -ne 1 ]]; then if [[ $CHECK_ONLY -eq 1 ]]; then - "$ROOT/bootstrap.sh" --no-ai --check || exit $? + "$ROOT/hh/scripts/bootstrap.sh" --no-ai --check || exit $? else - "$ROOT/bootstrap.sh" --no-ai "${RELEASE_ARGS[@]}" || exit $? + "$ROOT/hh/scripts/bootstrap.sh" --no-ai "${RELEASE_ARGS[@]}" || exit $? fi fi diff --git a/bootstrap.sh b/hh/scripts/bootstrap.sh similarity index 93% rename from bootstrap.sh rename to hh/scripts/bootstrap.sh index 562c727..20c9771 100755 --- a/bootstrap.sh +++ b/hh/scripts/bootstrap.sh @@ -17,10 +17,10 @@ # It still prompts before installing/pulling on an interactive terminal — skip # the whole thing with --no-ai, or skip the prompts with --yes. # -# After it finishes, spin up a local test session with: cd hh && ./lets-hack.sh +# After it finishes, spin up a local test session with: cd hh && ./scripts/lets-hack.sh set -uo pipefail -ROOT="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" VENV="$ROOT/.venv" HH_DIR="$ROOT/hh" @@ -92,14 +92,14 @@ fi if [[ $DO_AI -eq 1 ]]; then ai_args=(--ai-only) [[ $ASSUME_YES -eq 1 ]] && ai_args+=(--yes) - "$ROOT/bootstrap-ai.sh" "${ai_args[@]}" \ + "$ROOT/hh/scripts/bootstrap-ai.sh" "${ai_args[@]}" \ || echo "⚠ AI layer not completed — re-run ./bootstrap-ai.sh when ready" >&2 fi echo echo "ready. next steps:" -echo " cd hh && ./lets-hack.sh # local test session (server + clients in tmux)" +echo " cd hh && ./scripts/lets-hack.sh # local test session (server + clients in tmux)" echo " # or run the server + client by hand — see README.MD" if [[ $DO_AI -eq 0 ]]; then - echo " # AI layer skipped (--no-ai); add it later with ./bootstrap-ai.sh" + echo " # AI layer skipped (--no-ai); add it later with hh/scripts/bootstrap-ai.sh" fi diff --git a/hh/connect.sh b/hh/scripts/connect.sh similarity index 99% rename from hh/connect.sh rename to hh/scripts/connect.sh index c84cbd6..f68d8e6 100755 --- a/hh/connect.sh +++ b/hh/scripts/connect.sh @@ -19,7 +19,7 @@ # -P port (default: 4173, or $HH_PORT) # --tls use wss/https instead of the default plaintext-over-Tailscale set -euo pipefail -cd "$(dirname "$0")" +cd "$(dirname "$0")/.." DEFAULT_PORT=4173 DEFAULT_HOST=127.0.0.1 diff --git a/hh/demo-save-load.sh b/hh/scripts/demo-save-load.sh similarity index 99% rename from hh/demo-save-load.sh rename to hh/scripts/demo-save-load.sh index 0c6d093..4076ed6 100755 --- a/hh/demo-save-load.sh +++ b/hh/scripts/demo-save-load.sh @@ -10,12 +10,12 @@ # Headless: drives the ratatui client over tmux send-keys, asserts via # capture-pane + `docker exec`. PoC/correctness first; feeds video-toolkit later. # -# Usage: hh/demo-save-load.sh [--keep] +# Usage: hh/scripts/demo-save-load.sh [--keep] # --keep leave the server, container, image and tmux sessions up afterwards set -uo pipefail # ---- config ----------------------------------------------------------------- -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # Pick a free TCP port so we never collide with a stale server from another # session (a leftover server on a fixed port answers SRP with its own password # → spurious 401s). Honour an explicit $PORT if the caller forces one. diff --git a/hh/ensure-docker.sh b/hh/scripts/ensure-docker.sh similarity index 100% rename from hh/ensure-docker.sh rename to hh/scripts/ensure-docker.sh diff --git a/hh/ensure-vbox.sh b/hh/scripts/ensure-vbox.sh similarity index 100% rename from hh/ensure-vbox.sh rename to hh/scripts/ensure-vbox.sh diff --git a/hh/film-save-load.sh b/hh/scripts/film-save-load.sh similarity index 98% rename from hh/film-save-load.sh rename to hh/scripts/film-save-load.sh index 394a3e5..8fdf0b0 100755 --- a/hh/film-save-load.sh +++ b/hh/scripts/film-save-load.sh @@ -12,13 +12,13 @@ # session; `asciinema rec` runs in its own detached session that `tmux attach`es # to the inner one, so it mirrors exactly what we drive with send-keys. # -# Usage: hh/film-save-load.sh [--keep] [--no-render] +# Usage: hh/scripts/film-save-load.sh [--keep] [--no-render] # --keep leave server/sessions/container/image up afterwards # --no-render stop after writing the .cast (skip the mp4 render) set -uo pipefail # ---- config ----------------------------------------------------------------- -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" pick_port() { local p; for p in $(seq 4200 4280); do ss -ltn 2>/dev/null | grep -q ":$p " || { echo "$p"; return; }; done; echo 4173; } PORT="${PORT:-$(pick_port)}" PW="${PW:-malware-bless}" diff --git a/hh/film-virtualbox.sh b/hh/scripts/film-virtualbox.sh similarity index 99% rename from hh/film-virtualbox.sh rename to hh/scripts/film-virtualbox.sh index 0f7989d..99a14f6 100755 --- a/hh/film-virtualbox.sh +++ b/hh/scripts/film-virtualbox.sh @@ -11,13 +11,13 @@ # stitches title→terminal→gui→result; edge-tts narration + an SRT are muxed/burned # on top. # -# Usage: hh/film-virtualbox.sh [--keep] [--no-render] [--no-vm] +# Usage: hh/scripts/film-virtualbox.sh [--keep] [--no-render] [--no-vm] # --keep leave server/sessions up; don't power off the VM # --no-render stop after the captures (skip compose) # --no-vm skip booting the VM (reuse an already-running one for GUI grab) set -uo pipefail -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" VTK="$HOME/coding/video-toolkit" FORGE_PY="$HOME/anaconda3/bin/python3" pick_port() { local p; for p in $(seq 4240 4290); do ss -ltn 2>/dev/null | grep -q ":$p " || { echo "$p"; return; }; done; echo 4173; } diff --git a/host-room.sh b/hh/scripts/host-room.sh similarity index 98% rename from host-room.sh rename to hh/scripts/host-room.sh index 4548ec8..b171bd1 100755 --- a/host-room.sh +++ b/hh/scripts/host-room.sh @@ -54,7 +54,7 @@ examples: EOF } -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # repo root (where this lives) +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # repo root PY="$ROOT/.venv/bin/python" [[ -x "$PY" ]] || PY="$(command -v python3 || command -v python)" diff --git a/hh/join.sh b/hh/scripts/join.sh similarity index 98% rename from hh/join.sh rename to hh/scripts/join.sh index 691b725..b61a1cf 100755 --- a/hh/join.sh +++ b/hh/scripts/join.sh @@ -6,7 +6,7 @@ # prompt — never hardcoded, so it can't drift from the room's random password. NAME="${1:-guest}" HOST="${2:-127.0.0.1}" -cd "$(dirname "$0")" +cd "$(dirname "$0")/.." # Sync the latest code before joining, then rebuild so it takes effect. Pulls # are best-effort and fast-forward only: an unreachable remote or diverged diff --git a/hh/lets-hack.sh b/hh/scripts/lets-hack.sh similarity index 99% rename from hh/lets-hack.sh rename to hh/scripts/lets-hack.sh index 74b6b65..434678c 100755 --- a/hh/lets-hack.sh +++ b/hh/scripts/lets-hack.sh @@ -65,7 +65,7 @@ examples: EOF } -HERE="$(cd "$(dirname "$0")" && pwd)" # .../hh +HERE="$(cd "$(dirname "$0")/.." && pwd)" # .../hh ROOT="$(cd "$HERE/.." && pwd)" # repo root PY="$ROOT/.venv/bin/python" # venv always from the real checkout BIN="$HERE/target/debug/hack-house" diff --git a/hh/smoke-e2e.sh b/hh/scripts/smoke-e2e.sh similarity index 98% rename from hh/smoke-e2e.sh rename to hh/scripts/smoke-e2e.sh index cc2b725..35af3e0 100755 --- a/hh/smoke-e2e.sh +++ b/hh/scripts/smoke-e2e.sh @@ -15,7 +15,7 @@ # Env overrides: PY= BIN= PORT= PW= set -uo pipefail -REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # Python: prefer the repo venv locally, fall back to PATH (CI installs into the # job's own interpreter). if [[ -z "${PY:-}" ]]; then diff --git a/hh/smoke.sh b/hh/scripts/smoke.sh similarity index 97% rename from hh/smoke.sh rename to hh/scripts/smoke.sh index 6eb71b2..131b55c 100755 --- a/hh/smoke.sh +++ b/hh/scripts/smoke.sh @@ -3,10 +3,10 @@ # Exercises the full use-case path end to end against a live server: # rust unit tests → SRP self-test → boot server → rust client handshake + # round-trip → cross-language (python decrypts what the rust client sent). -# Run from anywhere: hh/smoke.sh +# Run from anywhere: hh/scripts/smoke.sh set -uo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" # .../hh +HERE="$(cd "$(dirname "$0")/.." && pwd)" # .../hh ROOT="$(cd "$HERE/.." && pwd)" # repo root PY="$ROOT/.venv/bin/python" BIN="$HERE/target/debug/hack-house" diff --git a/hh/test-features.sh b/hh/scripts/test-features.sh similarity index 99% rename from hh/test-features.sh rename to hh/scripts/test-features.sh index e967760..838dc69 100755 --- a/hh/test-features.sh +++ b/hh/scripts/test-features.sh @@ -14,7 +14,7 @@ # tmux attach -t hh-autotest set -uo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" # .../hh +HERE="$(cd "$(dirname "$0")/.." && pwd)" # .../hh ROOT="$(cd "$HERE/.." && pwd)" # repo root PY="$ROOT/.venv/bin/python" BIN="$HERE/target/debug/hack-house" diff --git a/hh/src/app.rs b/hh/src/app.rs index 315bcb1..b68301a 100644 --- a/hh/src/app.rs +++ b/hh/src/app.rs @@ -1397,7 +1397,7 @@ fn handle_command( && !start_daemon && !sbx::docker_daemon_up() { - app.err("docker daemon is not running — retry with `/sbx launch docker --start` to boot it (sudo), or run ./ensure-docker.sh in a terminal first"); + app.err("docker daemon is not running — retry with `/sbx launch docker --start` to boot it (sudo), or run ./scripts/ensure-docker.sh in a terminal first"); } else { let sz = term.size().map(|s| (s.width, s.height)).unwrap_or((80, 24)); let (rows, cols) = sbx_dims(sz.0, sz.1); @@ -1518,7 +1518,7 @@ fn handle_command( tokio::spawn(async move { let res = tokio::task::spawn_blocking(|| { let ver = sbx::vbox_version().ok_or_else(|| { - "VirtualBox isn't installed — install it with `/sbx gui --install`, or run ./ensure-vbox.sh".to_string() + "VirtualBox isn't installed — install it with `/sbx gui --install`, or run ./scripts/ensure-vbox.sh".to_string() })?; let vms = sbx::list_vms().map_err(|e| e.to_string())?; Ok::<_, String>((ver, vms)) @@ -1821,10 +1821,10 @@ fn handle_command( ms.join(", ") ), Ok(_) => "ollama is reachable but has no models pulled — \ - `ollama pull qwen2.5:3b` or run ./bootstrap-ai.sh" + `ollama pull qwen2.5:3b` or run ./scripts/bootstrap-ai.sh" .to_string(), Err(_) => "ollama not reachable at localhost:11434 — run \ - ./bootstrap-ai.sh, or `/ai start ` for a cloud model" + ./scripts/bootstrap-ai.sh, or `/ai start ` for a cloud model" .to_string(), }; let _ = tx.send(Net::Sys(msg)); diff --git a/hh/src/sbx.rs b/hh/src/sbx.rs index 23d8909..6d296c8 100644 --- a/hh/src/sbx.rs +++ b/hh/src/sbx.rs @@ -12,10 +12,10 @@ use std::io::{Read, Write}; use std::process::{Command, Stdio}; use std::sync::mpsc; -/// Helper that ensures the Docker daemon is running (ships beside this source). -const ENSURE_DOCKER: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/ensure-docker.sh"); -/// Detect-first VirtualBox installer (ships beside this source). -const ENSURE_VBOX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/ensure-vbox.sh"); +/// Helper that ensures the Docker daemon is running (ships in hh/scripts/). +const ENSURE_DOCKER: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/ensure-docker.sh"); +/// Detect-first VirtualBox installer (ships in hh/scripts/). +const ENSURE_VBOX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/scripts/ensure-vbox.sh"); /// Is the Docker daemon accepting connections? (`docker info` succeeds.) pub fn docker_daemon_up() -> bool {