diff --git a/infra/build-machines/haskell/scripts/bootstrap-alpine.sh b/infra/build-machines/haskell/scripts/bootstrap-alpine.sh new file mode 100755 index 0000000..3fafeb4 --- /dev/null +++ b/infra/build-machines/haskell/scripts/bootstrap-alpine.sh @@ -0,0 +1,157 @@ +#!/bin/sh +# bootstrap-alpine.sh — One-shot setup for Haskell build machine on Alpine Linux +# +# Usage (from workstation): +# scp bootstrap-alpine.sh root@:/tmp/ +# ssh root@ sh /tmp/bootstrap-alpine.sh +# +# What it does: +# 1. Installs system dependencies (apk) +# 2. Creates 'build' user with sudo +# 3. Installs GHCup + GHC + Cabal (single version — 8GB disk constraint) +# 4. Installs build-agent + OpenRC service +# 5. Configures SSH for key-based access +# +# Disk budget (8GB total): +# Alpine base: ~200 MB +# Build deps: ~300 MB +# GHCup + GHC: ~1800 MB +# Cabal + pkgdb: ~300 MB +# Headroom: ~5400 MB (for project builds) +set -eu + +GHC_VERSION="${GHC_VERSION:-9.8.4}" +CABAL_VERSION="${CABAL_VERSION:-3.12.1.0}" + +echo "=== Haskell Build Machine Bootstrap (Alpine) ===" +echo "GHC: ${GHC_VERSION} | Cabal: ${CABAL_VERSION}" +echo "" + +# ---- 1. System packages ---- +echo "[1/5] Installing system packages..." +apk update +apk add \ + build-base curl git \ + gmp-dev libffi-dev zlib-dev ncurses-dev \ + pkgconf openssh autossh jq rsync \ + sudo shadow python3 \ + musl-dev gcc g++ make \ + linux-headers \ + xz tar + +# ---- 2. Build user ---- +echo "[2/5] Creating build user..." +if ! id build >/dev/null 2>&1; then + adduser -D -s /bin/sh -h /home/build build + echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build + chmod 440 /etc/sudoers.d/build +fi + +# Create build workspace +mkdir -p /build +chown build:build /build + +# SSH directory +mkdir -p /home/build/.ssh +chmod 700 /home/build/.ssh +chown build:build /home/build/.ssh + +# ---- 3. Haskell toolchain ---- +echo "[3/5] Installing Haskell toolchain (this takes a while)..." + +# GHCup needs these env vars for non-interactive install +export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 +export BOOTSTRAP_HASKELL_GHC_VERSION="$GHC_VERSION" +export BOOTSTRAP_HASKELL_CABAL_VERSION="$CABAL_VERSION" +export BOOTSTRAP_HASKELL_INSTALL_STACK=0 +export BOOTSTRAP_HASKELL_INSTALL_HLS=0 + +# Install GHCup as build user +su - build -c " + export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 + export BOOTSTRAP_HASKELL_GHC_VERSION='$GHC_VERSION' + export BOOTSTRAP_HASKELL_CABAL_VERSION='$CABAL_VERSION' + export BOOTSTRAP_HASKELL_INSTALL_STACK=0 + export BOOTSTRAP_HASKELL_INSTALL_HLS=0 + curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh +" + +# Add ghcup to build user's profile +cat >> /home/build/.profile << 'PROFILE' +[ -f "$HOME/.ghcup/env" ] && . "$HOME/.ghcup/env" +PROFILE + +# Verify installation +su - build -c '. ~/.ghcup/env && ghc --version && cabal --version' + +# Pre-warm cabal package index +echo "[3/5] Warming cabal package index..." +su - build -c '. ~/.ghcup/env && cabal update' + +# ---- 4. Build agent ---- +echo "[4/5] Installing build-agent..." + +# The agent script will be copied separately via SCP +# Here we just set up the OpenRC service skeleton +cat > /etc/init.d/build-agent << 'INITD' +#!/sbin/openrc-run + +name="build-agent" +description="Haskell Build Agent — State Hub registration + SSH reverse tunnel" +command="/usr/local/bin/build-agent" +command_user="build" +command_background=true +pidfile="/run/${RC_SVCNAME}.pid" +output_log="/var/log/build-agent.log" +error_log="/var/log/build-agent.log" + +depend() { + need net + after firewall +} +INITD +chmod 755 /etc/init.d/build-agent + +# Create placeholder env file +if [ ! -f /etc/build-agent.env ]; then + cat > /etc/build-agent.env << 'ENV' +# Custodian State Hub — access via forward tunnel +STATE_HUB_URL=http://127.0.0.1:18000 +STATE_HUB_DOMAIN=railiance +SSH_RELAY_HOST= +SSH_RELAY_USER=worsch +SSH_KEY_PATH=/home/build/.ssh/id_build +REMOTE_PORT=12222 +ENV + chmod 600 /etc/build-agent.env +fi + +# Enable on boot +rc-update add build-agent default + +# ---- 5. SSH hardening ---- +echo "[5/5] Configuring SSH..." + +# Enable and start sshd +rc-update add sshd default +rc-service sshd start 2>/dev/null || true + +# Harden (will take effect after key injection) +sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config +sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config + +echo "" +echo "=== Bootstrap complete ===" +echo "" +echo "Disk usage:" +df -h / | tail -1 +echo "" +echo "GHC location: /home/build/.ghcup/" +su - build -c '. ~/.ghcup/env && ghc --version' +echo "" +echo "Next steps:" +echo " 1. Copy build-agent.py: scp build-agent.py root@:/usr/local/bin/build-agent" +echo " 2. Copy SSH keys: scp id_build root@:/home/build/.ssh/" +echo " 3. Edit env: ssh root@ vi /etc/build-agent.env" +echo " 4. Start agent: ssh root@ rc-service build-agent start" +echo " 5. Disable root login: ssh root@ 'sed -i s/PermitRootLogin.*/PermitRootLogin no/ /etc/ssh/sshd_config && rc-service sshd restart'"