chore: consolidate all shell scripts into hh/scripts/
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

Move the 14 hack-house scripts (bootstrap, lets-hack, host-room, smoke,
demo/film harnesses, connect/join, ensure-docker/vbox) into hh/scripts/
and fix their path resolution for the deeper location. Update cross-refs:
sbx.rs script consts, app.rs hints, CI, direnv .envrc, README, and docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-06-06 17:49:32 -07:00
parent 9f7ab92270
commit 1fa8c332ed
21 changed files with 57 additions and 57 deletions
+1 -1
View File
@@ -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
+20 -20
View File
@@ -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 <profile>` (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 <model>`.
- **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
+1 -1
View File
@@ -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 /
+4 -4
View File
@@ -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:<port>`), 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. |
+1 -1
View File
@@ -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
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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.
@@ -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}"
@@ -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; }
+1 -1
View File
@@ -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)"
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -15,7 +15,7 @@
# Env overrides: PY=<python> BIN=<client binary> PORT=<port> PW=<password>
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
+2 -2
View File
@@ -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"
@@ -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"
+4 -4
View File
@@ -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 <vm> --install`, or run ./ensure-vbox.sh".to_string()
"VirtualBox isn't installed — install it with `/sbx gui <vm> --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 <profile>` for a cloud model"
./scripts/bootstrap-ai.sh, or `/ai start <profile>` for a cloud model"
.to_string(),
};
let _ = tx.send(Net::Sys(msg));
+4 -4
View File
@@ -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 {