gsh/scripts/build-wsl2-image.sh
Tyler J King 3c4042ce8e feat: WSL2 jumphost image builder
scripts/build-wsl2-image.sh — idempotent setup for governed jumphost.

Installs: gsh, kubectl, helm (all to ~/.local/bin, no sudo needed)
Configures: corpus directory, SSH aliases (dev.gsh, stg.gsh),
  .gshrc environment defaults
Export: --export flag prints wsl --export/import commands

No sudo required for gsh/corpus/config setup. System packages
(curl, git, etc.) prompt for manual install if sudo unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:04:38 -04:00

193 lines
6.3 KiB
Bash
Executable file

#!/bin/bash
# build-wsl2-image.sh — Configure WSL2 instance as governed jumphost
#
# Usage:
# ./scripts/build-wsl2-image.sh # Configure current instance
# ./scripts/build-wsl2-image.sh --export # Configure + export hint
#
# Idempotent: safe to run multiple times. No sudo required for gsh setup.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GSH_DIR="$(dirname "$SCRIPT_DIR")"
CORPUS_CID="sha256:dev-jumphost"
CORPUS_DIR="$HOME/.gsh/corpus/$CORPUS_CID"
echo "=== Guildhouse WSL2 Jumphost Builder ==="
echo ""
# ─── Step 1: System packages ─────────────────────────────
echo "[1/7] Checking system packages..."
MISSING=""
for pkg in curl git jq openssh-client ca-certificates; do
dpkg -s "$pkg" &>/dev/null || MISSING="$MISSING $pkg"
done
if [ -n "$MISSING" ]; then
echo " Missing:$MISSING"
if sudo -n true 2>/dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq $MISSING 2>/dev/null
else
echo " Run: sudo apt-get install -y$MISSING"
fi
else
echo " All present"
fi
# ─── Step 2: kubectl ─────────────────────────────────────
echo "[2/7] kubectl..."
if command -v kubectl &>/dev/null; then
echo " $(kubectl version --client 2>/dev/null | head -1)"
else
echo " Installing kubectl..."
curl -sLo "$HOME/.local/bin/kubectl" "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x "$HOME/.local/bin/kubectl"
echo " Installed: $(kubectl version --client 2>/dev/null | head -1)"
fi
# ─── Step 3: helm ────────────────────────────────────────
echo "[3/7] helm..."
if command -v helm &>/dev/null; then
echo " $(helm version --short 2>/dev/null)"
else
echo " Installing helm..."
curl -fsSL https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz | tar xz -C /tmp linux-amd64/helm
mv /tmp/linux-amd64/helm "$HOME/.local/bin/helm"
chmod +x "$HOME/.local/bin/helm"
rm -rf /tmp/linux-amd64
echo " Installed: $(helm version --short 2>/dev/null)"
fi
# ─── Step 4: gsh binary ─────────────────────────────────
echo "[4/7] gsh..."
if [ -f "$GSH_DIR/target/release/gsh" ]; then
mkdir -p "$HOME/.local/bin"
cp "$GSH_DIR/target/release/gsh" "$HOME/.local/bin/gsh"
chmod +x "$HOME/.local/bin/gsh"
echo " Installed to ~/.local/bin/gsh"
elif command -v gsh &>/dev/null; then
echo " Already on PATH: $(which gsh)"
else
echo " ERROR: gsh binary not found. Build first: cd ~/projects/gsh && cargo build --release"
exit 1
fi
# Ensure ~/.local/bin on PATH
if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then
export PATH="$HOME/.local/bin:$PATH"
fi
# ─── Step 5: Corpus directory ────────────────────────────
echo "[5/7] Corpus..."
mkdir -p "$CORPUS_DIR"
for tool in kubectl helm; do
REAL_PATH=$(which $tool 2>/dev/null || true)
if [ -n "$REAL_PATH" ]; then
# Symlink to the real binary (simpler than wrapper scripts)
ln -sf "$REAL_PATH" "$CORPUS_DIR/$tool" 2>/dev/null || true
echo " $tool$REAL_PATH"
fi
done
echo " Corpus at: $CORPUS_DIR"
# ─── Step 6: SSH config ─────────────────────────────────
echo "[6/7] SSH config..."
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
# Generate SSH key if none exists
if [ ! -f "$HOME/.ssh/id_ed25519" ]; then
ssh-keygen -t ed25519 -f "$HOME/.ssh/id_ed25519" -N "" -q
echo " Generated SSH key: ~/.ssh/id_ed25519"
fi
# Add gsh SSH aliases if not present
add_ssh_host() {
local name="$1" host="$2" port="$3"
if ! grep -q "Host $name" "$HOME/.ssh/config" 2>/dev/null; then
cat >> "$HOME/.ssh/config" << EOF
Host $name
HostName $host
Port $port
User \$(whoami)
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
EOF
echo " Added: $name$host:$port"
else
echo " Exists: $name"
fi
}
touch "$HOME/.ssh/config"
chmod 600 "$HOME/.ssh/config"
add_ssh_host "dev.gsh" "127.0.0.1" "2223"
add_ssh_host "stg.gsh" "178.104.110.197" "30222"
# ─── Step 7: Environment + shell config ──────────────────
echo "[7/7] Environment..."
# .gshrc — governed shell defaults
cat > "$HOME/.gshrc" << GSHRC
# Guildhouse Governed Shell environment
# Sourced by .bashrc
export GSAP_CORPUS_CID="$CORPUS_CID"
export GSH_CORPUS_DIR="\$HOME/.gsh/corpus"
# Add gsh to PATH
export PATH="\$HOME/.local/bin:\$PATH"
GSHRC
echo " Created ~/.gshrc"
# Source from .bashrc if not already
if ! grep -q "gshrc" "$HOME/.bashrc" 2>/dev/null; then
echo '' >> "$HOME/.bashrc"
echo '# Guildhouse governed shell' >> "$HOME/.bashrc"
echo '[ -f ~/.gshrc ] && source ~/.gshrc' >> "$HOME/.bashrc"
echo " Added .gshrc to .bashrc"
fi
# ─── Done ────────────────────────────────────────────────
echo ""
echo "=== Jumphost configured ==="
echo ""
echo " gsh: $(which gsh 2>/dev/null || echo '~/.local/bin/gsh')"
echo " kubectl: $(which kubectl)"
echo " helm: $(which helm)"
echo " corpus: $CORPUS_DIR/"
echo ""
echo "Connect:"
echo " ssh dev.gsh — governed shell (local Docker Desktop)"
echo " ssh stg.gsh — governed shell (Hetzner staging)"
echo " gsh — local ungoverned shell"
echo ""
# ─── Optional: Export hint ───────────────────────────────
if [ "${1:-}" = "--export" ]; then
echo "=== Export ==="
echo ""
echo "Clean build artifacts to reduce size:"
echo " rm -rf ~/projects/gsh/target/debug"
echo " rm -rf ~/projects/substrate-project/substrate/target/debug"
echo " sudo apt-get clean"
echo ""
DISTRO_NAME=$(hostname 2>/dev/null || echo "gsh-jumphost")
echo "From PowerShell:"
echo " wsl --export $DISTRO_NAME gsh-jumphost.tar"
echo ""
echo "Import on another machine:"
echo " wsl --import gsh-jumphost C:\\WSL\\gsh-jumphost gsh-jumphost.tar"
echo " wsl -d gsh-jumphost"
fi