Tangled infrastructure definitions in Nix

Compare changes

Choose any two refs to compare.

+1
common/ssh.nix
··· 1 1 { 2 2 sshKeys = [ 3 3 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICf5rx2r0w98r5lOpxr+/hScABDnk1UfgTH8T2WzeNp4 icy@kvothe" 4 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICJPYX06+qKr9IHWfkgCtHbExoBOOwS/+iAWbog9bAdk icy@wyndle" 4 5 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMj1Dn9YuFo2BNr993ymBa6nzyyIKAURIqMbUtfI8+4X op@mantis" 5 6 ]; 6 7 }
+61 -30
flake.lock
··· 1 1 { 2 2 "nodes": { 3 + "actor-typeahead-src": { 4 + "flake": false, 5 + "locked": { 6 + "lastModified": 1762835797, 7 + "narHash": "sha256-heizoWUKDdar6ymfZTnj3ytcEv/L4d4fzSmtr0HlXsQ=", 8 + "ref": "refs/heads/main", 9 + "rev": "677fe7f743050a4e7f09d4a6f87bbf1325a06f6b", 10 + "revCount": 6, 11 + "type": "git", 12 + "url": "https://tangled.org/@jakelazaroff.com/actor-typeahead" 13 + }, 14 + "original": { 15 + "type": "git", 16 + "url": "https://tangled.org/@jakelazaroff.com/actor-typeahead" 17 + } 18 + }, 3 19 "colmena": { 4 20 "inputs": { 5 21 "flake-compat": "flake-compat", ··· 29 45 ] 30 46 }, 31 47 "locked": { 32 - "lastModified": 1751854533, 33 - "narHash": "sha256-U/OQFplExOR1jazZY4KkaQkJqOl59xlh21HP9mI79Vc=", 48 + "lastModified": 1766150702, 49 + "narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", 34 50 "owner": "nix-community", 35 51 "repo": "disko", 36 - "rev": "16b74a1e304197248a1bc663280f2548dbfcae3c", 52 + "rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", 37 53 "type": "github" 38 54 }, 39 55 "original": { ··· 58 74 "type": "github" 59 75 } 60 76 }, 77 + "flake-compat_2": { 78 + "flake": false, 79 + "locked": { 80 + "lastModified": 1751685974, 81 + "narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=", 82 + "rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1", 83 + "type": "tarball", 84 + "url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1" 85 + }, 86 + "original": { 87 + "type": "tarball", 88 + "url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz" 89 + } 90 + }, 61 91 "flake-utils": { 62 92 "locked": { 63 93 "lastModified": 1659877975, ··· 78 108 "systems": "systems" 79 109 }, 80 110 "locked": { 81 - "lastModified": 1694529238, 82 - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 111 + "lastModified": 1731533236, 112 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 83 113 "owner": "numtide", 84 114 "repo": "flake-utils", 85 - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 115 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 86 116 "type": "github" 87 117 }, 88 118 "original": { ··· 100 130 ] 101 131 }, 102 132 "locked": { 103 - "lastModified": 1751702058, 104 - "narHash": "sha256-/GTdqFzFw/Y9DSNAfzvzyCMlKjUyRKMPO+apIuaTU4A=", 133 + "lastModified": 1763982521, 134 + "narHash": "sha256-ur4QIAHwgFc0vXiaxn5No/FuZicxBr2p0gmT54xZkUQ=", 105 135 "owner": "nix-community", 106 136 "repo": "gomod2nix", 107 - "rev": "664ad7a2df4623037e315e4094346bff5c44e9ee", 137 + "rev": "02e63a239d6eabd595db56852535992c898eba72", 108 138 "type": "github" 109 139 }, 110 140 "original": { ··· 153 183 "indigo": { 154 184 "flake": false, 155 185 "locked": { 156 - "lastModified": 1745333930, 157 - "narHash": "sha256-83fIHqDE+dfnZ88HaNuwfKFO+R0RKAM1WxMfNh/Matk=", 186 + "lastModified": 1753693716, 187 + "narHash": "sha256-DMIKnCJRODQXEHUxA+7mLzRALmnZhkkbHlFT2rCQYrE=", 158 188 "owner": "oppiliappan", 159 189 "repo": "indigo", 160 - "rev": "e4e59280737b8676611fc077a228d47b3e8e9491", 190 + "rev": "5f170569da9360f57add450a278d73538092d8ca", 161 191 "type": "github" 162 192 }, 163 193 "original": { ··· 195 225 "nixery-flake": { 196 226 "flake": false, 197 227 "locked": { 198 - "lastModified": 1745149613, 199 - "narHash": "sha256-rcSnsnSWA0IUjmbG2iSpvVB0702tcR3zIyU3iFJBo0g=", 228 + "lastModified": 1762501370, 229 + "narHash": "sha256-WO2NvvFB3KkFfChE5F6ghog7mvBAVKpMsQMqwadZT4k=", 200 230 "owner": "tazjin", 201 231 "repo": "nixery", 202 - "rev": "c6d4692b1b6eb105c9abce0411d2ef4b8708a6e1", 232 + "rev": "be8a4005de3f27f95e677e7b61abef387d4a840d", 203 233 "type": "github" 204 234 }, 205 235 "original": { ··· 226 256 }, 227 257 "nixpkgs_2": { 228 258 "locked": { 229 - "lastModified": 1751792365, 230 - "narHash": "sha256-J1kI6oAj25IG4EdVlg2hQz8NZTBNYvIS0l4wpr9KcUo=", 259 + "lastModified": 1767634882, 260 + "narHash": "sha256-2GffSfQxe3sedHzK+sTKlYo/NTIAGzbFCIsNMUPAAnk=", 231 261 "owner": "nixos", 232 262 "repo": "nixpkgs", 233 - "rev": "1fd8bada0b6117e6c7eb54aad5813023eed37ccb", 263 + "rev": "3c9db02515ef1d9b6b709fc60ba9a540957f661c", 234 264 "type": "github" 235 265 }, 236 266 "original": { 237 267 "owner": "nixos", 238 - "ref": "nixos-unstable", 268 + "ref": "nixos-25.11", 239 269 "repo": "nixpkgs", 240 270 "type": "github" 241 271 } 242 272 }, 243 273 "nixpkgs_3": { 244 274 "locked": { 245 - "lastModified": 1751984180, 246 - "narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=", 275 + "lastModified": 1766070988, 276 + "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=", 247 277 "owner": "nixos", 248 278 "repo": "nixpkgs", 249 - "rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0", 279 + "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8", 250 280 "type": "github" 251 281 }, 252 282 "original": { ··· 311 341 }, 312 342 "tangled": { 313 343 "inputs": { 344 + "actor-typeahead-src": "actor-typeahead-src", 345 + "flake-compat": "flake-compat_2", 314 346 "gomod2nix": "gomod2nix", 315 347 "htmx-src": "htmx-src", 316 348 "htmx-ws-src": "htmx-ws-src", ··· 322 354 "sqlite-lib-src": "sqlite-lib-src" 323 355 }, 324 356 "locked": { 325 - "lastModified": 1754650303, 326 - "narHash": "sha256-6Jw4nZGclppKrDNjzu/DTbqEahamXi1NOjzd4VssqxA=", 327 - "ref": "master", 328 - "rev": "48467689090388d3c1b0648c9079dcaec3671e6e", 329 - "revCount": 1072, 357 + "lastModified": 1767767073, 358 + "narHash": "sha256-BSZJ1TY5lGt7xNgFRtcKwYcSOI6VC2CHLfm7y/GgHwU=", 359 + "ref": "refs/heads/master", 360 + "rev": "6dc86ffbed5a290ca6a4890caa2dadea5c8b8a81", 361 + "revCount": 1792, 330 362 "type": "git", 331 - "url": "https://tangled.sh/@tangled.sh/core" 363 + "url": "https://tangled.org/tangled.org/core" 332 364 }, 333 365 "original": { 334 - "ref": "master", 335 366 "type": "git", 336 - "url": "https://tangled.sh/@tangled.sh/core" 367 + "url": "https://tangled.org/tangled.org/core" 337 368 } 338 369 } 339 370 },
+97 -54
flake.nix
··· 1 1 { 2 2 description = "nix infra for tangled"; 3 + 3 4 inputs = { 4 - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 5 - tangled.url = "git+https://tangled.sh/@tangled.sh/core?ref=master"; 5 + nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; 6 + tangled.url = "git+https://tangled.org/tangled.org/core"; 6 7 colmena.url = "github:zhaofengli/colmena/release-0.4.x"; 7 8 disko = { 8 9 url = "github:nix-community/disko"; ··· 16 17 }; 17 18 }; 18 19 19 - outputs = 20 - { nixpkgs, disko, colmena, nixery-flake, tangled, ... }: 21 - { 22 - nixosConfigurations.nixery = nixpkgs.lib.nixosSystem { 23 - system = "x86_64-linux"; 24 - modules = [ 25 - disko.nixosModules.disko 26 - tangled.nixosModules.spindle 27 - ./hosts/nixery/configuration.nix 28 - ]; 29 - }; 30 - nixosConfigurations.pds = nixpkgs.lib.nixosSystem { 31 - system = "x86_64-linux"; 32 - modules = [ 33 - disko.nixosModules.disko 34 - ./hosts/pds/configuration.nix 35 - ]; 36 - }; 20 + outputs = { nixpkgs, disko, colmena, nixery-flake, tangled, ... }: 21 + let 22 + system = "x86_64-linux"; 23 + commonArgs = import ./common/ssh.nix; 37 24 38 - colmenaHive = colmena.lib.makeHive { 39 - meta = { 40 - nixpkgs = nixpkgs.legacyPackages.x86_64-linux; 41 - specialArgs = { 42 - nixery-pkgs = import nixery-flake.outPath { 43 - pkgs = import nixpkgs { system = "x86_64-linux"; }; 44 - }; 45 - commonArgs = import ./common/ssh.nix; 46 - }; 25 + # Helper function to create nixosConfiguration 26 + mkHost = hostname: extraModules: 27 + nixpkgs.lib.nixosSystem { 28 + inherit system; 29 + specialArgs = { inherit commonArgs; }; 30 + modules = [ 31 + disko.nixosModules.disko 32 + ./hosts/${hostname}/configuration.nix 33 + ] ++ extraModules; 47 34 }; 48 35 49 - defaults = { pkgs, ... }: { 50 - environment.systemPackages = [ 51 - pkgs.curl 52 - ]; 53 - }; 54 - pds = { pkgs, ... }: { 36 + # Helper function to create colmena host 37 + mkColmenaHost = hostname: targetHost: targetPort: extraModules: 38 + { 55 39 deployment = { 56 - targetHost = "tngl.sh"; 57 - targetPort = 22; 40 + inherit targetHost; 41 + inherit targetPort; 58 42 targetUser = "tangler"; 59 43 buildOnTarget = true; 60 44 }; 61 - nixpkgs.system = "x86_64-linux"; 62 - 45 + nixpkgs.system = system; 46 + time.timeZone = "Europe/Helsinki"; 63 47 imports = [ 64 48 disko.nixosModules.disko 65 - ./hosts/pds/configuration.nix 49 + ./hosts/${hostname}/configuration.nix 50 + ] ++ extraModules; 51 + }; 52 + 53 + # Host configurations 54 + hosts = { 55 + appview = { 56 + modules = [ 57 + tangled.nixosModules.appview 58 + ./hosts/appview/services/appview.nix 59 + ./hosts/appview/services/nginx.nix 60 + ]; 61 + target = "95.111.205.38"; 62 + }; 63 + 64 + pds = { 65 + modules = [ 66 66 ./hosts/pds/services/nginx.nix 67 67 ./hosts/pds/services/pds.nix 68 68 ]; 69 - time.timeZone = "Europe/Helsinki"; 69 + target = "tngl.sh"; 70 70 }; 71 71 72 - nixery = { pkgs, ... }: { 73 - deployment = { 74 - targetHost = "nixery.tangled.sh"; 75 - targetPort = 22; 76 - targetUser = "tangler"; 77 - buildOnTarget = true; 78 - }; 79 - nixpkgs.system = "x86_64-linux"; 80 - 81 - imports = [ 82 - disko.nixosModules.disko 72 + nixery = { 73 + modules = [ 83 74 tangled.nixosModules.spindle 84 - ./hosts/nixery/configuration.nix 85 75 ./hosts/nixery/services/nginx.nix 86 76 ./hosts/nixery/services/openbao/openbao.nix 87 77 ./hosts/nixery/services/openbao/proxy.nix 88 78 ./hosts/nixery/services/nixery.nix 89 79 ]; 90 - time.timeZone = "Europe/Helsinki"; 80 + target = "nixery.tangled.sh"; 81 + }; 82 + 83 + spindle = { 84 + modules = [ 85 + tangled.nixosModules.spindle 86 + ./hosts/spindle/services/openbao/openbao.nix 87 + ./hosts/spindle/services/openbao/proxy.nix 88 + ./hosts/spindle/services/spindle.nix 89 + ./hosts/spindle/services/nginx.nix 90 + ]; 91 + target = "spindle.alpha.tangled.sh"; 92 + }; 93 + 94 + knot1 = { 95 + modules = [ 96 + tangled.nixosModules.knot 97 + ./hosts/knot1/services/knot.nix 98 + ./hosts/knot1/services/nginx.nix 99 + ]; 100 + target = "knot1.alpha.tangled.sh"; 101 + }; 102 + }; 103 + in 104 + { 105 + # nixos-anywhere and nixos-rebuild use these 106 + nixosConfigurations = { 107 + appview = mkHost "appview" hosts.appview.modules; 108 + pds = mkHost "pds" hosts.pds.modules; 109 + nixery = mkHost "nixery" hosts.nixery.modules; 110 + spindle = mkHost "spindle" hosts.spindle.modules; 111 + knot1 = mkHost "knot1" hosts.knot1.modules; 112 + }; 113 + 114 + # colmena uses this 115 + colmenaHive = colmena.lib.makeHive { 116 + meta = { 117 + nixpkgs = nixpkgs.legacyPackages.${system}; 118 + specialArgs = { 119 + inherit commonArgs; 120 + nixery-pkgs = import nixery-flake.outPath { 121 + pkgs = import nixpkgs { inherit system; }; 122 + }; 123 + }; 91 124 }; 125 + 126 + defaults = { pkgs, ... }: { 127 + environment.systemPackages = [ pkgs.curl ]; 128 + }; 129 + 130 + appview = mkColmenaHost "appview" hosts.appview.target 2222 hosts.appview.modules; 131 + pds = mkColmenaHost "pds" hosts.pds.target 22 hosts.pds.modules; 132 + nixery = mkColmenaHost "nixery" hosts.nixery.target 22 hosts.nixery.modules; 133 + spindle = mkColmenaHost "spindle" hosts.spindle.target 22 hosts.spindle.modules; 134 + knot1 = mkColmenaHost "knot1" hosts.knot1.target 22 hosts.knot1.modules; 92 135 }; 93 136 }; 94 137 }
+62
hosts/appview/configuration.nix
··· 1 + { modulesPath 2 + , lib 3 + , pkgs 4 + , ... 5 + } @ args: 6 + { 7 + imports = [ 8 + (modulesPath + "/installer/scan/not-detected.nix") 9 + (modulesPath + "/profiles/qemu-guest.nix") 10 + ./disk-config.nix 11 + ]; 12 + boot.loader.grub = { 13 + # no need to set devices, disko will add all devices that have a EF02 partition to the list already 14 + # devices = [ ]; 15 + efiSupport = true; 16 + efiInstallAsRemovable = true; 17 + }; 18 + 19 + networking.hostName = "appview-arn"; 20 + services = { 21 + openssh.enable = true; 22 + openssh.ports = [2222]; 23 + }; 24 + 25 + # networking.extraHosts = '' 26 + # 85.9.211.103 knot1.tangled.sh 27 + # ''; 28 + 29 + 30 + nix = { 31 + extraOptions = '' 32 + experimental-features = nix-command flakes ca-derivations 33 + warn-dirty = false 34 + keep-outputs = false 35 + ''; 36 + }; 37 + 38 + environment.systemPackages = map lib.lowPrio [ 39 + pkgs.curl 40 + pkgs.gitMinimal 41 + ]; 42 + 43 + users.users.tangler = { 44 + extraGroups = [ "networkmanager" "wheel" ]; 45 + openssh.authorizedKeys.keys = args.commonArgs.sshKeys; 46 + isNormalUser = true; 47 + }; 48 + 49 + security.sudo.extraRules = [ 50 + { 51 + users = [ "tangler" ]; 52 + commands = [ 53 + { 54 + command = "ALL"; 55 + options = [ "NOPASSWD" ]; 56 + } 57 + ]; 58 + } 59 + ]; 60 + 61 + system.stateVersion = "25.05"; 62 + }
+56
hosts/appview/disk-config.nix
··· 1 + # Example to create a bios compatible gpt partition 2 + { lib, ... }: 3 + { 4 + disko.devices = { 5 + disk.disk1 = { 6 + device = lib.mkDefault "/dev/vda"; 7 + type = "disk"; 8 + content = { 9 + type = "gpt"; 10 + partitions = { 11 + boot = { 12 + name = "boot"; 13 + size = "1M"; 14 + type = "EF02"; 15 + }; 16 + esp = { 17 + name = "ESP"; 18 + size = "500M"; 19 + type = "EF00"; 20 + content = { 21 + type = "filesystem"; 22 + format = "vfat"; 23 + mountpoint = "/boot"; 24 + }; 25 + }; 26 + root = { 27 + name = "root"; 28 + size = "100%"; 29 + content = { 30 + type = "lvm_pv"; 31 + vg = "pool"; 32 + }; 33 + }; 34 + }; 35 + }; 36 + }; 37 + lvm_vg = { 38 + pool = { 39 + type = "lvm_vg"; 40 + lvs = { 41 + root = { 42 + size = "100%FREE"; 43 + content = { 44 + type = "filesystem"; 45 + format = "ext4"; 46 + mountpoint = "/"; 47 + mountOptions = [ 48 + "defaults" 49 + ]; 50 + }; 51 + }; 52 + }; 53 + }; 54 + }; 55 + }; 56 + }
+11
hosts/appview/services/appview.nix
··· 1 + { modulesPath 2 + , lib 3 + , pkgs 4 + , ... 5 + } @ args: 6 + { 7 + services.tangled.appview = { 8 + enable = true; 9 + environmentFile = "/etc/secrets/appview.env"; 10 + }; 11 + }
+6
hosts/appview/services/litestream.nix
··· 1 + { 2 + services.litestream = { 3 + enable = true; 4 + environmentFile = "/etc/secrets/litestream.env" 5 + }; 6 + }
+53
hosts/appview/services/nginx-alpha.nix
··· 1 + { config, pkgs, ... }: 2 + { 3 + services.nginx = { 4 + enable = true; 5 + recommendedTlsSettings = true; 6 + recommendedOptimisation = true; 7 + recommendedGzipSettings = true; 8 + 9 + # Fix proxy headers hash warnings 10 + appendHttpConfig = '' 11 + proxy_headers_hash_max_size 1024; 12 + proxy_headers_hash_bucket_size 128; 13 + ''; 14 + 15 + virtualHosts = { 16 + # AppView service on alpha.tangled.sh 17 + "alpha.tangled.sh" = { 18 + forceSSL = true; 19 + enableACME = true; 20 + 21 + locations."/" = { 22 + proxyPass = "http://127.0.0.1:3000"; 23 + extraConfig = '' 24 + proxy_http_version 1.1; 25 + proxy_set_header Host $host; 26 + proxy_set_header X-Real-IP $remote_addr; 27 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 28 + proxy_set_header X-Forwarded-Proto $scheme; 29 + client_max_body_size 100M; 30 + ''; 31 + }; 32 + 33 + # WebSocket support for /logs endpoint 34 + locations."~ /logs$" = { 35 + proxyPass = "http://127.0.0.1:3000"; 36 + proxyWebsockets = true; 37 + extraConfig = '' 38 + proxy_read_timeout 86400; 39 + ''; 40 + }; 41 + }; 42 + }; 43 + }; 44 + 45 + # Open firewall ports 46 + networking.firewall.allowedTCPPorts = [ 80 443 ]; 47 + 48 + # ACME configuration for Let's Encrypt 49 + security.acme = { 50 + acceptTerms = true; 51 + defaults.email = "team@tangled.org"; 52 + }; 53 + }
+123
hosts/appview/services/nginx.nix
··· 1 + { config, pkgs, ... }: 2 + { 3 + services.nginx = { 4 + enable = true; 5 + recommendedProxySettings = true; 6 + recommendedTlsSettings = true; 7 + recommendedOptimisation = true; 8 + recommendedGzipSettings = true; 9 + 10 + # bot blocking 11 + appendHttpConfig = '' 12 + map $http_user_agent $block_bot { 13 + default 0; 14 + ~*PerplexityBot 1; 15 + ~*GPTBot 1; 16 + ~*ChatGPT-User 1; 17 + ~*CCBot 1; 18 + ~*anthropic-ai 1; 19 + ~*Claude-Web 1; 20 + } 21 + ''; 22 + 23 + streamConfig = '' 24 + upstream knot-sailor { 25 + server 94.237.110.185:22; 26 + } 27 + 28 + server { 29 + listen 22; 30 + listen [::]:22; 31 + proxy_pass knot-sailor; 32 + } 33 + ''; 34 + 35 + virtualHosts = { 36 + # Redirect tangled.sh โ†’ tangled.org 37 + "tangled.sh" = { 38 + serverAliases = [ "www.tangled.sh" ]; 39 + locations."/" = { 40 + return = "301 https://tangled.org$request_uri"; 41 + }; 42 + forceSSL = true; 43 + enableACME = true; 44 + }; 45 + 46 + # Redirect strings.tangled.sh โ†’ tangled.org/strings/* 47 + "strings.tangled.sh" = { 48 + locations."/" = { 49 + return = "301 https://tangled.org/strings$request_uri"; 50 + }; 51 + forceSSL = true; 52 + enableACME = true; 53 + }; 54 + 55 + # Redirect strings.tangled.org โ†’ tangled.org/strings/* 56 + "strings.tangled.org" = { 57 + locations."/" = { 58 + return = "301 https://tangled.org/strings$request_uri"; 59 + }; 60 + forceSSL = true; 61 + enableACME = true; 62 + }; 63 + 64 + # Main app on tangled.org 65 + "tangled.org" = { 66 + serverAliases = [ "www.tangled.org" ]; 67 + 68 + forceSSL = true; 69 + enableACME = true; 70 + 71 + extraConfig = '' 72 + if ($block_bot) { 73 + return 403; 74 + } 75 + 76 + # Redirect www โ†’ bare domain 77 + if ($host = www.tangled.org) { 78 + return 301 https://tangled.org$request_uri; 79 + } 80 + 81 + client_max_body_size 100M; 82 + ''; 83 + 84 + locations."~ ^/@tangled\\.sh(/.*)?$" = { 85 + extraConfig = '' 86 + rewrite ^/@tangled\.sh(.*)$ https://tangled.org/@tangled.org$1 permanent; 87 + ''; 88 + }; 89 + 90 + locations."~ ^/tangled\\.sh(/.*)?$" = { 91 + extraConfig = '' 92 + rewrite ^/tangled\.sh(.*)$ https://tangled.org/tangled.org$1 permanent; 93 + ''; 94 + }; 95 + 96 + 97 + locations."~ /logs$" = { 98 + proxyPass = "http://127.0.0.1:3000"; 99 + proxyWebsockets = true; 100 + extraConfig = '' 101 + proxy_read_timeout 86400; 102 + ''; 103 + }; 104 + 105 + locations."/" = { 106 + proxyPass = "http://127.0.0.1:3000"; 107 + extraConfig = '' 108 + client_max_body_size 100M; 109 + ''; 110 + }; 111 + }; 112 + }; 113 + }; 114 + 115 + # Open firewall ports 116 + networking.firewall.allowedTCPPorts = [ 80 443 2222 22 ]; 117 + 118 + # ACME configuration for Let's Encrypt 119 + security.acme = { 120 + acceptTerms = true; 121 + defaults.email = "team@tangled.org"; 122 + }; 123 + }
+57
hosts/knot1/configuration.nix
··· 1 + { modulesPath 2 + , lib 3 + , pkgs 4 + , ... 5 + } @ args: 6 + { 7 + imports = [ 8 + (modulesPath + "/installer/scan/not-detected.nix") 9 + (modulesPath + "/profiles/qemu-guest.nix") 10 + ./disk-config.nix 11 + ]; 12 + boot.loader.grub = { 13 + # no need to set devices, disko will add all devices that have a EF02 partition to the list already 14 + # devices = [ ]; 15 + efiSupport = true; 16 + efiInstallAsRemovable = true; 17 + }; 18 + 19 + networking.hostName = "knot1-ams"; 20 + services = { 21 + openssh.enable = true; 22 + }; 23 + 24 + 25 + nix = { 26 + extraOptions = '' 27 + experimental-features = nix-command flakes ca-derivations 28 + warn-dirty = false 29 + keep-outputs = false 30 + ''; 31 + }; 32 + 33 + environment.systemPackages = map lib.lowPrio [ 34 + pkgs.curl 35 + pkgs.gitMinimal 36 + ]; 37 + 38 + users.users.tangler = { 39 + extraGroups = [ "networkmanager" "wheel" "docker" ]; 40 + openssh.authorizedKeys.keys = args.commonArgs.sshKeys; 41 + isNormalUser = true; 42 + }; 43 + 44 + security.sudo.extraRules = [ 45 + { 46 + users = [ "tangler" ]; 47 + commands = [ 48 + { 49 + command = "ALL"; 50 + options = [ "NOPASSWD" ]; 51 + } 52 + ]; 53 + } 54 + ]; 55 + 56 + system.stateVersion = "25.05"; 57 + }
+56
hosts/knot1/disk-config.nix
··· 1 + # Example to create a bios compatible gpt partition 2 + { lib, ... }: 3 + { 4 + disko.devices = { 5 + disk.disk1 = { 6 + device = lib.mkDefault "/dev/vda"; 7 + type = "disk"; 8 + content = { 9 + type = "gpt"; 10 + partitions = { 11 + boot = { 12 + name = "boot"; 13 + size = "1M"; 14 + type = "EF02"; 15 + }; 16 + esp = { 17 + name = "ESP"; 18 + size = "500M"; 19 + type = "EF00"; 20 + content = { 21 + type = "filesystem"; 22 + format = "vfat"; 23 + mountpoint = "/boot"; 24 + }; 25 + }; 26 + root = { 27 + name = "root"; 28 + size = "100%"; 29 + content = { 30 + type = "lvm_pv"; 31 + vg = "pool"; 32 + }; 33 + }; 34 + }; 35 + }; 36 + }; 37 + lvm_vg = { 38 + pool = { 39 + type = "lvm_vg"; 40 + lvs = { 41 + root = { 42 + size = "100%FREE"; 43 + content = { 44 + type = "filesystem"; 45 + format = "ext4"; 46 + mountpoint = "/"; 47 + mountOptions = [ 48 + "defaults" 49 + ]; 50 + }; 51 + }; 52 + }; 53 + }; 54 + }; 55 + }; 56 + }
+11
hosts/knot1/services/knot.nix
··· 1 + { 2 + services.tangled.knot = { 3 + enable = true; 4 + stateDir = "/home/git"; 5 + server = { 6 + listenAddr = "127.0.0.1:5555"; 7 + owner = "did:plc:hwevmowznbiukdf6uk5dwrrq"; 8 + hostname = "knot1.alpha.tangled.sh"; 9 + }; 10 + }; 11 + }
+35
hosts/knot1/services/nginx.nix
··· 1 + { 2 + services.nginx = { 3 + enable = true; 4 + virtualHosts = { 5 + "knot1.alpha.tangled.sh" = { 6 + forceSSL = true; 7 + enableACME = true; 8 + locations."/" = { 9 + proxyPass = "http://127.0.0.1:5555"; 10 + 11 + extraConfig = '' 12 + proxy_set_header X-Forwarded-For $remote_addr; 13 + proxy_set_header Host $host; 14 + proxy_set_header X-Real-IP $remote_addr; 15 + proxy_set_header X-Forwarded-Proto $scheme; 16 + ''; 17 + }; 18 + locations."/events" = { 19 + proxyPass = "http://127.0.0.1:5555"; 20 + extraConfig = '' 21 + proxy_set_header X-Forwarded-For $remote_addr; 22 + proxy_set_header Host $host; 23 + proxy_set_header Upgrade $http_upgrade; 24 + proxy_set_header Connection "upgrade"; 25 + ''; 26 + }; 27 + }; 28 + }; 29 + }; 30 + security.acme = { 31 + acceptTerms = true; 32 + defaults.email = "team@tangled.org"; 33 + }; 34 + networking.firewall.allowedTCPPorts = [ 80 443 ]; 35 + }
+6 -1
hosts/nixery/configuration.nix
··· 19 19 networking.hostName = "nixery"; 20 20 services = { 21 21 openssh.enable = true; 22 - tangled-spindle = { 22 + tangled.spindle = { 23 23 enable = true; 24 24 server = { 25 25 owner = "did:plc:wshs7t2adsemcrrd4snkeqli"; # @tangled.sh 26 26 hostname = "spindle.tangled.sh"; 27 27 listenAddr = "127.0.0.1:6555"; 28 + queueSize = 100; 29 + maxJobCount = 2; 28 30 secrets = { 29 31 provider = "openbao"; 30 32 }; 33 + }; 34 + pipelines = { 35 + workflowTimeout = "15m"; 31 36 }; 32 37 }; 33 38 };
+94
hosts/nixery/services/docker-cleanup.nix
··· 1 + { config, pkgs, ... }: 2 + 3 + { 4 + systemd.services.docker-cleanup = { 5 + description = "Docker cleanup service - removes unused containers, networks, images, and volumes"; 6 + 7 + serviceConfig = { 8 + Type = "oneshot"; 9 + User = "root"; 10 + ExecStart = pkgs.writeShellScript "docker-cleanup" '' 11 + set -e 12 + 13 + echo "Starting Docker cleanup at $(date)" 14 + 15 + # remove containers running for 15+ minutes 16 + echo "Removing containers running for 15+ minutes..." 17 + CONTAINERS_TO_REMOVE=$(${pkgs.docker}/bin/docker ps --format "table {{.ID}}\t{{.RunningFor}}" --no-trunc | awk ' 18 + /minute/ { 19 + if ($2 >= 15) print $1 20 + } 21 + /hour/ { 22 + print $1 23 + } 24 + /day/ { 25 + print $1 26 + } 27 + /week/ { 28 + print $1 29 + } 30 + /month/ { 31 + print $1 32 + } 33 + /year/ { 34 + print $1 35 + } 36 + ') 37 + 38 + if [ -n "$CONTAINERS_TO_REMOVE" ]; then 39 + echo "Found containers to remove: $CONTAINERS_TO_REMOVE" 40 + echo "$CONTAINERS_TO_REMOVE" | xargs -r ${pkgs.docker}/bin/docker rm -f 41 + else 42 + echo "No containers running for 15+ minutes found" 43 + fi 44 + 45 + # remove stopped containers 46 + echo "Removing stopped containers..." 47 + ${pkgs.docker}/bin/docker container prune -f || true 48 + 49 + # remove unused networks (excluding default networks) 50 + echo "Removing unused networks..." 51 + ${pkgs.docker}/bin/docker network prune -f || true 52 + 53 + # remove unused images (dangling images only by default) 54 + echo "Removing dangling images..." 55 + ${pkgs.docker}/bin/docker image prune -f || true 56 + 57 + # remove unused volumes 58 + echo "Removing unused volumes..." 59 + ${pkgs.docker}/bin/docker volume prune -f || true 60 + 61 + echo "Docker cleanup completed at $(date)" 62 + ''; 63 + 64 + PrivateNetwork = false; # Needs network access for Docker 65 + ProtectSystem = "strict"; 66 + ProtectHome = true; 67 + NoNewPrivileges = true; 68 + 69 + # Logging 70 + StandardOutput = "journal"; 71 + StandardError = "journal"; 72 + }; 73 + 74 + # ensure docker is running before cleanup 75 + after = [ "docker.service" ]; 76 + requires = [ "docker.service" ]; 77 + 78 + # don't restart on failure 79 + restartIfChanged = false; 80 + }; 81 + 82 + # timer to run the cleanup service every n minutes 83 + systemd.timers.docker-cleanup = { 84 + description = "Timer for Docker cleanup service"; 85 + timerConfig = { 86 + OnCalendar = "*:0/15"; # run every 15 minutes 87 + Persistent = true; # persist timer across reboots 88 + WakeSystem = false; # run immediately if the system was powered off when timer should have run 89 + }; 90 + wantedBy = [ "timers.target" ]; 91 + }; 92 + 93 + virtualisation.docker.enable = true; 94 + }
+11
hosts/nixery/services/nginx.nix
··· 1 + { tangled-pkgs, pkgs, ... }: 2 + 1 3 { 2 4 services.nginx = { 3 5 enable = true; 4 6 virtualHosts = { 7 + "docs.tangled.org" = { 8 + forceSSL = true; 9 + enableACME = true; 10 + root = "${tangled-pkgs.docs}"; 11 + locations."/" = { 12 + tryFiles = "$uri $uri/ =404"; 13 + index = "index.html"; 14 + }; 15 + }; 5 16 "nixery.tangled.sh" = { 6 17 forceSSL = true; 7 18 enableACME = true;
+20
hosts/nixery/services/openbao/openbao.nix
··· 1 + { config, pkgs, lib, ... }: 1 2 { 3 + # Create openbao user and group 4 + users.groups.openbao = {}; 5 + 6 + users.users.openbao = { 7 + isSystemUser = true; 8 + group = "openbao"; 9 + home = "/var/lib/openbao"; 10 + createHome = true; 11 + description = "OpenBao service user"; 12 + }; 13 + 14 + systemd.services.openbao = { 15 + serviceConfig = { 16 + DynamicUser = lib.mkForce false; 17 + User = "openbao"; 18 + Group = "openbao"; 19 + }; 20 + }; 21 + 2 22 services.openbao = { 3 23 enable = true; 4 24 settings = {
+57
hosts/spindle/configuration.nix
··· 1 + { modulesPath 2 + , lib 3 + , pkgs 4 + , ... 5 + } @ args: 6 + { 7 + imports = [ 8 + (modulesPath + "/installer/scan/not-detected.nix") 9 + (modulesPath + "/profiles/qemu-guest.nix") 10 + ./disk-config.nix 11 + ]; 12 + boot.loader.grub = { 13 + # no need to set devices, disko will add all devices that have a EF02 partition to the list already 14 + # devices = [ ]; 15 + efiSupport = true; 16 + efiInstallAsRemovable = true; 17 + }; 18 + 19 + networking.hostName = "spindle-waw"; 20 + services = { 21 + openssh.enable = true; 22 + }; 23 + 24 + 25 + nix = { 26 + extraOptions = '' 27 + experimental-features = nix-command flakes ca-derivations 28 + warn-dirty = false 29 + keep-outputs = false 30 + ''; 31 + }; 32 + 33 + environment.systemPackages = map lib.lowPrio [ 34 + pkgs.curl 35 + pkgs.gitMinimal 36 + ]; 37 + 38 + users.users.tangler = { 39 + extraGroups = [ "networkmanager" "wheel" "docker" ]; 40 + openssh.authorizedKeys.keys = args.commonArgs.sshKeys; 41 + isNormalUser = true; 42 + }; 43 + 44 + security.sudo.extraRules = [ 45 + { 46 + users = [ "tangler" ]; 47 + commands = [ 48 + { 49 + command = "ALL"; 50 + options = [ "NOPASSWD" ]; 51 + } 52 + ]; 53 + } 54 + ]; 55 + 56 + system.stateVersion = "25.05"; 57 + }
+56
hosts/spindle/disk-config.nix
··· 1 + # Example to create a bios compatible gpt partition 2 + { lib, ... }: 3 + { 4 + disko.devices = { 5 + disk.disk1 = { 6 + device = lib.mkDefault "/dev/vda"; 7 + type = "disk"; 8 + content = { 9 + type = "gpt"; 10 + partitions = { 11 + boot = { 12 + name = "boot"; 13 + size = "1M"; 14 + type = "EF02"; 15 + }; 16 + esp = { 17 + name = "ESP"; 18 + size = "500M"; 19 + type = "EF00"; 20 + content = { 21 + type = "filesystem"; 22 + format = "vfat"; 23 + mountpoint = "/boot"; 24 + }; 25 + }; 26 + root = { 27 + name = "root"; 28 + size = "100%"; 29 + content = { 30 + type = "lvm_pv"; 31 + vg = "pool"; 32 + }; 33 + }; 34 + }; 35 + }; 36 + }; 37 + lvm_vg = { 38 + pool = { 39 + type = "lvm_vg"; 40 + lvs = { 41 + root = { 42 + size = "100%FREE"; 43 + content = { 44 + type = "filesystem"; 45 + format = "ext4"; 46 + mountpoint = "/"; 47 + mountOptions = [ 48 + "defaults" 49 + ]; 50 + }; 51 + }; 52 + }; 53 + }; 54 + }; 55 + }; 56 + }
+37
hosts/spindle/services/nginx.nix
··· 1 + { 2 + services.nginx = { 3 + enable = true; 4 + virtualHosts = { 5 + "spindle.alpha.tangled.sh" = { 6 + forceSSL = true; 7 + enableACME = true; 8 + locations."/" = { 9 + proxyPass = "http://127.0.0.1:6555"; 10 + }; 11 + locations."/events" = { 12 + proxyPass = "http://127.0.0.1:6555"; 13 + extraConfig = '' 14 + proxy_set_header X-Forwarded-For $remote_addr; 15 + proxy_set_header Host $host; 16 + proxy_set_header Upgrade $http_upgrade; 17 + proxy_set_header Connection "upgrade"; 18 + ''; 19 + }; 20 + locations."/logs/" = { 21 + proxyPass = "http://127.0.0.1:6555"; 22 + extraConfig = '' 23 + proxy_set_header X-Forwarded-For $remote_addr; 24 + proxy_set_header Host $host; 25 + proxy_set_header Upgrade $http_upgrade; 26 + proxy_set_header Connection "upgrade"; 27 + ''; 28 + }; 29 + }; 30 + }; 31 + }; 32 + security.acme = { 33 + acceptTerms = true; 34 + defaults.email = "team@tangled.org"; 35 + }; 36 + networking.firewall.allowedTCPPorts = [ 80 443 ]; 37 + }
+39
hosts/spindle/services/openbao/openbao.nix
··· 1 + { config, pkgs, lib, ... }: 2 + { 3 + # Create openbao user and group 4 + users.groups.openbao = {}; 5 + 6 + users.users.openbao = { 7 + isSystemUser = true; 8 + group = "openbao"; 9 + home = "/var/lib/openbao"; 10 + createHome = true; 11 + description = "OpenBao service user"; 12 + }; 13 + 14 + systemd.services.openbao = { 15 + serviceConfig = { 16 + DynamicUser = lib.mkForce false; 17 + User = "openbao"; 18 + Group = "openbao"; 19 + }; 20 + }; 21 + 22 + services.openbao = { 23 + enable = true; 24 + settings = { 25 + ui = true; 26 + 27 + listener.default = { 28 + type = "tcp"; 29 + address = "127.0.0.1:8201"; 30 + tls_disable = true; 31 + }; 32 + 33 + cluster_addr = "http://127.0.0.1:8202"; 34 + api_addr = "http://127.0.0.1:8201"; 35 + 36 + storage.raft.path = "/var/lib/openbao"; 37 + }; 38 + }; 39 + }
+100
hosts/spindle/services/openbao/proxy.nix
··· 1 + { pkgs, ... }: 2 + 3 + { 4 + systemd.services.openbao-proxy = { 5 + description = "OpenBao Proxy with Auto-Auth"; 6 + after = [ "network.target" ]; 7 + wantedBy = [ "multi-user.target" ]; 8 + serviceConfig = { 9 + User = "root"; 10 + ExecStart = "${pkgs.openbao}/bin/bao proxy -config=/etc/openbao/proxy.hcl"; 11 + Restart = "always"; 12 + RestartSec = "5"; 13 + LimitNOFILE = "65536"; 14 + }; 15 + }; 16 + 17 + 18 + 19 + environment.etc."openbao/proxy.hcl".text = '' 20 + vault { 21 + address = "http://localhost:8201" 22 + 23 + # Retry configuration 24 + retry { 25 + num_retries = 5 26 + } 27 + } 28 + 29 + # Auto-Auth using AppRole 30 + auto_auth { 31 + method "approle" { 32 + mount_path = "auth/approle" 33 + config = { 34 + role_id_file_path = "/etc/openbao/role-id" 35 + secret_id_file_path = "/etc/openbao/secret-id" 36 + remove_secret_id_file_after_reading = false 37 + } 38 + } 39 + 40 + # Write authenticated token to file 41 + sink "file" { 42 + config = { 43 + path = "/var/lib/openbao/token" 44 + mode = 0640 45 + } 46 + } 47 + } 48 + 49 + # API Proxy listener for Spindle 50 + listener "tcp" { 51 + address = "127.0.0.1:8200" 52 + tls_disable = true 53 + 54 + # Security headers 55 + require_request_header = false 56 + 57 + # Enable proxy API for management 58 + proxy_api { 59 + enable_quit = true 60 + } 61 + } 62 + 63 + # Enable API proxy with auto-auth token 64 + api_proxy { 65 + use_auto_auth_token = true 66 + } 67 + 68 + cache { 69 + } 70 + 71 + # Logging configuration 72 + log_level = "info" 73 + log_format = "standard" 74 + log_file = "/var/log/openbao/proxy.log" 75 + log_rotate_duration = "24h" 76 + log_rotate_max_files = 30 77 + 78 + # Process management 79 + pid_file = "/var/lib/openbao/proxy.pid" 80 + 81 + # Disable idle connections for reliability 82 + disable_idle_connections = ["auto-auth", "proxying"] 83 + ''; 84 + 85 + # Create necessary directories and files 86 + systemd.tmpfiles.rules = [ 87 + # Directories 88 + "d /var/lib/openbao 0755 root root -" 89 + "d /var/lib/openbao/cache 0755 root root -" 90 + "d /var/log/openbao 0755 root root -" 91 + "d /etc/openbao 0755 root root -" 92 + 93 + # Credential files (content must be populated externally) 94 + "f /etc/openbao/role-id 0600 root root -" 95 + "f /etc/openbao/secret-id 0600 root root -" 96 + 97 + # Configuration file 98 + "f /etc/openbao/proxy.hcl 0644 root root -" 99 + ]; 100 + }
+19
hosts/spindle/services/spindle.nix
··· 1 + { config, pkgs, ... }: 2 + { 3 + services.tangled.spindle = { 4 + enable = true; 5 + server = { 6 + owner = "did:plc:wshs7t2adsemcrrd4snkeqli"; # @tangled.sh 7 + hostname = "spindle.alpha.tangled.sh"; 8 + listenAddr = "127.0.0.1:6555"; 9 + queueSize = 100; 10 + maxJobCount = 2; 11 + secrets = { 12 + provider = "openbao"; 13 + }; 14 + }; 15 + pipelines = { 16 + workflowTimeout = "15m"; 17 + }; 18 + }; 19 + }