sample-rig
A minimal, non-sensitive bundle that proves an installation works and shows what shipped. Copy it, rename it, and you have another rig.
make manifest # generate the artifact — no cluster, no kubectl needed
make up # deploy it into the local cluster
make list # every rig in this cluster, with addresses
make dev # run the UI locally with vite, no cluster at all
make up prints an address. Open it and the page says IT WORKS, then lists
the tools and rigs in the bundle.
What it is for
Three jobs, in the order you hit them:
- Prove the install. kind is there, a cluster exists, MetalLB hands out an
address, a
type: LoadBalancerService actually resolves, and a pod serves. If all of that works, the environment is sound. - Say what shipped. The page renders
bundle.json— standalone tools and rigs, flat, with none of soleprint's internal hierarchy. Editing that file is the only step needed to change the listing. - Stand in for the real thing. Nothing here is sensitive. The real architecture connects separately, against a setup already known to work.
The UI is a complement, not the product
rig-ui/ is just a vite app. It complements a rig; a rig is complete and useful
without it, and nothing depends on it being there. It is deliberately not
generated by kind or tilt — you copy the folder into a rig after that rig is
pulled, and apply one manifest:
kubectl apply -n <namespace> -f rig-ui/k8s.yaml
That file is the whole integration: one Pod running npm run dev on
node:22-alpine, one Service. A bare Pod rather than a Deployment because this
is a dev-loop convenience, not a workload to keep alive.
The app and bundle.json arrive as a ConfigMap, so nothing is baked into an
image and editing the bundle is the entire update cycle. The container runs
npm install at start, which needs egress to a registry — on a locked-down
cluster point npm at the internal one, or bake an image instead. Nothing else
changes if you do.
One artifact, two destinations
ctrl/manifest.py emits generated/<slug>.yaml — namespace, the app and
bundle embedded in a ConfigMap, Pod, Service. It is self-contained and applies
unmodified anywhere:
kubectl apply -f generated/sample-rig.yaml # local kind, or an external cluster
make up applies that same file. There is no separate local path, so what
works here cannot quietly differ from what is applied elsewhere.
This is what type: LoadBalancer buys. MetalLB answers it on kind; the AWS load
balancer controller answers it on EKS. NodePort would not survive the trip — it
is a single cluster-wide port range, so two rigs would have to negotiate numbers.
VPC-agnostic on purpose. The target is EKS, but the Service carries no
annotations — no aws-load-balancer-subnets, no security groups, no -scheme,
no -type: nlb. Each of those encodes a specific network layout, and one of them
appearing here would pin the artifact to the account and VPC it was written
against, which is precisely what stops it also working on kind. Subnet discovery
is the cluster's business: EKS resolves it from the tags its own subnets carry.
That leaves one thing genuinely environment-specific — internal versus
internet-facing. A bare LoadBalancer provisions internet-facing, which a
regulated account will usually refuse, and should. That belongs in a
per-environment overlay applied on top, never inlined into this artifact.
MetalLB only — no ingress-nginx. Its controller supports a narrow window of
Kubernetes versions, so depending on it constrains which k8s a rig can be built
with. That undercuts running trailing-edge control planes to model a legacy
estate, which is the reason versions.env pins v1_33..v1_36. MetalLB carries no
such constraint, so reachability costs nothing in version coverage.
Several rigs, one cluster
Identity follows the folder name, the same rule rig uses for cluster identity. The namespace is the folder; resource names are generic, and names only have to be unique within a namespace.
cp -r sample-rig corporate-rig
cd corporate-rig && make up # its own namespace, its own address
No edits, no collisions, both in the same local cluster. make list shows them
together. make down removes only this one — siblings, MetalLB and the cluster
are left alone.
Client rigs are gitignored (*-rig/, with sample-rig/ the deliberate
exception): a rig's k8s files spell out a real architecture, and that is exactly
what must not land in this repo.
Staging workstations
ctrl/manifest.py is stdlib-only on purpose: it runs on a bare machine before
anything is installed. The toolchain itself is rig's job — make deps installs
the pinned kind and tilt binaries, which is what makes a staging AWS workspace
reachable from the same commands as a laptop.
Layout
sample-rig/
├── Makefile # thin — one target per ctrl/ script
├── bundle.json # what shipped; the UI renders THIS
├── rig-ui/ # the vite app — optional, copied into a rig to enable it
│ ├── k8s.yaml # how to plug it in: one Pod, one Service
│ ├── index.html
│ ├── package.json
│ ├── vite.config.js
│ └── src/{main.js,style.css}
├── ctrl/
│ ├── manifest.py # emits the artifact
│ └── bundle.sh # generate / deploy / inspect
└── generated/ # the artifact — committed, this is the deliverable
Editing bundle.json or anything in rig-ui/ means re-running make manifest.
The ConfigMap carries a checksum of everything embedded, so a stale deployment is
visible rather than silent.
Not built, but not foreclosed
Everything derives from bundle.json plus a target namespace. A Pulumi or
Terraform emitter would sit beside ctrl/manifest.py consuming the same inputs;
nothing above it assumes the artifact is YAML.
Licence terms for the compiled UI component belong in the soleprint-generated bundle, not here — this sample carries no proprietary component.