Tangled infrastructure definitions in Nix

Compare changes

Choose any two refs to compare.

+15 -15
flake.lock
··· 45 45 ] 46 46 }, 47 47 "locked": { 48 - "lastModified": 1751854533, 49 - "narHash": "sha256-U/OQFplExOR1jazZY4KkaQkJqOl59xlh21HP9mI79Vc=", 48 + "lastModified": 1766150702, 49 + "narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", 50 50 "owner": "nix-community", 51 51 "repo": "disko", 52 - "rev": "16b74a1e304197248a1bc663280f2548dbfcae3c", 52 + "rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", 53 53 "type": "github" 54 54 }, 55 55 "original": { ··· 225 225 "nixery-flake": { 226 226 "flake": false, 227 227 "locked": { 228 - "lastModified": 1745149613, 229 - "narHash": "sha256-rcSnsnSWA0IUjmbG2iSpvVB0702tcR3zIyU3iFJBo0g=", 228 + "lastModified": 1762501370, 229 + "narHash": "sha256-WO2NvvFB3KkFfChE5F6ghog7mvBAVKpMsQMqwadZT4k=", 230 230 "owner": "tazjin", 231 231 "repo": "nixery", 232 - "rev": "c6d4692b1b6eb105c9abce0411d2ef4b8708a6e1", 232 + "rev": "be8a4005de3f27f95e677e7b61abef387d4a840d", 233 233 "type": "github" 234 234 }, 235 235 "original": { ··· 256 256 }, 257 257 "nixpkgs_2": { 258 258 "locked": { 259 - "lastModified": 1764522689, 260 - "narHash": "sha256-SqUuBFjhl/kpDiVaKLQBoD8TLD+/cTUzzgVFoaHrkqY=", 259 + "lastModified": 1767634882, 260 + "narHash": "sha256-2GffSfQxe3sedHzK+sTKlYo/NTIAGzbFCIsNMUPAAnk=", 261 261 "owner": "nixos", 262 262 "repo": "nixpkgs", 263 - "rev": "8bb5646e0bed5dbd3ab08c7a7cc15b75ab4e1d0f", 263 + "rev": "3c9db02515ef1d9b6b709fc60ba9a540957f661c", 264 264 "type": "github" 265 265 }, 266 266 "original": { ··· 354 354 "sqlite-lib-src": "sqlite-lib-src" 355 355 }, 356 356 "locked": { 357 - "lastModified": 1766504962, 358 - "narHash": "sha256-is2yCWdVQ0S5NY893TfTVFgBUpWkh8SFZd0yy+gsjAU=", 357 + "lastModified": 1767767073, 358 + "narHash": "sha256-BSZJ1TY5lGt7xNgFRtcKwYcSOI6VC2CHLfm7y/GgHwU=", 359 359 "ref": "refs/heads/master", 360 - "rev": "3596ea2d0f5aba3f13ccf92ceb2560b7fa581364", 361 - "revCount": 1775, 360 + "rev": "6dc86ffbed5a290ca6a4890caa2dadea5c8b8a81", 361 + "revCount": 1792, 362 362 "type": "git", 363 - "url": "https://tangled.org/@tangled.org/core" 363 + "url": "https://tangled.org/tangled.org/core" 364 364 }, 365 365 "original": { 366 366 "type": "git", 367 - "url": "https://tangled.org/@tangled.org/core" 367 + "url": "https://tangled.org/tangled.org/core" 368 368 } 369 369 } 370 370 },
+96 -54
flake.nix
··· 1 1 { 2 2 description = "nix infra for tangled"; 3 + 3 4 inputs = { 4 5 nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11"; 5 - tangled.url = "git+https://tangled.org/@tangled.org/core"; 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 - tangled-pkgs = tangled.packages.x86_64-linux; 46 - commonArgs = import ./common/ssh.nix; 47 - }; 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; 48 34 }; 49 35 50 - defaults = { pkgs, ... }: { 51 - environment.systemPackages = [ 52 - pkgs.curl 53 - ]; 54 - }; 55 - pds = { pkgs, ... }: { 36 + # Helper function to create colmena host 37 + mkColmenaHost = hostname: targetHost: targetPort: extraModules: 38 + { 56 39 deployment = { 57 - targetHost = "tngl.sh"; 58 - targetPort = 22; 40 + inherit targetHost; 41 + inherit targetPort; 59 42 targetUser = "tangler"; 60 43 buildOnTarget = true; 61 44 }; 62 - nixpkgs.system = "x86_64-linux"; 63 - 45 + nixpkgs.system = system; 46 + time.timeZone = "Europe/Helsinki"; 64 47 imports = [ 65 48 disko.nixosModules.disko 66 - ./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 = [ 67 66 ./hosts/pds/services/nginx.nix 68 67 ./hosts/pds/services/pds.nix 69 68 ]; 70 - time.timeZone = "Europe/Helsinki"; 69 + target = "tngl.sh"; 71 70 }; 72 71 73 - nixery = { pkgs, ... }: { 74 - deployment = { 75 - targetHost = "nixery.tangled.sh"; 76 - targetPort = 22; 77 - targetUser = "tangler"; 78 - buildOnTarget = true; 79 - }; 80 - nixpkgs.system = "x86_64-linux"; 81 - 82 - imports = [ 83 - disko.nixosModules.disko 72 + nixery = { 73 + modules = [ 84 74 tangled.nixosModules.spindle 85 - ./hosts/nixery/configuration.nix 86 75 ./hosts/nixery/services/nginx.nix 87 76 ./hosts/nixery/services/openbao/openbao.nix 88 77 ./hosts/nixery/services/openbao/proxy.nix 89 78 ./hosts/nixery/services/nixery.nix 90 79 ]; 91 - 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 + }; 92 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; 93 135 }; 94 136 }; 95 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 + }
+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 + }