# How to plug the UI into whatever k8s you generated. THIS IS THE WHOLE THING: # one Pod running the vite app, one Service to reach it. # # Optional by design. The UI complements a rig; it is not part of the end # product, and a rig is complete and useful without it. Apply this only when you # want the listing: # # kubectl apply -n -f rig-ui/k8s.yaml # # A bare Pod, not a Deployment — this is a dev-loop convenience, not a workload # to keep alive. If it dies you re-apply it; nothing depends on it staying up. # # The app and bundle.json arrive as a ConfigMap named `rig-ui`, which # ctrl/manifest.py generates from the folder. Nothing is baked into an image, so # editing bundle.json and re-applying is the whole update cycle. apiVersion: v1 kind: Pod metadata: name: rig-ui labels: app: rig-ui spec: containers: - name: vite image: node:22-alpine workingDir: /app # npm install at start: no image to build and no registry to publish to, # which is the point of a minimal plug-in. It needs egress to a registry — # on a locked-down cluster point npm at the internal one, or bake an image # instead. Nothing else here changes if you do. command: ["sh", "-c"] # A ConfigMap mounts flat (keys cannot contain '/'), so the files are # placed into vite's expected layout here. bundle.json goes to public/ # because that is what vite serves at /bundle.json, which is where the # app fetches it. args: - | mkdir -p /app/src /app/public && cp /src/package.json /src/vite.config.js /src/index.html /app/ && cp /src/main.js /src/style.css /app/src/ && cp /src/bundle.json /app/public/ && npm install --no-audit --no-fund && npm run dev env: # Rendered in the heading so two rigs sharing a cluster stay # distinguishable. Set from the namespace by ctrl/manifest.py. - name: VITE_RIG_NAME value: __RIG_NAME__ ports: - name: http containerPort: 5173 volumeMounts: # /src is read-only from the ConfigMap; the app is copied to a writable # /app because npm install has to create node_modules. - name: rig-ui mountPath: /src - name: app mountPath: /app readinessProbe: httpGet: { path: /, port: 5173 } # npm install decides how long this takes, and it is the slow part. initialDelaySeconds: 15 periodSeconds: 5 failureThreshold: 30 resources: requests: { memory: 128Mi, cpu: 50m } limits: { memory: 512Mi } volumes: - name: rig-ui configMap: name: rig-ui - name: app emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: rig-ui labels: app: rig-ui # No annotations, deliberately — see k8s/app.yaml. The target is EKS but this # stays VPC-agnostic: no subnets, no security groups, no -scheme, no -type. # A bare LoadBalancer is what lets one manifest work on kind and on EKS. spec: type: LoadBalancer selector: app: rig-ui ports: - name: http port: 80 targetPort: 5173 protocol: TCP # Pinned, because a LoadBalancer Service also allocates a NodePort and # this is the only address that works everywhere. # # On WSL the MetalLB address is on a docker bridge INSIDE the Linux VM, # and Windows has no route to it — the page looks broken while the # cluster is perfectly healthy. 30080 is what rig's `hostport` ingress # mode publishes to the host, so this is reachable at # localhost:$HTTP_PORT from a Windows browser with nothing configured. # # Costs nothing elsewhere: MetalLB still assigns an external IP on Linux, # and on EKS the load balancer targets this NodePort anyway. One Service, # no per-environment branch. # # A pinned NodePort is cluster-unique, so two rigs must live in separate # clusters — which is how they are run anyway. nodePort: 30080