#!/bin/bash # build-substrate-wsl2.sh — Build Substrate WSL2 distro image (Fedora-based) # # Builds a minimal Fedora rootfs with gsh as the default governed shell. # Output: ./dist/substrate-gsh.tar (importable via wsl --import) # # Usage: # ./scripts/build-substrate-wsl2.sh # # # From PowerShell: # wsl --import substrate-gsh C:\WSL\substrate-gsh .\substrate-gsh.tar # wsl -d substrate-gsh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GSH_DIR="$(dirname "$SCRIPT_DIR")" DIST_DIR="$GSH_DIR/dist" BUILD_TAG="substrate-gsh-builder" SUBSTRATE_DIR="${SUBSTRATE_DIR:-$(find ~/projects -maxdepth 2 -type d -name "substrate" -path "*/substrate-project/*" 2>/dev/null | head -1)}" echo "=== Substrate WSL2 Distro Builder ===" echo "" # ─── Pre-flight ─────────────────────────────────────────── GSH_BIN="$GSH_DIR/target/release/gsh" if [ ! -f "$GSH_BIN" ]; then echo "Building gsh..." (cd "$GSH_DIR" && cargo build --release 2>&1 | tail -3) fi echo "gsh: $(du -h "$GSH_BIN" | cut -f1)" PROXY_BIN="$SUBSTRATE_DIR/target/release/bascule-proxy" if [ -f "$PROXY_BIN" ]; then echo "bascule-proxy: $(du -h "$PROXY_BIN" | cut -f1)" else echo "bascule-proxy: not found (will be excluded)" PROXY_BIN="" fi # ─── Build context ──────────────────────────────────────── mkdir -p "$DIST_DIR" BUILD_CTX=$(mktemp -d) trap "rm -rf $BUILD_CTX" EXIT cp "$GSH_BIN" "$BUILD_CTX/gsh" if [ -n "$PROXY_BIN" ]; then cp "$PROXY_BIN" "$BUILD_CTX/bascule-proxy" fi # ─── Dockerfile ─────────────────────────────────────────── cat > "$BUILD_CTX/Dockerfile" << 'DOCKERFILE' FROM fedora:41 # System packages RUN dnf install -y --setopt=install_weak_deps=False \ bash coreutils findutils grep sed gawk \ curl git jq openssh-clients ca-certificates \ passwd sudo vim-minimal less procps-ng \ iproute iputils hostname which tar gzip unzip \ && dnf clean all # kubectl RUN curl -fsSLo /usr/local/bin/kubectl \ "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ && chmod +x /usr/local/bin/kubectl # helm RUN curl -fsSL https://get.helm.sh/helm-v3.17.3-linux-amd64.tar.gz | tar xz -C /tmp \ && mv /tmp/linux-amd64/helm /usr/local/bin/helm && chmod +x /usr/local/bin/helm \ && rm -rf /tmp/linux-amd64 # gsh COPY gsh /usr/local/bin/gsh RUN chmod +x /usr/local/bin/gsh # bascule-proxy (optional) COPY bascule-proxy* /tmp/ RUN if [ -f /tmp/bascule-proxy ]; then \ mv /tmp/bascule-proxy /usr/local/bin/bascule-proxy && chmod +x /usr/local/bin/bascule-proxy; \ fi && rm -f /tmp/bascule-proxy* # Operator user (replace Fedora's system operator account) RUN userdel operator 2>/dev/null; groupadd -f wheel \ && useradd -m -s /bin/bash -G wheel operator \ && mkdir -p /etc/sudoers.d \ && echo '%wheel ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/wheel \ && chmod 0440 /etc/sudoers.d/wheel \ && echo /usr/local/bin/gsh >> /etc/shells \ && chsh -s /usr/local/bin/gsh operator # Corpus RUN mkdir -p /home/operator/.gsh/corpus/sha256:substrate-jumphost \ && ln -s /usr/local/bin/kubectl /home/operator/.gsh/corpus/sha256:substrate-jumphost/kubectl \ && ln -s /usr/local/bin/helm /home/operator/.gsh/corpus/sha256:substrate-jumphost/helm # SSH config RUN mkdir -p /home/operator/.ssh && chmod 700 /home/operator/.ssh \ && printf 'Host dev.gsh\n HostName 127.0.0.1\n Port 2223\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n\nHost stg.gsh\n HostName 178.104.110.197\n Port 30222\n StrictHostKeyChecking no\n UserKnownHostsFile /dev/null\n' \ > /home/operator/.ssh/config \ && chmod 600 /home/operator/.ssh/config # gsh environment + Windows Entra identity detection RUN printf 'export GSAP_CORPUS_CID="sha256:substrate-jumphost"\n\ export GSH_CORPUS_DIR="$HOME/.gsh/corpus"\n\ export PATH="$HOME/.local/bin:$PATH"\n\ \n\ # Detect Windows principal (WSL2 interop)\n\ # Entra-joined: whoami /upn → tking@guildhouse.dev\n\ # Domain-joined: USERNAME@USERDNSDOMAIN\n\ # Local: USERNAME only\n\ if command -v cmd.exe >/dev/null 2>&1; then\n\ _UPN=$(cmd.exe /c "whoami /upn" 2>/dev/null | tr -d "\\r")\n\ if [ -n "$_UPN" ] && echo "$_UPN" | grep -q "@"; then\n\ _UPN_LC=$(echo "$_UPN" | tr "A-Z" "a-z")\n\ export GSH_PRINCIPAL="$_UPN_LC"\n\ _DOMAIN=$(echo "$_UPN_LC" | cut -d@ -f2)\n\ _USER=$(echo "$_UPN_LC" | cut -d@ -f1)\n\ export GSH_DID="did:web:${_DOMAIN}/user/${_USER}"\n\ else\n\ _WIN_USER=$(cmd.exe /c "echo %%USERNAME%%" 2>/dev/null | tr -d "\\r")\n\ [ -n "$_WIN_USER" ] && export GSH_PRINCIPAL="$_WIN_USER"\n\ fi\n\ unset _UPN _UPN_LC _DOMAIN _USER _WIN_USER\n\ fi\n' > /home/operator/.gshrc \ && printf '[ -f ~/.gshrc ] && source ~/.gshrc\n' >> /home/operator/.bashrc # Fix ownership RUN chown -R operator:operator /home/operator # WSL2 config RUN printf '[boot]\nsystemd=true\n\n[user]\ndefault=operator\n\n[interop]\nenabled=true\nappendWindowsPath=false\n\n[network]\ngenerateResolvConf=true\n' \ > /etc/wsl.conf # MOTD RUN printf '\n Substrate Governed Shell — Guildhouse Edition\n\n ssh dev.gsh — local dev cluster\n ssh stg.gsh — Hetzner staging\n gsh — local ungoverned shell\n\n' \ > /etc/motd DOCKERFILE # If no bascule-proxy, create a placeholder so COPY doesn't fail if [ -z "$PROXY_BIN" ]; then touch "$BUILD_CTX/bascule-proxy-skip" fi # ─── Build ──────────────────────────────────────────────── echo "" echo "[1/3] Building Fedora rootfs..." docker build -t "$BUILD_TAG" "$BUILD_CTX" 2>&1 | tail -10 # ─── Export ─────────────────────────────────────────────── echo "" echo "[2/3] Exporting rootfs..." CONTAINER_ID=$(docker create "$BUILD_TAG" /bin/true) docker export "$CONTAINER_ID" > "$DIST_DIR/substrate-gsh.tar" docker rm "$CONTAINER_ID" > /dev/null TAR_SIZE=$(du -h "$DIST_DIR/substrate-gsh.tar" | cut -f1) echo "Exported: $DIST_DIR/substrate-gsh.tar ($TAR_SIZE)" # ─── Stage for Windows ──────────────────────────────────── echo "" echo "[3/3] Staging..." WIN_USER=$(cmd.exe /c "echo %USERNAME%" 2>/dev/null | tr -d '\r' || true) if [ -n "$WIN_USER" ] && [ -d "/mnt/c/Users/$WIN_USER/Desktop" ]; then cp "$DIST_DIR/substrate-gsh.tar" "/mnt/c/Users/$WIN_USER/Desktop/substrate-gsh.tar" echo "Copied to Desktop: substrate-gsh.tar" fi echo "" echo "=== Build Complete ===" echo "" echo " Image: $DIST_DIR/substrate-gsh.tar ($TAR_SIZE)" echo " gsh: /usr/local/bin/gsh (login shell)" echo " kubectl: /usr/local/bin/kubectl" echo " helm: /usr/local/bin/helm" echo " proxy: $([ -n "$PROXY_BIN" ] && echo '/usr/local/bin/bascule-proxy' || echo 'not included')" echo " User: operator (sudo, gsh login shell)" echo " Corpus: sha256:substrate-jumphost" echo "" echo "From PowerShell:" echo " wsl --import substrate-gsh C:\\WSL\\substrate-gsh .\\substrate-gsh.tar" echo " wsl -d substrate-gsh"