Files
the-custodian/infra/build-machines/haskell/scripts/bootstrap-alpine.sh
2026-04-25 17:08:53 +02:00

158 lines
4.6 KiB
Bash
Executable File

#!/bin/sh
# bootstrap-alpine.sh — One-shot setup for Haskell build machine on Alpine Linux
#
# Usage (from workstation):
# scp bootstrap-alpine.sh root@<vm-ip>:/tmp/
# ssh root@<vm-ip> 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@<vm>:/usr/local/bin/build-agent"
echo " 2. Copy SSH keys: scp id_build root@<vm>:/home/build/.ssh/"
echo " 3. Edit env: ssh root@<vm> vi /etc/build-agent.env"
echo " 4. Start agent: ssh root@<vm> rc-service build-agent start"
echo " 5. Disable root login: ssh root@<vm> 'sed -i s/PermitRootLogin.*/PermitRootLogin no/ /etc/ssh/sshd_config && rc-service sshd restart'"