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>
This commit is contained in:
parent
02bcd58c99
commit
b363d1da3b
2 changed files with 168 additions and 0 deletions
1
dist/.gitignore
vendored
Normal file
1
dist/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
*.tar
|
||||
167
scripts/build-substrate-wsl2.sh
Executable file
167
scripts/build-substrate-wsl2.sh
Executable file
|
|
@ -0,0 +1,167 @@
|
|||
#!/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"
|
||||
Loading…
Reference in a new issue