#!/bin/bash # bootstrap-jumphost.sh # Configure this WSL2 instance as a governed jumphost. # Run once. Idempotent. set -euo pipefail SUBSTRATE_ROOT="/opt/substrate" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" GSH_ROOT="$(dirname "$SCRIPT_DIR")" GSH_BIN="$GSH_ROOT/target/release/gsh" DEV_CORPUS_CID="sha256:dev-jumphost" echo "=== Governed Jumphost Bootstrap ===" echo "Substrate root: $SUBSTRATE_ROOT" echo "gsh binary: $GSH_BIN" echo "" # ── Directory structure ─────────────────────────────────────── echo "Creating directory structure..." sudo mkdir -p "$SUBSTRATE_ROOT/corpus" sudo mkdir -p "$SUBSTRATE_ROOT/org-ops/bin" sudo mkdir -p "$SUBSTRATE_ROOT/org-ops/lib" sudo mkdir -p "$SUBSTRATE_ROOT/profiles" sudo chown -R "$(whoami)" "$SUBSTRATE_ROOT" # ── Shared governance library ───────────────────────────────── cat > "$SUBSTRATE_ROOT/org-ops/lib/governance.sh" << 'GOVLIB' # governance.sh — shared functions for org-ops wrappers # Sourced by every governed wrapper. _gov_params_cid() { echo -n "$*" | sha256sum | awk '{print "sha256:"$1}' } _gov_log() { local binary="$1" exit_code="$2" params_cid="$3" local session_id="${BASCULE_SESSION_ID:-ungoverned}" if [ "$session_id" != "ungoverned" ]; then >&2 printf '{"governance":"executed","binary":"%s","params_cid":"%s","session_id":"%s","exit_code":%d}\n' \ "$binary" "$params_cid" "$session_id" "$exit_code" fi } GOVLIB echo " governance.sh installed" # ── Org-ops wrappers ───────────────────────────────────────── write_wrapper() { local name="$1" real_path="$2" local wrapper="$SUBSTRATE_ROOT/org-ops/bin/${name}-governed" cat > "$wrapper" << WRAPPER #!/bin/bash # ${name}-governed — governance wrapper for $name # Generated by bootstrap-jumphost.sh set -euo pipefail source "$SUBSTRATE_ROOT/org-ops/lib/governance.sh" PARAMS_CID=\$(_gov_params_cid "\$@") $real_path "\$@" EXIT_CODE=\$? _gov_log "$name" "\$EXIT_CODE" "\$PARAMS_CID" exit \$EXIT_CODE WRAPPER chmod +x "$wrapper" echo " ${name}-governed → $real_path" } echo "Installing org-ops wrappers..." write_wrapper "kubectl" "/usr/local/bin/kubectl" write_wrapper "helm" "/usr/local/bin/helm" write_wrapper "hcloud" "/tmp/hcloud" write_wrapper "ansible-playbook" "/usr/bin/ansible-playbook" # ── Dev corpus ──────────────────────────────────────────────── echo "Creating dev corpus ($DEV_CORPUS_CID)..." mkdir -p "$SUBSTRATE_ROOT/corpus/$DEV_CORPUS_CID" for tool in kubectl helm hcloud ansible-playbook; do ln -sf "$SUBSTRATE_ROOT/org-ops/bin/${tool}-governed" \ "$SUBSTRATE_ROOT/corpus/$DEV_CORPUS_CID/$tool" done echo " Binaries linked into corpus" # ── Capability profile ──────────────────────────────────────── cat > "$SUBSTRATE_ROOT/profiles/$DEV_CORPUS_CID.json" << 'PROFILE' { "corpus_id": "dev-jumphost", "corpus_entry_cid": "sha256:dev-jumphost", "classification": "development", "risk_level": "elevated", "binaries": [ { "name": "kubectl", "layer_classifications": ["applications"], "access_capabilities": ["read", "write"], "wrapped_by": "org-ops/kubectl-governed", "real_path": "/usr/local/bin/kubectl" }, { "name": "helm", "layer_classifications": ["applications"], "access_capabilities": ["read", "write"], "wrapped_by": "org-ops/helm-governed", "real_path": "/usr/local/bin/helm" }, { "name": "hcloud", "layer_classifications": ["systems"], "access_capabilities": ["read", "write"], "wrapped_by": "org-ops/hcloud-governed", "real_path": "/tmp/hcloud" }, { "name": "ansible-playbook", "layer_classifications": ["systems", "applications", "network"], "access_capabilities": ["read", "write"], "wrapped_by": "org-ops/ansible-governed", "real_path": "/usr/bin/ansible-playbook" } ] } PROFILE echo " Capability profile written" # ── Install gsh ─────────────────────────────────────────────── if [ -f "$GSH_BIN" ]; then sudo cp "$GSH_BIN" /usr/local/bin/gsh echo " gsh installed to /usr/local/bin/gsh" else echo " WARNING: gsh binary not found at $GSH_BIN" echo " Run: cd $GSH_ROOT && cargo build --release" fi # ── Environment setup ───────────────────────────────────────── GSH_ENV_FILE="$HOME/.gsh-env" cat > "$GSH_ENV_FILE" << ENV # Governed shell environment — sourced by .bashrc export GSAP_CORPUS_CID="$DEV_CORPUS_CID" export GSAP_BROKER_URL="\${GSAP_BROKER_URL:-}" export PATH="/opt/substrate/corpus/$DEV_CORPUS_CID:\$PATH" ENV # Add to .bashrc if not already there: if ! grep -q "gsh-env" "$HOME/.bashrc" 2>/dev/null; then echo "" >> "$HOME/.bashrc" echo "# Governed shell environment" >> "$HOME/.bashrc" echo "[ -f $GSH_ENV_FILE ] && source $GSH_ENV_FILE" >> "$HOME/.bashrc" echo " .bashrc updated to source gsh-env" else echo " .bashrc already sources gsh-env" fi echo "" echo "=== Bootstrap Complete ===" echo "" echo "Corpus: $DEV_CORPUS_CID" echo "Profile: $SUBSTRATE_ROOT/profiles/$DEV_CORPUS_CID.json" echo "" echo "Usage:" echo " source ~/.gsh-env" echo " gsh --ungoverned --exec 'kubectl get nodes'" echo " gsh --exec 'kubectl --context docker-desktop get nodes'" echo "" echo "For governed mode, set GSAP_BROKER_URL and GSAP_TOKEN."