# planck — 20-node Raspberry Pi 4 home lab 20× Raspberry Pi 4 (8GB, ARM64), each booting a 240GB NVMe-in-USB stick. Fresh Raspberry Pi OS Lite (Debian 13) everywhere, running a k3s Kubernetes cluster: servers planck002/003/004 (embedded etcd), workers 001 + 005–020. **Home page: https://carr.pub** (served by this cluster). ## The map | Path | What it is | |------|-----------| | `docs/PLAN.md` | Full history: what was built, every bug and its fix, postmortems. READ FIRST | | `docs/IPS.md` | Node ↔ IP ↔ MAC table (static IPs, pinned on nodes) | | `docs/INVENTORY.md` | Hardware inventory per node | | `scripts/flash-one.sh` | Reimage a node's NVMe stick (run on laptop via pkexec) | | `scripts/k3s-join.sh` | Join servers/agents to the cluster (needs K3S_TOKEN) | | `scripts/loadtest.sh` | Spawn a k6 load swarm across N nodes | | `scripts/inventory.sh` | Regenerate hardware inventory | | `manifests/monitoring/` | Prometheus, node-exporter, Grafana, DDNS CronJob | | `manifests/loadtest/` | k6 rig + practice target | | `manifests/web/` | The carr.pub site + Ingress | | `manifests/cluster-issuer.yaml` | Let's Encrypt issuer | | `configs/` | Node password (gitignored), installer internals, retired netboot servers | ## Daily operations - kubectl on the laptop: `~/.local/bin/kubectl` (kubeconfig `~/.kube/config`) - SSH any node: `ssh adamcarr@planck0NN.local` (passwordless sudo) - Grafana: `kubectl -n monitoring port-forward svc/grafana 3000:3000` - Load test: `scripts/loadtest.sh [pods] [vus-per-pod] [duration]` - New public site: Ingress in `web` namespace on `.carr.pub` with the `cert-manager.io/cluster-issuer: letsencrypt-prod` annotation + DNSimple record Agent documentation lives in `~/.agents/skills/planck-cluster/SKILL.md` (loaded automatically by opencode sessions). ## Safety rules - Never reboot more than ONE of planck002/003/004 at once (etcd quorum) - Images must be linux/arm64 multi-arch - The cluster room switch shares a PSU with nodes 011–015