Kieran's opinionated (and probably slightly dumb) nix config

feat: add machine based service list

dunkirk.sh 650c45ee d543b8c9

verified
+165 -24
+4
docs/src/deployment.md
··· 61 61 3. Add a deploy workflow to the app repo 62 62 63 63 See `modules/nixos/services/cachet.nix` for a minimal example. 64 + 65 + ## Machine health checks 66 + 67 + Machines with Tailscale enabled automatically expose their hostname for reachability checks in the services manifest via `atelier.machine.tailscaleHost`. This defaults to `networking.hostName` when `services.tailscale.enable` is true.
+36 -4
docs/src/services/README.md
··· 1 1 # Services 2 2 3 - All services run on **terebithia** (Oracle Cloud aarch64) behind Caddy with Cloudflare DNS TLS. 3 + Services are grouped by machine in the services manifest. Machines with Tailscale enabled automatically expose their hostname for reachability checks via `atelier.machine.tailscaleHost`. 4 + 5 + ## Machines 4 6 5 - ## mkService-based 7 + | Machine | Platform | Tailscale | 8 + |---------|----------|-----------| 9 + | terebithia | Oracle Cloud aarch64 | `terebithia` | 10 + | moonlark | — | — | 11 + | prattle | — | — | 12 + 13 + ## terebithia 14 + 15 + All services run behind Caddy with Cloudflare DNS TLS. 16 + 17 + ### mkService-based 6 18 7 19 | Service | Domain | Port | Runtime | Description | 8 20 |---------|--------|------|---------|-------------| ··· 15 27 | traverse | traverse.dunkirk.sh | 4173 | bun | Code walkthrough diagram server | 16 28 | cedarlogic | cedarlogic.dunkirk.sh | 3100 | custom | Circuit simulator | 17 29 18 - ## Multi-instance 30 + ### Multi-instance 19 31 20 32 | Service | Domain | Port | Description | 21 33 |---------|--------|------|-------------| 22 34 | emojibot-hackclub | hc.emojibot.dunkirk.sh | 3002 | Emojibot for Hack Club | 23 35 | emojibot-df1317 | df.emojibot.dunkirk.sh | 3005 | Emojibot for df1317 | 24 36 25 - ## Custom / external 37 + ### Custom / external 26 38 27 39 | Service | Domain | Description | 28 40 |---------|--------|-------------| ··· 32 44 | spindle | spindle.dunkirk.sh | Tangled CI | 33 45 | battleship-arena | battleship.dunkirk.sh | Battleship game server | 34 46 | n8n | n8n.dunkirk.sh | Workflow automation | 47 + 48 + ## Services manifest 49 + 50 + The manifest is now grouped by machine. Evaluate with: 51 + 52 + ```sh 53 + nix eval --json .#services-manifest 54 + ``` 55 + 56 + Output shape: 57 + 58 + ```json 59 + { 60 + "terebithia": { 61 + "hostname": "terebithia", 62 + "tailscale_host": "terebithia", 63 + "services": [{ "name": "cachet", "health_url": "https://cachet.dunkirk.sh/health", ... }] 64 + } 65 + } 66 + ``` 35 67 36 68 ## Architecture 37 69
+2 -2
docs/src/services/cedarlogic.md
··· 25 25 26 26 ## Build step 27 27 28 - Unlike other services, cedarlogic runs a build during deploy: 28 + On initial scaffold, cedarlogic installs deps and builds: 29 29 30 30 ``` 31 31 bun install → parse-gates → bun run build (Vite) 32 32 ``` 33 33 34 - The build has a 120s timeout to accommodate Vite compilation. 34 + Subsequent deploys handle their own build via the deploy workflow. The build has a 120s timeout to accommodate Vite compilation.
+2
docs/src/services/emojibot.md
··· 20 20 channel = "C02T3CU03T3"; 21 21 repository = "https://github.com/taciturnaxolotl/emojibot"; 22 22 secretsFile = config.age.secrets."emojibot/hackclub".path; 23 + healthUrl = "https://hc.emojibot.dunkirk.sh/health"; 23 24 }; 24 25 }; 25 26 ``` ··· 33 34 | `repository` | string | `"https://github.com/taciturnaxolotl/emojibot"` | Git repo URL | 34 35 | `workspace` | string or null | `null` | Slack workspace name (for identification) | 35 36 | `channel` | string or null | `null` | Slack channel ID | 37 + | `healthUrl` | string or null | `null` | Health check URL for monitoring | 36 38 37 39 ## Current instances 38 40
+8 -13
flake.nix
··· 243 243 ]; 244 244 245 245 }; 246 - "john" = home-manager.lib.homeManagerConfiguration { 247 - pkgs = nixpkgs.legacyPackages.x86_64-linux; 248 - extraSpecialArgs = { 249 - inherit inputs outputs; 250 - nixpkgs-unstable = nixpkgs-unstable; 251 - system = "x86_64-linux"; 252 - }; 253 - modules = [ 254 - ./machines/john 255 - unstable-overlays 256 - ]; 257 - }; 258 246 }; 259 247 260 248 # Darwin configurations ··· 274 262 # Service manifest for infra dashboard 275 263 # Evaluate with: nix eval --json .#services-manifest 276 264 services-manifest = import ./lib/services-manifest.nix { 277 - config = self.nixosConfigurations.terebithia.config; 265 + configSets = [ 266 + self.nixosConfigurations 267 + self.darwinConfigurations 268 + self.homeConfigurations 269 + ]; 270 + extraMachines = { 271 + everseen = { type = "client"; tailscaleHost = "everseen"; }; 272 + }; 278 273 lib = nixpkgs.lib; 279 274 }; 280 275
+13 -4
lib/services-manifest.nix
··· 1 - # Generate a JSON-serialisable manifest of all atelier services. 1 + # Generate a JSON-serialisable manifest of all machines and their services. 2 2 # 3 3 # Called from flake.nix: 4 4 # services-manifest = import ./lib/services-manifest.nix { 5 - # config = self.nixosConfigurations.terebithia.config; 5 + # configSets = [ ... ]; 6 + # extraMachines = { everseen = { type = "client"; tailscaleHost = "everseen"; }; }; 6 7 # inherit lib; 7 8 # }; 8 9 # 9 10 # Evaluate with: 10 11 # nix eval --json .#services-manifest 11 12 12 - { config, lib }: 13 + { configSets, extraMachines ? {}, lib }: 13 14 14 15 let 15 16 services = import ./services.nix { inherit lib; }; 17 + 18 + # Convert simple extraMachines entries into manifest shape 19 + extras = lib.mapAttrs (name: cfg: { 20 + hostname = cfg.hostname or name; 21 + type = cfg.type or "server"; 22 + tailscale_host = cfg.tailscaleHost or null; 23 + services = []; 24 + }) extraMachines; 16 25 in 17 - services.mkManifest config 26 + (services.mkMachinesManifest configSets) // extras
+46 -1
lib/services.nix
··· 70 70 }; 71 71 72 72 /** 73 - Build the full services manifest from an evaluated NixOS config. 73 + Build a services manifest from an evaluated NixOS config. 74 74 75 75 Discovers all enabled mkService-based services plus emojibot 76 76 instances. Returns a sorted list of service entries suitable ··· 139 139 serviceList = (lib.mapAttrsToList mkEntry standardServices) ++ emojibotInstances; 140 140 in 141 141 lib.sort (a: b: a.name < b.name) serviceList; 142 + 143 + /** 144 + Build a manifest of all machines and their services. 145 + 146 + Takes one or more attrsets of system configurations (NixOS, Darwin, 147 + or home-manager) and returns an attrset keyed by machine name. 148 + Only machines with `atelier.machine.enable = true` are included. 149 + 150 + # Arguments 151 + 152 + - `configSets` — list of attrsets of system configurations 153 + 154 + # Type 155 + 156 + ``` 157 + [ AttrSet ] -> AttrSet 158 + ``` 159 + 160 + # Example 161 + 162 + ```nix 163 + mkMachinesManifest [ self.nixosConfigurations self.darwinConfigurations ] 164 + => { terebithia = { hostname = "terebithia"; services = [ ... ]; }; } 165 + ``` 166 + */ 167 + mkMachinesManifest = configSets: 168 + let 169 + self = import ./services.nix { inherit lib; }; 170 + merged = lib.foldl (acc: cs: acc // cs) {} configSets; 171 + enabled = lib.filterAttrs (_: sys: 172 + sys.config.atelier.machine.enable or false 173 + ) merged; 174 + mkMachineEntry = name: sys: 175 + let 176 + config = sys.config; 177 + hasAtelierServices = config ? atelier && config.atelier ? services; 178 + services = if hasAtelierServices then self.mkManifest config else []; 179 + in { 180 + hostname = config.networking.hostName or name; 181 + type = config.atelier.machine.type or "server"; 182 + tailscale_host = config.atelier.machine.tailscaleHost or null; 183 + services = services; 184 + }; 185 + in 186 + lib.mapAttrs mkMachineEntry enabled; 142 187 }
+7
machines/atalanta/default.nix
··· 6 6 { 7 7 imports = [ 8 8 ./home-manager.nix 9 + ../../modules/shared/machine.nix 9 10 ]; 10 11 11 12 # Set host platform for Apple Silicon ··· 38 39 39 40 # Set hostname 40 41 networking.hostName = "atalanta"; 42 + 43 + atelier.machine = { 44 + enable = true; 45 + type = "client"; 46 + tailscaleHost = "atalanta"; 47 + }; 41 48 42 49 # Define user 43 50 users.users.kierank = {
+5
machines/ember/default.nix
··· 33 33 }; 34 34 35 35 atelier = { 36 + machine = { 37 + enable = true; 38 + type = "server"; 39 + tailscaleHost = "ember"; 40 + }; 36 41 shell.enable = true; 37 42 apps = { 38 43 helix.enable = true;
+1
machines/moonlark/default.nix
··· 272 272 }; 273 273 274 274 atelier = { 275 + machine.enable = false; 275 276 authentication.enable = true; 276 277 apps.tuigreet = { 277 278 enable = true;
+1
machines/prattle/default.nix
··· 85 85 }; 86 86 87 87 atelier = { 88 + machine.enable = false; 88 89 authentication.enable = true; 89 90 }; 90 91
+5
modules/home/system/machine.nix
··· 1 + { ... }: 2 + 3 + { 4 + imports = [ ../../shared/machine.nix ]; 5 + }
+12
modules/nixos/system/machine.nix
··· 1 + { lib, config, ... }: 2 + 3 + { 4 + imports = [ ../../shared/machine.nix ]; 5 + 6 + config.atelier.machine = { 7 + enable = lib.mkDefault (config.services.tailscale.enable or false); 8 + tailscaleHost = lib.mkDefault ( 9 + if config.services.tailscale.enable or false then config.networking.hostName else null 10 + ); 11 + }; 12 + }
+23
modules/shared/machine.nix
··· 1 + { lib, ... }: 2 + 3 + { 4 + options.atelier.machine = { 5 + enable = lib.mkOption { 6 + type = lib.types.bool; 7 + default = false; 8 + description = "Include this machine in the services manifest."; 9 + }; 10 + 11 + type = lib.mkOption { 12 + type = lib.types.enum [ "server" "client" ]; 13 + default = "server"; 14 + description = "Machine type — server or client"; 15 + }; 16 + 17 + tailscaleHost = lib.mkOption { 18 + type = lib.types.nullOr lib.types.str; 19 + default = null; 20 + description = "Tailscale hostname for reachability checks"; 21 + }; 22 + }; 23 + }