# spindle Our own [Tangled](https://tangled.org) spindle: the CI runner for the repos under `demi.dev`, starting with [`demi.dev/pneumo`](https://tangled.org/demi.dev/pneumo). We run it because Tangled's hosted spindle denies every nixery workflow: the engine requests 6 GiB per job, and the hosted per-repo quota is 4 GiB. The decision and the rejected options are in [ADR 0001](docs/adr/0001-architecture.md). ## Architecture ``` knot (tangled.org) ──events──▶ mill (Railway, spindle.demi.dev) ▲ wss://spindle.demi.dev/mill │ (the executor dials out) executor (NixOS VM on the home server, nixery on Docker) mill and executor share one Railway bucket as the artifact store ``` | Piece | Where | State | | -- | -- | -- | | Mill (`SPINDLE_ROLE=mill`) | Railway project `spindle`, built from `mill/Dockerfile` | Volume `/data` (SQLite, repos, logs). Identity `did:web:spindle.demi.dev`, from the signing key in the Railway variable `SPINDLE_SIGNING_KEY_PEM` | | Executor (`SPINDLE_ROLE=executor`) | VM `spindle-exec-1` on the owner's home server, built from `executor/flake.nix` | Executor token and bucket credentials in `/var/lib/secrets/` on the VM | | Artifact store | Railway bucket in project `spindle` | Workflow artifacts and logs | Tangled `v1.20.0` (`2096d06d`) is pinned in `mill/Dockerfile` and `executor/flake.nix`; they are always bumped together. ## Layout | Path | What | | -- | -- | | `mill/` | The mill's container image | | `executor/` | The executor VM's NixOS configuration | | `executor/bootstrap/` | The minimal NixOS image the VM was first created from | | `docs/adr/` | Decisions | | [`docs/runbook.md`](docs/runbook.md) | Operating the spindle: upgrades, token rotation, backups and outages | ## Checks Needs [mise](https://mise.jdx.dev) and a running Docker. Nix runs in a pinned `nixos/nix` container, so you don't need it on the host. ```sh mise install mise run check ``` Agents: read [`CLAUDE.md`](CLAUDE.md) first.