# 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. ```bash 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: 1. **Prove the install.** kind is there, a cluster exists, MetalLB hands out an address, a `type: LoadBalancer` Service actually resolves, and a pod serves. If all of that works, the environment is sound. 2. **Say what shipped.** The page renders [`bundle.json`](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. 3. **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: ```bash kubectl apply -n -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/.yaml` — namespace, the app and bundle embedded in a ConfigMap, Pod, Service. It is self-contained and applies unmodified anywhere: ```bash 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. ```bash 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.