Session principal resolution chain: GSH_PRINCIPAL → BASCULE_DISPLAY_NAME → derive from DID → whoami() GSH_DID → BASCULE_USER_DID → whoami() .gshrc Windows identity detection: Entra-joined: whoami /upn → tking@guildhouse.dev → DID Domain-joined: USERNAME@USERDNSDOMAIN → DID Local: USERNAME only (no DID) Governed sessions (Bascule) override with authenticated identity. Non-WSL2 environments fall back silently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
187 lines
7.4 KiB
Bash
Executable file
187 lines
7.4 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 + 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"
|