# rig A runnable local model of a large, regulated estate — legacy and new side by side. Its job is onboarding and exploration, not a production replica: most services are deliberately mocked, because what has to be faithful is the topology, not the workloads. ## Prerequisite **Docker.** Nothing else — no curl, no jq, no python, no apt repositories. ### Starting from plain Windows Everything here is bash and runs *inside* a Linux shell, so on a Windows machine that means WSL. Nothing in rig installs WSL, and nothing will: `wsl --install` enables Windows features and requires a reboot, which is not something a script should do to a machine on your behalf — and there is no tested undo for it. From an elevated PowerShell or Command Prompt, once: ```powershell wsl --install ``` Then reboot and open the Linux shell it installed. **If you cloned this on the Windows side, copy it into WSL before carrying on.** WSL can reach the Windows drives at `/mnt/c`, and working from there mostly functions — slowly — but file watching does not: that filesystem raises no inotify events, so anything watching for edits silently stops seeing them. ```bash cp -r /mnt/c/Users//rig ~/rig cd ~/rig ``` `make deps` reports it if you are running from `/mnt/...`. Then carry on below. If it fails, the usual causes give unhelpful messages: | symptom | cause | | --- | --- | | "the virtual machine could not be started" | virtualization disabled in BIOS/UEFI | | the command is not recognised | Windows build too old — needs 2004 or later | | the install starts, then nothing works | a reboot is still pending | Running the scripts from **Git Bash, MSYS or Cygwin does not work** — those look close enough to a Linux shell to get started and then fail without `/proc` or a docker socket. `ctrl/deps.sh` detects that and says so rather than letting you find out the slow way. ## Read the docs first ```bash make docs # serves on localhost, prints the URL ``` They run before anything is installed, which matters because they are the instructions for everything else. No cluster and no toolchain required. ## Then ```bash make check # report host and config problems; changes nothing make deps # install the toolchain (add `core` on a managed machine) make cluster up # cluster + registry + the profile's addons ``` That is the whole setup. `make cluster up` also starts this environment's local registry and wires it into the node, so an image built locally is pullable by the cluster without going near docker.io. `make check` shows its port, among everything else: ```bash make check # ... registry endpoint localhost: docker build -t localhost:/app:1 . docker push localhost:/app:1 kubectl --context kind-$(basename $PWD) run app --image=localhost:/app:1 ``` The port block is derived from the directory name, so two copies of rig never collide — nothing to configure. `make check` lists it; `bash ctrl/ports.sh persist` pins it into `ctrl/.env` if you want it fixed: ```bash make cluster list # every cluster on this machine, with memory make cluster free # stop the others if memory is tight make cluster down # remove this cluster and its registry ``` **The verbs are yours to change.** `cluster` is the script — `ctrl/cluster.sh` — and every spelling above is a `Makefile` target that calls it. `make kind-up` is an alias for `make cluster up`, kept because the other projects on this machine answer to that spelling and muscle memory spans repos rather than stopping at one. Nothing outside the `Makefile` reads these names, so rename them, drop the ones you never type, or add whatever your own projects already say: each alias is two lines at the bottom of the file, calling the same script the canonical target does. **`make tilt` works on a fresh copy, unedited.** rig ships `ctrl/Tiltfile`, and `k8s/base` already boots, so the dev loop comes up with the two examples running and nothing to configure first. It hardcodes nothing. It asks `ctrl/ports.sh active` for this environment's cluster name, kube context, ports and manifest directory — the same values every other rig script resolves through `ctrl/lib/config.sh` — so a copied and renamed rig deploys into its own cluster with no edits. Every other project here writes its slug into the Tiltfile five or six times by hand, which is exactly the collision `kind-config.yaml.tpl` exists to avoid. What it deploys is `MANIFESTS_DIR`, defaulting to rig's own `ctrl/k8s/overlays/dev`. Point that at an overlay versioned elsewhere and rig stops owning the manifests. Replace the examples, then add your images and resources in the two marked sections. The catalogue below them holds the blocks that recur across every project here — `docker_build`, resource ordering, gateway reload, port-forwards — with the parts that are easy to get wrong already commented. `make help` lists every target. On a machine where Docker really is the only thing installed, `make deps` has nothing to download with — see [BOOTSTRAP.md](BOOTSTRAP.md), which runs the toolchain through the installer container and carries on to scaffolding and running a new project. ## One directory is one environment Copy this directory, rename it, run it. Cluster name, kubectl context, image tags and the host port block all derive from the directory name, so copies never collide and neither one's teardown can touch the other. A copy of this directory is a **sibling** of it, named after the environment it models (`acme-rig`). That is why the ignore rules for copies sit in the *parent* repo's `.gitignore` rather than here: a rule in this directory cannot see a directory beside it. ## Profiles **rig needs no profile.** With none named it runs on built-in defaults: one node, no addons, a local registry, the newest Kubernetes version it pins. A profile is an optional overlay — a file in `ctrl/env.d/`, named by `PROFILE` — for when you want different addons or registry. rig ships **examples**, not active profiles, because each one is a use case rather than something every rig needs. Copy one to use it: | example | what it changes | | --- | --- | | `client.env.example` | images through a mirror of a corporate registry | | `offline.env.example` | air-gapped: everything from a preloaded local registry | | `data.env.example` | databases and a scheduler: postgres, redis, airflow | ```bash cp ctrl/env.d/data.env.example ctrl/env.d/data.env PROFILE=data make cluster up # installs the profile's addons too PROFILE=data make check # what the profile wants, and what exists ``` The **cluster itself** is one file, `ctrl/k8s/kind-config.yaml.tpl` (one node). To change it — more nodes, other port mappings — edit it and `make cluster reset`. The node count is read back out of it, so there is nothing to drift. The layout under `ctrl/k8s/` is the same as every other project here — a kind config, a kustomize `base/`, an `overlays/dev/` — see [`ctrl/k8s/README.md`](ctrl/k8s/README.md). ## Addons Each addon is its own idempotent script in `ctrl/addons/`, and a profile names the ones it wants in `ADDONS`. Adding one is adding a file — there is no dispatcher to edit. **There is no ingress controller, deliberately.** They pin a narrow window of Kubernetes versions, so depending on one would constrain which k8s a rig can be built with — and running a trailing-edge control plane to model a legacy estate is the whole point. Services are reached through MetalLB and `type: LoadBalancer`, which carries no such constraint and is also what a real cluster does. | Addon | Does | | --- | --- | | `metallb` | gives `type: LoadBalancer` an address it can actually reach | | `cert-manager` | a local CA, so TLS works offline | | `metrics-server` | makes `kubectl top` work on kind | | `postgres` | database, in the `data` namespace | | `redis` | cache and broker | | `airflow` | scheduled pipelines; needs postgres and redis | The last three are **cabinets**: a public service dropped in as-is, the upstream image unmodified, reachable at a known address. A cabinet is declared once and installs on either target — a `service.yml` composes it for a laptop, and these install the same one here. The names match on purpose: each cabinet carries a `rig_addon` field pointing at `ctrl/addons/.sh`. Plain manifests rather than helm charts, like every other addon: a chart repo is a network dependency, and the `offline` profile exists precisely so there is a path with none. Images are pinned in `ctrl/versions.env` and can be preloaded. Passwords are generated on first install and kept across re-runs, so re-running an addon never rotates a credential out from under something already connected: ```bash kubectl -n data get secret postgres -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d kubectl -n data port-forward svc/airflow 8080:8080 ```