gsh/scripts/build-substrate-wsl2.sh
Tyler J King b363d1da3b feat: Substrate WSL2 distro builder (Fedora 41)
scripts/build-substrate-wsl2.sh — builds a custom Fedora WSL2 distro
with gsh as the default governed shell for the operator user.

Image contents (337MB):
  Fedora 41 + systemd
  gsh as login shell (/usr/local/bin/gsh)
  bascule-proxy for governed cluster connections
  kubectl + helm with corpus symlinks
  SSH aliases: dev.gsh, stg.gsh
  WSL2 config: systemd=true, default user=operator

Build: docker builds Fedora rootfs, exports as tar
Import: wsl --import substrate-gsh C:\WSL\substrate-gsh substrate-gsh.tar
Boot: wsl -d substrate-gsh → governed shell prompt

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

167 lines
6.5 KiB
Bash
Executable file

#!/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
RUN printf 'export GSAP_CORPUS_CID="sha256:substrate-jumphost"\nexport GSH_CORPUS_DIR="$HOME/.gsh/corpus"\nexport PATH="$HOME/.local/bin:$PATH"\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"