diff --git a/AGENTS.md b/AGENTS.md index 374f361..b252369 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ # Pre-commit checks -- **Test before committing.** Run `make` (full lifecycle: build → deploy → wait → install → verify) before committing infrastructure changes. Do NOT commit untested changes — lint passing does not mean the code works. +- **Test before committing.** Run `just up` (full lifecycle: build → deploy → wait → install → verify) before committing infrastructure changes. Do NOT commit untested changes — lint passing does not mean the code works. - Run the relevant linters/formatters before committing: - **Nix files:** `alejandra` (formats all `.nix` files in the repo) - **Terraform/OpenTofu files:** `tofu fmt -recursive` (formats `.tf` files), `tofu validate` (checks syntax) @@ -97,7 +97,7 @@ - **New security change:** Add to `security-hardening.md` under `## Changes Made` with What/Why/Test/Verify format. - **New useful link:** Add to the relevant doc's `## Useful Links` section with a brief note on what it covers. - **New agent rule:** Add to `AGENTS.md` under the appropriate section. If no section fits, create one. -- **User-facing workflow or feature change:** Update `README.md` if the change affects how a user interacts with the system (new Makefile targets, changed commands, new prerequisites, new features users would want to know about). +- **User-facing workflow or feature change:** Update `README.md` if the change affects how a user interacts with the system (new justfile targets, changed commands, new prerequisites, new features users would want to know about). ## How to add to the docs diff --git a/docs/superpowers/plans/2026-07-15-multi-cluster-implementation.md b/docs/superpowers/plans/2026-07-15-multi-cluster-implementation.md index 723cc14..3b99b56 100644 --- a/docs/superpowers/plans/2026-07-15-multi-cluster-implementation.md +++ b/docs/superpowers/plans/2026-07-15-multi-cluster-implementation.md @@ -4,9 +4,9 @@ **Goal:** Parameterize the Kubernetes cluster by `CLUSTER_INDEX` (subnet offset) and `WORKER_COUNT` (node count) so each worktree/CI can deploy isolated clusters with configurable size. -**Architecture:** Add `cluster_index` and `worker_count` variables to tofu. Derive all IPs and node names dynamically. Pass computed values to NixOS modules via flake.nix. Expose via Makefile env vars. +**Architecture:** Add `cluster_index` and `worker_count` variables to tofu. Derive all IPs and node names dynamically. Pass computed values to NixOS modules via flake.nix. Expose via justfile env vars. -**Tech Stack:** OpenTofu (libvirt), NixOS (Nix), Make +**Tech Stack:** OpenTofu (libvirt), NixOS (Nix), just ## Global Constraints @@ -32,7 +32,7 @@ | `kubernetes/common.nix` | Accept `subnetThirdOctet` + `nodeHosts` args, derive `extraHosts` | | `kubernetes/master.nix` | Accept `subnetThirdOctet` arg, derive `masterAddress` and gateway | | `kubernetes/worker.nix` | Accept `subnetThirdOctet` arg, derive default `nodeIp` and gateway | -| `Makefile` | Expose `CLUSTER_INDEX`, `WORKER_COUNT` env vars | +| `justfile` | Expose `CLUSTER_INDEX`, `WORKER_COUNT` env vars | --- @@ -524,16 +524,16 @@ git commit -m "common.nix, master.nix, worker.nix [kubernetes/]: accept subnetTh --- -### Task 4: Makefile — Expose env vars +### Task 4: justfile — Expose env vars **Files:** -- Modify: `kubernetes/Makefile` +- Modify: `kubernetes/justfile` **Interfaces:** - Consumes: `CLUSTER_INDEX`, `WORKER_COUNT` env vars - Produces: Passes them to tofu -- [ ] **Step 1: Update `kubernetes/Makefile`** +- [ ] **Step 1: Update `kubernetes/justfile`** Change lines 1-5 from: ```makefile @@ -578,8 +578,8 @@ deploy: ## Destroy old VMs and create new ones from images - [ ] **Step 2: Commit** ```bash -git add kubernetes/Makefile -git commit -m "Makefile [kubernetes/]: expose CLUSTER_INDEX and WORKER_COUNT env vars" +git add kubernetes/justfile +git commit -m "justfile [kubernetes/]: expose CLUSTER_INDEX and WORKER_COUNT env vars" ``` --- diff --git a/docs/superpowers/specs/2026-07-15-configurable-cluster-topology-design.md b/docs/superpowers/specs/2026-07-15-configurable-cluster-topology-design.md index 380ad78..5b736a0 100644 --- a/docs/superpowers/specs/2026-07-15-configurable-cluster-topology-design.md +++ b/docs/superpowers/specs/2026-07-15-configurable-cluster-topology-design.md @@ -11,7 +11,7 @@ Currently hardcoded to 1 master + 3 workers in: - `flake.nix` (static `nodeConfigs` map) - `kubernetes/common.nix` (static `extraHosts`) - `kubernetes/worker.nix` (default `nodeIp` for worker-0) -- `Makefile` (no topology control) +- `justfile` (no topology control) ## Solution @@ -37,7 +37,7 @@ Add `master_count` and `worker_count` variables. Derive all node definitions dyn | `kubernetes/common.nix` | Generate `extraHosts` dynamically | | `kubernetes/master.nix` | No change (master is always index 0) | | `kubernetes/worker.nix` | No change (receives `nodeIp` via `extraConfig`) | -| `Makefile` | Expose `MASTER_COUNT`, `WORKER_COUNT` env vars | +| `justfile` | Expose `MASTER_COUNT`, `WORKER_COUNT` env vars | ## Detailed Changes @@ -216,37 +216,35 @@ nodeHosts = lib.concatStringsSep "\n" ( Where `computedNodes` is the fully-resolved map with IPs. -### 6. `Makefile` - -```makefile -CLUSTER_INDEX ?= 0 -MASTER_COUNT ?= 1 -WORKER_COUNT ?= 3 -export CLUSTER_INDEX MASTER_COUNT WORKER_COUNT - -deploy: - cd $(TOFU) && tofu destroy -auto-approve - cd $(TOFU) && tofu apply -auto-approve \ - -var "image_dir=../result" \ - -var "cluster_index=$(CLUSTER_INDEX)" \ - -var "master_count=$(MASTER_COUNT)" \ - -var "worker_count=$(WORKER_COUNT)" +### 6. `justfile` + +```justfile +export CLUSTER_INDEX := env_var_or_default("CLUSTER_INDEX", "0") +export MASTER_COUNT := env_var_or_default("MASTER_COUNT", "1") +export WORKER_COUNT := env_var_or_default("WORKER_COUNT", "3") + +deploy: (down) + cd {{TOFU}} && tofu apply -auto-approve \ + -var "image_dir=../result" \ + -var "cluster_index={{CLUSTER_INDEX}}" \ + -var "master_count={{MASTER_COUNT}}" \ + -var "worker_count={{WORKER_COUNT}}" ``` ## Usage ```bash # Default: 1 master + 3 workers -make up +just up # Lightweight dev cluster -CLUSTER_INDEX=1 WORKER_COUNT=1 make up +CLUSTER_INDEX=1 WORKER_COUNT=1 just up # Large test cluster -CLUSTER_INDEX=2 WORKER_COUNT=5 make up +CLUSTER_INDEX=2 WORKER_COUNT=5 just up # Master-only (no workers, for etcd testing) -WORKER_COUNT=0 make up +WORKER_COUNT=0 just up ``` ## Backward Compatibility diff --git a/docs/superpowers/specs/2026-07-15-multi-cluster-subnet-isolation-design.md b/docs/superpowers/specs/2026-07-15-multi-cluster-subnet-isolation-design.md index 8813daa..a9d9714 100644 --- a/docs/superpowers/specs/2026-07-15-multi-cluster-subnet-isolation-design.md +++ b/docs/superpowers/specs/2026-07-15-multi-cluster-subnet-isolation-design.md @@ -35,7 +35,7 @@ Default is `cluster_index=0` (current behavior, no breaking change). | `kubernetes/common.nix` | Derive `extraHosts` from cluster_index | | `kubernetes/master.nix` | Derive `masterAddress` from cluster_index | | `kubernetes/worker.nix` | Derive `nodeIp` from cluster_index (per-node offset) | -| `Makefile` | Expose `CLUSTER_INDEX` env var | +| `justfile` | Expose `CLUSTER_INDEX` env var | ## Detailed Changes @@ -292,32 +292,32 @@ Accept `subnetThirdOctet` arg, derive `masterAddress`: Already receives `nodeIp` via `extraConfig` from `flake.nix`. No changes needed — the computed IP flows through. -### 8. `Makefile` +### 8. `justfile` Add `CLUSTER_INDEX` support: -```makefile -CLUSTER_INDEX ?= 0 -export CLUSTER_INDEX +```justfile +export CLUSTER_INDEX := env_var_or_default("CLUSTER_INDEX", "0") -deploy: - cd $(TOFU) && tofu destroy -auto-approve - cd $(TOFU) && tofu apply -auto-approve -var "image_dir=../result" -var "cluster_index=$(CLUSTER_INDEX)" +deploy: (down) + cd {{TOFU}} && tofu apply -auto-approve \ + -var "image_dir=../result" \ + -var "cluster_index={{CLUSTER_INDEX}}" ``` ## Usage ```bash # Default cluster (index 0, 192.168.122.0/24) -make up +just up # Parallel cluster for testing -CLUSTER_INDEX=1 make up +CLUSTER_INDEX=1 just up # Worktree-specific -CLUSTER_INDEX=2 make up +CLUSTER_INDEX=2 just up # Destroy specific cluster -CLUSTER_INDEX=1 make down +CLUSTER_INDEX=1 just down ``` ## Backward Compatibility diff --git a/kubernetes/Makefile b/kubernetes/Makefile deleted file mode 100644 index 77e3ac3..0000000 --- a/kubernetes/Makefile +++ /dev/null @@ -1,75 +0,0 @@ -KUBE_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) -TOFU := $(KUBE_DIR)/tofu -ANSIBLE := $(KUBE_DIR)/ansible -SSH_KEY := $(KUBE_DIR)/ssh-key - -# Cluster configuration (override via environment) -CLUSTER_INDEX ?= 0 -WORKER_COUNT ?= 3 -OVMF_CODE_PATH := $(shell nix eval --raw nixpkgs#OVMF.fd) -export CLUSTER_INDEX -export WORKER_COUNT - -# Derived master IP from cluster_index -MASTER_IP := 192.168.$$(shell echo $$(($$((122 + $(CLUSTER_INDEX)))))).10 - -.DEFAULT_GOAL := up - -.PHONY: help build deploy down ssh wait-ssh ansible verify up lint fmt check - -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' - -build: ## Build NixOS VM images - nix build .#images - -deploy: ## Destroy old VMs and create new ones from images - cd $(TOFU) && tofu destroy -auto-approve -var "cluster_index=$(CLUSTER_INDEX)" -var "worker_count=$(WORKER_COUNT)" -var "ovmf_code_path=$(OVMF_CODE_PATH)" - cd $(TOFU) && tofu apply -auto-approve -var "image_dir=../result" -var "cluster_index=$(CLUSTER_INDEX)" -var "worker_count=$(WORKER_COUNT)" -var "ovmf_code_path=$(OVMF_CODE_PATH)" - -down: ## Destroy all VMs - cd $(TOFU) && tofu destroy -auto-approve -var "cluster_index=$(CLUSTER_INDEX)" -var "worker_count=$(WORKER_COUNT)" -var "ovmf_code_path=$(OVMF_CODE_PATH)" - -wait-ssh: ## Wait for SSH on master (checks every 5s, 60 retries) - @echo "Waiting for SSH on $(MASTER_IP)..." - @for i in $$(seq 1 60); do \ - ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentityAgent=none \ - -o ConnectTimeout=5 -i $(SSH_KEY) root@$(MASTER_IP) true 2>/dev/null && \ - echo "SSH ready." && exit 0; \ - echo " attempt $$i/60 — retrying in 5s..."; \ - sleep 5; \ - done; \ - echo "ERROR: SSH not available after 5 minutes." && exit 1 - -ssh: ## SSH into master node - ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentityAgent=none \ - -i $(SSH_KEY) root@$(MASTER_IP) - -ansible: ## Run full Ansible playbook (wait-ssh + Cilium + Rook-Ceph + verify) - cd $(ANSIBLE) && ansible-playbook site.yml - -verify: ## Run only the verify playbook - cd $(ANSIBLE) && ansible-playbook verify.yml - -up: build deploy wait-ssh ansible ## Full lifecycle: build → deploy → wait → install → verify - -lint: ## Run all linters - @echo "=== alejandra ===" - @nix-shell -p alejandra --run "alejandra --check $(KUBE_DIR)/*.nix" - @echo "=== tflint ===" - @nix-shell -p tflint --run "tflint --recursive --chdir $(TOFU)" - @echo "=== ansible-lint ===" - @nix-shell -p ansible-lint --run "ansible-lint $(ANSIBLE)" - @echo "=== yamllint ===" - @nix-shell -p yamllint --run "yamllint -c $(KUBE_DIR)/../.yamllint $(KUBE_DIR)/../" - @echo "All lints passed." - -fmt: ## Format all files - @echo "=== alejandra ===" - @nix-shell -p alejandra --run "alejandra $(KUBE_DIR)/*.nix" - @echo "=== tofu fmt ===" - @cd $(TOFU) && tofu fmt -recursive - @echo "All files formatted." - -check: lint ## Run all linters (alias for lint) diff --git a/kubernetes/README.md b/kubernetes/README.md index 0e7f86e..6823930 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -8,19 +8,20 @@ Cilium CNI + Rook-Ceph storage. - NixOS with flakes enabled - `libvirtd` running (`systemctl status libvirtd`) - OpenTofu 1.12+ +- [just](https://github.com/casey/just) ## Quick Start ```bash cd kubernetes -make up +just up ``` This runs the full lifecycle: build images → deploy VMs → wait for SSH → install Cilium + Rook-Ceph → verify cluster health. -See `docs/resume-state.md` for all Makefile targets and options. +See `docs/resume-state.md` for all justfile targets and options. -## What `make up` Does +## What `just up` Does 1. **Build images** — `nix build .#images` creates QCOW2 images for all 4 nodes 2. **Deploy VMs** — `tofu destroy` + `tofu apply` creates libvirt VMs from images @@ -32,28 +33,28 @@ See `docs/resume-state.md` for all Makefile targets and options. ## SSH into Master ```bash -make ssh +just ssh ``` -> **Note:** Bitwarden SSH agent blocks key auth. The Makefile handles this with `IdentityAgent=none`. +> **Note:** Bitwarden SSH agent blocks key auth. The justfile handles this with `IdentityAgent=none`. ## Verify Cluster ```bash -make verify +just verify ``` ## Clean Up ```bash -make down +just down ``` ## Environment Variables ```bash -CLUSTER_INDEX=1 make up # Different cluster subnet (default: 0) -WORKER_COUNT=5 make up # More workers (default: 3) +CLUSTER_INDEX=1 just up # Different cluster subnet (default: 0) +WORKER_COUNT=5 just up # More workers (default: 3) ``` ## Single Node Testing (QEMU) diff --git a/kubernetes/docs/resume-state.md b/kubernetes/docs/resume-state.md index 303255c..eb9af25 100644 --- a/kubernetes/docs/resume-state.md +++ b/kubernetes/docs/resume-state.md @@ -81,25 +81,24 @@ $TOFU destroy -auto-approve && \ $TOFU apply -auto-approve -var "image_dir=../result" ``` -## Makefile Usage +## Justfile Usage -The Makefile provides a complete lifecycle for managing the cluster. All targets are available via `make `. +The justfile provides a complete lifecycle for managing the cluster. All targets are available via `just `. ### Quick Reference | Target | Description | |--------|-------------| -| `make help` | Show available targets with descriptions | -| `make up` | **Full lifecycle**: build images → deploy VMs → wait for SSH → run Ansible | -| `make build` | Build NixOS VM images only | -| `make deploy` | Destroy old VMs and create new ones from images | -| `make down` | Destroy all VMs | -| `make wait-ssh` | Wait for SSH on master (checks every 5s, 60 retries) | -| `make ssh` | SSH into master node | -| `make ansible` | Run full Ansible playbook (installs Cilium, Rook-Ceph, verifies) | -| `make verify` | Run only the verification playbook | -| `make lint` | Run all linters (alejandra, tflint, ansible-lint, yamllint) | -| `make fmt` | Format all files (alejandra, tofu fmt) | +| `just up` | **Full lifecycle**: build images → deploy VMs → wait for SSH → run Ansible | +| `just build` | Build NixOS VM images only | +| `just deploy` | Destroy old VMs and create new ones from images | +| `just down` | Destroy all VMs | +| `just wait-ssh` | Wait for SSH on master (checks every 5s, 60 retries) | +| `just ssh` | SSH into master node | +| `just ansible` | Run full Ansible playbook (installs Cilium, Rook-Ceph, verifies) | +| `just verify` | Run only the verification playbook | +| `just lint` | Run all linters (alejandra, tflint, ansible-lint, yamllint) | +| `just fmt` | Format all files (alejandra, tofu fmt) | ### Environment Variables @@ -107,54 +106,54 @@ Override cluster configuration via environment variables: ```bash # Deploy a different cluster index (default: 0) -CLUSTER_INDEX=1 make up +CLUSTER_INDEX=1 just up # Change worker count (default: 3) -WORKER_COUNT=5 make up +WORKER_COUNT=5 just up # Combine both -CLUSTER_INDEX=2 WORKER_COUNT=4 make up +CLUSTER_INDEX=2 WORKER_COUNT=4 just up ``` ### Typical Workflows **Initial deployment:** ```bash -make up +just up ``` **Rebuild after NixOS config changes:** ```bash -make up # Full lifecycle rebuild +just up # Full lifecycle rebuild ``` **Just verify cluster health:** ```bash -make verify +just verify ``` **SSH into master for debugging:** ```bash -make ssh +just ssh ``` **Clean up when done:** ```bash -make down +just down ``` **Before committing changes:** ```bash -make lint # Check formatting and linting -make fmt # Auto-fix formatting issues +just lint # Check formatting and linting +just fmt # Auto-fix formatting issues ``` ### Target Details -- **`make up`** — The primary target. Chains: `build` → `deploy` → `wait-ssh` → `ansible` -- **`make build`** — Runs `nix build .#images` to create QCOW2 images -- **`make deploy`** — Runs `tofu destroy` then `tofu apply` with the built images -- **`make ansible`** — Runs the full Ansible playbook which: +- **`just up`** — The primary target. Chains: `build` → `deploy` → `wait-ssh` → `ansible` +- **`just build`** — Runs `nix build .#images` to create QCOW2 images +- **`just deploy`** — Runs `tofu destroy` then `tofu apply` with the built images +- **`just ansible`** — Runs the full Ansible playbook which: 1. Waits for SSH on all nodes 2. Installs Cilium CNI with eBPF masquerade 3. Installs Rook-Ceph storage (operator, CephCluster, CSI, StorageClass) diff --git a/kubernetes/flake.nix b/kubernetes/flake.nix index 3755dfb..fb10d81 100644 --- a/kubernetes/flake.nix +++ b/kubernetes/flake.nix @@ -117,6 +117,7 @@ pkgs.cfssl pkgs.cilium-cli pkgs.helm + pkgs.just pkgs.kubectl pkgs.OVMF.fd pkgs.opentofu diff --git a/kubernetes/justfile b/kubernetes/justfile new file mode 100755 index 0000000..6b23568 --- /dev/null +++ b/kubernetes/justfile @@ -0,0 +1,87 @@ +#!/usr/bin/env just --justfile + +# Cluster configuration (override via environment) +export CLUSTER_INDEX := env_var_or_default("CLUSTER_INDEX", "0") +export WORKER_COUNT := env_var_or_default("WORKER_COUNT", "3") + +OVMF_CODE_PATH := `nix eval --raw nixpkgs#OVMF.fd` +KUBE_DIR := justfile_directory() +TOFU := KUBE_DIR / "tofu" +ANSIBLE := KUBE_DIR / "ansible" +SSH_KEY := KUBE_DIR / "ssh-key" +MASTER_IP := "192.168." + `echo $((122 + {{CLUSTER_INDEX}}))` + ".10" + +# Build NixOS VM images +build: + nix build .#images + +# Destroy old VMs and create new ones from images +deploy: (down) + cd {{TOFU}} && tofu apply -auto-approve \ + -var "image_dir=../result" \ + -var "cluster_index={{CLUSTER_INDEX}}" \ + -var "worker_count={{WORKER_COUNT}}" \ + -var "ovmf_code_path={{OVMF_CODE_PATH}}" + +# Destroy all VMs +down: + cd {{TOFU}} && tofu destroy -auto-approve \ + -var "cluster_index={{CLUSTER_INDEX}}" \ + -var "worker_count={{WORKER_COUNT}}" \ + -var "ovmf_code_path={{OVMF_CODE_PATH}}" + +# Wait for SSH on master (checks every 5s, 60 retries) +wait-ssh: + #!/usr/bin/env bash + echo "Waiting for SSH on {{MASTER_IP}}..." + for i in $(seq 1 60); do + ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentityAgent=none \ + -o ConnectTimeout=5 -i {{SSH_KEY}} root@{{MASTER_IP}} true 2>/dev/null && \ + echo "SSH ready." && exit 0 + echo " attempt $i/60 — retrying in 5s..." + sleep 5 + done + echo "ERROR: SSH not available after 5 minutes." && exit 1 + +# SSH into master node +ssh: + ssh -F /dev/null -o StrictHostKeyChecking=no -o IdentityAgent=none \ + -i {{SSH_KEY}} root@{{MASTER_IP}} + +# Run full Ansible playbook (Cilium + Rook-Ceph + verify) +ansible: + cd {{ANSIBLE}} && ansible-playbook site.yml + +# Run only the verify playbook +verify: + cd {{ANSIBLE}} && ansible-playbook verify.yml + +# Full lifecycle: build → deploy → wait → install → verify +up: build deploy wait-ssh ansible + +# Run all linters +lint: + #!/usr/bin/env bash + set -euo pipefail + echo "=== alejandra ===" + nix-shell -p alejandra --run "alejandra --check {{KUBE_DIR}}/*.nix" + echo "=== tflint ===" + nix-shell -p tflint --run "tflint --recursive --chdir {{TOFU}}" + echo "=== ansible-lint ===" + nix-shell -p ansible-lint --run "ansible-lint {{ANSIBLE}}" + echo "=== yamllint ===" + nix-shell -p yamllint --run "yamllint -c {{KUBE_DIR}}/../.yamllint {{KUBE_DIR}}/../" + echo "All lints passed." + +# Format all files +fmt: + #!/usr/bin/env bash + set -euo pipefail + echo "=== alejandra ===" + nix-shell -p alejandra --run "alejandra {{KUBE_DIR}}/*.nix" + echo "=== tofu fmt ===" + cd {{TOFU}} && tofu fmt -recursive + echo "All files formatted." + +# Run all linters (alias for lint) +check: lint