kubestronaut-sim/images/vm/guest/firstboot.sh
vumas3r f3d0e536b6 Exam simulator for CKA, CKAD and CKS with per-task hosts and a remote desktop
kubestronaut-sim runs each exam task on a host of its own - a kind (kubeadm)
cluster in rootless podman, or a QEMU/KVM Ubuntu VM for the CKS tasks that
need their own kernel (Falco, AppArmor) - and puts a browser desktop in front
of them, the way killer.sh and sailor.sh do.

- ksim, sim/: the engine (sessions, drills, study mode, grading, coverage,
  audit), host and VM lifecycle, the desktop
- images/: exam host images (common, CKS variant with its tools, VM guest)
- cka/ (131 tasks), ckad/ (105), cks/ (110): task banks, curricula with
  study notes, reference solutions and scripted solutions

Every domain of every track has at least 17 pool tasks. `./ksim audit` proves
a task three ways: it seeds cleanly on its own host, scores nothing before
anyone has worked on it, and reaches full marks when its scripted solution is
run on the host as `candidate`. All 346 tasks pass (CKA audited 2026-10-13,
CKAD and CKS 2026-10-12).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
2026-10-13 16:29:33 -06:00

58 lines
2.9 KiB
Bash

#!/bin/bash
# Every boot of a VM exam host (ksim-firstboot.service). The hypervisor hands over two things
# in a small shared directory: the host's name and the simulator's public ssh key. On the first boot the node
# then becomes a single-node kubeadm cluster of its own, under that name - which is why the cluster
# is not baked into the base disk: its node name and certificates would be the same on every host.
exec >> /var/log/ksim-firstboot.log 2>&1
set -x
F=/run/ksim-host
mkdir -p $F
mountpoint -q $F || mount -t 9p -o trans=virtio,version=9p2000.L,ro ksim $F
name=$(tr -d ' \n' < $F/hostname 2>/dev/null)
if [ -n "$name" ] && [ "$(hostname)" != "$name" ]; then
hostnamectl set-hostname "$name"
grep -q "^127.0.1.1 " /etc/hosts && sed -i "s/^127.0.1.1 .*/127.0.1.1 $name/" /etc/hosts || echo "127.0.1.1 $name" >> /etc/hosts
fi
if [ -s $F/authorized_keys ]; then
for h in /root /home/candidate; do
install -d -m700 $h/.ssh && cat $F/authorized_keys > $h/.ssh/authorized_keys && chmod 600 $h/.ssh/authorized_keys
done
chown -R candidate:candidate /home/candidate/.ssh
fi
# the task's files: a directory of the simulator, shared into the VM
mkdir -p /opt/course
mountpoint -q /opt/course || mount -t 9p -o trans=virtio,version=9p2000.L,msize=262144 course /opt/course
[ -f /var/lib/ksim-ready ] && exit 0
kubeadm init --node-name "$(hostname)" --pod-network-cidr=10.244.0.0/16 --kubernetes-version "$(kubeadm version -o short)" \
--apiserver-cert-extra-sans=127.0.0.1,localhost,"$(hostname)" || { echo "kubeadm init failed"; exit 1; }
# Pod network: one node, so a bridge is all it takes - no CNI daemon to pull or wait for.
# (Nothing here enforces NetworkPolicy; tasks that need that run on the container hosts.)
mkdir -p /etc/cni/net.d
cat > /etc/cni/net.d/10-ksim.conflist <<'EOF'
{
"cniVersion": "1.0.0",
"name": "ksim",
"plugins": [
{"type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "hairpinMode": true,
"ipam": {"type": "host-local", "ranges": [[{"subnet": "10.244.0.0/24"}]], "routes": [{"dst": "0.0.0.0/0"}]}},
{"type": "portmap", "capabilities": {"portMappings": true}}
]
}
EOF
export KUBECONFIG=/etc/kubernetes/admin.conf
for i in $(seq 1 60); do kubectl get --raw=/readyz >/dev/null 2>&1 && break; sleep 2; done
kubectl taint nodes --all node-role.kubernetes.io/control-plane- || true
install -d -m700 /root/.kube && install -m600 /etc/kubernetes/admin.conf /root/.kube/config
install -d -m700 -o candidate -g candidate /home/candidate/.kube && install -m600 -o candidate -g candidate /etc/kubernetes/admin.conf /home/candidate/.kube/config
for i in $(seq 1 90); do
[ "$(kubectl get node "$(hostname)" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)" = True ] && break; sleep 2
done
# a task's Pods resolve names from their first second on: the host is ready when cluster DNS is
kubectl -n kube-system rollout status deploy coredns --timeout=180s || true
touch /var/lib/ksim-ready