{ description = "Alexion's NixOS configuration — one flake for every host"; inputs = { # Base channel. nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; # Fresher packages, reachable per-package as `unstable.`. nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable"; # Latest stable release, reachable per-package as `stable.`. nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-26.05"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; # Signed AMO extensions, pinned by version and hash. firefox-addons = { url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; inputs.nixpkgs.follows = "nixpkgs"; }; # Follows our nixpkgs so its plugins build against the same package set. nixvim = { url = "github:nix-community/nixvim"; inputs.nixpkgs.follows = "nixpkgs"; }; # Declarative disk partitioning. # Each host declares its own layout. disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; # Upstream per-machine hardware profiles. # Each host imports its own. nixos-hardware = { url = "github:NixOS/nixos-hardware"; inputs.nixpkgs.follows = "nixpkgs"; }; # Decrypts committed secrets at activation, from an age identity on the host. sops-nix = { url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; # Themes the graphical layer from one base16 scheme. # Follows our nixpkgs so it themes the same package set the host builds. stylix = { url = "github:danth/stylix"; inputs.nixpkgs.follows = "nixpkgs"; }; # Agent-ergonomic CLI for Gitea, with a home-manager module for the agent context. gitea-axi = { url = "git+https://git.alexion.dev/alexion/gitea-axi"; inputs.nixpkgs.follows = "nixpkgs"; }; # Personal agent skills, packaged as per-skill derivations with a home-manager module. skills = { url = "git+https://git.alexion.dev/alexion/skills"; inputs.nixpkgs.follows = "nixpkgs"; }; # CachyOS kernel and binary cache. # Pins its own nixpkgs so its cache stays usable and the kernel is fetched from it. chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; }; outputs = { self, nixpkgs, ... }@inputs: let inherit (nixpkgs) lib; my = import ./lib { inherit lib inputs self; }; in { # Helper functions for discovering and building hosts. lib = my; # Every host under hosts/ is discovered and built. nixosConfigurations = my.mkHosts (self + "/hosts"); # A project shell for agent-local resources that should travel with this # checkout rather than the operator's global profile. devShells.x86_64-linux.default = let pkgs = nixpkgs.legacyPackages.x86_64-linux; in pkgs.mkShell { packages = [ inputs.gitea-axi.packages.x86_64-linux.gitea-axi ]; shellHook = inputs.skills.lib.mkSkillsShellHook [ inputs.gitea-axi.packages.x86_64-linux.gitea-axi-skill ]; }; # `nix flake check` builds host toplevels and project-level checks. checks.x86_64-linux = let pkgs = nixpkgs.legacyPackages.x86_64-linux; in (lib.mapAttrs ( name: host: if host.config.warnings == [] then host.config.system.build.toplevel else throw "Host ${name} has evaluation warnings:\n${lib.concatStringsSep "\n" host.config.warnings}" ) self.nixosConfigurations) // { copyparty-reverse-proxy-trust-config = let pikachuConfig = self.nixosConfigurations.pikachu.config; serviceConfig = pikachuConfig.containers.copyparty.config.systemd.services.copyparty.serviceConfig; configScript = builtins.readFile serviceConfig.ExecStartPre; plainConfig = (lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module ./modules/services/copyparty.nix { modules.services.copyparty = { enable = true; passwordFile = "/run/password"; volumes."/" = { path = "/srv/files"; access.A = [ "alexion" ]; }; }; } ]; }).config; plainScript = builtins.readFile plainConfig.systemd.services.copyparty.serviceConfig.ExecStartPre; in pkgs.runCommand "copyparty-reverse-proxy-trust-config-check" { } '' ${ lib.optionalString ( !( lib.hasInfix " rproxy: 1" configScript && lib.hasInfix " xff-hdr: x-forwarded-for" configScript && lib.hasInfix " xff-src: 10.23.20.109/32" configScript && !(lib.hasInfix "rproxy" plainScript) && !(lib.hasInfix "xff-hdr" plainScript) && !(lib.hasInfix "xff-src" plainScript) ) ) "exit 1" } touch $out ''; copyparty-reverse-proxy-trust-requires-source = let testConfig = (lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module ./modules/services/copyparty.nix { modules.services.copyparty = { enable = true; passwordFile = "/run/password"; reverseProxy.enable = true; volumes."/" = { path = "/srv/files"; access.A = [ "alexion" ]; }; }; } ]; }).config; hasFailedTrustedSourcesAssertion = lib.any ( assertion: assertion.assertion == false && assertion.message == "modules.services.copyparty.reverseProxy.trustedSources must name at least one trusted proxy source when reverse-proxy support is enabled." ) testConfig.assertions; in pkgs.runCommand "copyparty-reverse-proxy-trust-requires-source-check" { } '' ${lib.optionalString (!hasFailedTrustedSourcesAssertion) "exit 1"} touch $out ''; reverse-proxy-basic-route = let routeHost = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.test.budget = "10.23.20.42"; registry.route.test.budget = { domain = "budget.alexion.dev"; port = 5006; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts.test = routeHost; }; testConfig = (lib.nixosSystem { system = "x86_64-linux"; specialArgs = { my = self.lib; inherit registry; }; modules = [ ./modules/reverse-proxy.nix { modules.reverse-proxy.enable = true; } ]; }).config; vhost = testConfig.services.nginx.virtualHosts."budget.alexion.dev"; in pkgs.runCommand "reverse-proxy-basic-route-check" { } '' ${ lib.optionalString ( !(vhost.forceSSL && vhost.enableACME && vhost.locations."/".proxyPass == "http://10.23.20.42:5006") ) "exit 1" } touch $out ''; reverse-proxy-actualbudget-guest-route = let testConfig = self.nixosConfigurations.pikachu.config; route = testConfig.registry.route.pikachu.actualbudget; vhost = testConfig.containers.reverse-proxy.config.services.nginx.virtualHosts."budget.alexion.dev"; in pkgs.runCommand "reverse-proxy-actualbudget-guest-route-check" { } '' ${ lib.optionalString ( !( route.domain == "budget.alexion.dev" && route.port == 5006 && !(route ? backend) && !(route ? path) && !(route ? scheme) && !(route ? aliases) && !(route ? tls) && !(route ? websockets) && !(route ? enable) && vhost.locations."/".proxyPass == "http://10.23.20.42:5006" ) ) "exit 1" } touch $out ''; generic-registry-fleet-view = let hosts = { alpha = lib.nixosSystem { system = "x86_64-linux"; specialArgs.registry = registry; modules = [ self.lib.registry.module { registry.address.alpha.service = "10.0.0.5"; system.stateVersion = "26.11"; } ]; }; beta = lib.nixosSystem { system = "x86_64-linux"; specialArgs.registry = registry; modules = [ self.lib.registry.module ( { registry, ... }: { environment.etc."registry-address-check".text = registry.address.alpha.service; system.stateVersion = "26.11"; } ) ]; }; }; registry = self.lib.registry.collect { inherit hosts; }; betaConfig = hosts.beta.config; in pkgs.runCommand "generic-registry-fleet-view-check" { } '' ${ lib.optionalString ( !( registry.address.alpha.service == "10.0.0.5" && betaConfig.environment.etc."registry-address-check".text == "10.0.0.5" && !(registry.address ? alpha-service) ) ) "exit 1" } touch $out ''; shell-shortcut-registry-fish-abbreviations = let testConfig = self.nixosConfigurations.neogaia.config; user = testConfig.user.name; shortcuts = testConfig.registry.shell-shortcut.${testConfig.networking.hostName}; fishConfig = testConfig.home-manager.users.${user}.programs.fish; abbrs = fishConfig.shellAbbrs; aliases = fishConfig.shellAliases; expectedAliases = { ls = "eza -alg --color=always --group-directories-first --icons=always"; la = "eza -a --color=always --group-directories-first --icons=always"; ll = "eza -lg --color=always --group-directories-first --icons=always"; lt = "eza -aT -I '.git' --color=always --group-directories-first --icons=always"; "l." = "eza -a | grep -e '^\\.'"; }; expectedAbbrs = { ".." = "cd .."; "..." = "cd ../.."; "...." = "cd ../../.."; vi = "nvim"; vim = "nvim"; cp = "cp -v"; tmx = "tmux new-session -A -s"; hs = "herdr --session"; tarnow = "tar -acf "; untar = "tar -zxvf "; wget = "wget -c "; grep = "grep --color=auto"; fgrep = "fgrep --color=auto"; egrep = "egrep --color=auto"; jctl = "journalctl -p 3 -xb"; update = "sudo nixos-rebuild switch --flake .#${testConfig.networking.hostName}"; cleanup = "sudo nix-collect-garbage -d"; }; in pkgs.runCommand "shell-shortcut-registry-fish-abbreviations-check" { } '' ${ lib.optionalString ( !( lib.mapAttrs (_name: shortcut: shortcut.command) shortcuts == (expectedAliases // expectedAbbrs) && lib.filterAttrs (name: _value: builtins.hasAttr name expectedAliases) aliases == expectedAliases && lib.filterAttrs (name: _value: builtins.hasAttr name expectedAbbrs) abbrs == expectedAbbrs ) ) "exit 1" } touch $out ''; address-registry = let hostAddressConfig = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.neogaia.self = "10.23.50.146"; system.stateVersion = "26.11"; } ]; }; guestAddressConfig = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ inputs.sops-nix.nixosModules.sops self.lib.registry.module ./modules/network.nix (self.lib.guest { name = "copyparty"; }) { networking.hostName = "pikachu"; modules.network.vlans = [ 20 ]; guests.copyparty = { enable = true; vlan = 20; address = "10.23.20.126"; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts = { neogaia = hostAddressConfig; pikachu = guestAddressConfig; }; }; in pkgs.runCommand "address-registry-check" { } '' ${ lib.optionalString ( !( registry.address.neogaia.self == "10.23.50.146" && registry.address.pikachu.copyparty == "10.23.20.126" && guestAddressConfig.config.containers.copyparty.config.systemd.network.networks."20-eth0".networkConfig.DHCP == "yes" ) ) "exit 1" } touch $out ''; ssh-targets-use-address-registry = let neogaiaConfig = self.nixosConfigurations.neogaia.config; pikachuConfig = self.nixosConfigurations.pikachu.config; neogaiaUser = neogaiaConfig.user.name; neogaiaSsh = neogaiaConfig.home-manager.users.${neogaiaUser}.programs.ssh.settings; in pkgs.runCommand "ssh-targets-use-address-registry-check" { } '' ${ lib.optionalString ( !( neogaiaConfig.registry.address.neogaia.self == "10.23.50.146" && pikachuConfig.registry.address.pikachu.self == "10.23.10.102" && neogaiaConfig.modules.ssh.targets.pikachu.hostName == pikachuConfig.registry.address.pikachu.self && pikachuConfig.modules.ssh.targets.neogaia.hostName == neogaiaConfig.registry.address.neogaia.self && neogaiaSsh.pikachu.data.HostName == pikachuConfig.registry.address.pikachu.self ) ) "exit 1" } touch $out ''; reverse-proxy-route-registry = let routeHost = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.routeHost.app = "10.23.20.126"; registry.route.routeHost.app = { domain = "worker.alexion.dev"; port = 3923; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts = { inherit routeHost; pikachu = self.nixosConfigurations.pikachu; }; }; testConfig = (lib.nixosSystem { system = "x86_64-linux"; specialArgs = { my = self.lib; inherit registry; }; modules = [ ./modules/reverse-proxy.nix { modules.reverse-proxy.enable = true; } ]; }).config; vhosts = testConfig.services.nginx.virtualHosts; in pkgs.runCommand "reverse-proxy-route-registry-check" { } '' ${ lib.optionalString ( !( registry.route ? routeHost && registry.route.routeHost ? app && registry.route.pikachu ? actualbudget && vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923" && vhosts."budget.alexion.dev".locations."/".proxyPass == "http://10.23.20.42:5006" && vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923" ) ) "exit 1" } touch $out ''; reverse-proxy-address-backends = let workerHost = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.worker.self = "10.23.50.146"; registry.route.worker.self = { domain = "worker.alexion.dev"; port = 8080; }; system.stateVersion = "26.11"; } ]; }; guestRouteHost = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ inputs.sops-nix.nixosModules.sops self.lib.registry.module ./modules/network.nix (self.lib.guest { name = "files"; }) { networking.hostName = "pikachu"; modules.network.vlans = [ 20 ]; guests.files = { enable = true; vlan = 20; address = "10.23.20.126"; reverseProxy = { enable = true; route = { domain = "files.alexion.dev"; port = 3923; }; }; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts = { worker = workerHost; pikachu = guestRouteHost; }; }; testConfig = (lib.nixosSystem { system = "x86_64-linux"; specialArgs = { my = self.lib; inherit registry; }; modules = [ ./modules/reverse-proxy.nix { modules.reverse-proxy.enable = true; } ]; }).config; vhosts = testConfig.services.nginx.virtualHosts; workerCompositeEntry = registry.join "worker" "self"; in pkgs.runCommand "reverse-proxy-address-backends-check" { } '' ${ lib.optionalString ( !( workerCompositeEntry.address == "10.23.50.146" && workerCompositeEntry.route.domain == "worker.alexion.dev" && vhosts."worker.alexion.dev".locations."/".proxyPass == "http://10.23.50.146:8080" && registry.route.pikachu.files.port == 3923 && vhosts."files.alexion.dev".locations."/".proxyPass == "http://10.23.20.126:3923" ) ) "exit 1" } touch $out ''; reverse-proxy-route-requires-address = let routeHost = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.route.raichu.files = { domain = "files.alexion.dev"; port = 3923; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts.raichu = routeHost; }; testConfig = lib.nixosSystem { system = "x86_64-linux"; specialArgs = { my = self.lib; inherit registry; }; modules = [ ./modules/reverse-proxy.nix { modules.reverse-proxy.enable = true; system.stateVersion = "26.11"; } ]; }; result = builtins.tryEval (builtins.deepSeq testConfig.config.services.nginx.virtualHosts true); in pkgs.runCommand "reverse-proxy-route-requires-address-check" { } '' ${lib.optionalString result.success "exit 1"} touch $out ''; reverse-proxy-duplicate-domain-fails = let pikachu = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.pikachu.copyparty = "10.23.20.126"; registry.route.pikachu.copyparty = { domain = "files.alexion.dev"; port = 3923; }; system.stateVersion = "26.11"; } ]; }; raichu = lib.nixosSystem { system = "x86_64-linux"; specialArgs.my = self.lib; modules = [ self.lib.registry.module { registry.address.raichu.files = "10.23.20.10"; registry.route.raichu.files = { domain = "files.alexion.dev"; port = 3923; }; system.stateVersion = "26.11"; } ]; }; registry = self.lib.registry.collect { hosts = { inherit pikachu raichu; }; }; testConfig = lib.nixosSystem { system = "x86_64-linux"; specialArgs = { my = self.lib; inherit registry; }; modules = [ ./modules/reverse-proxy.nix { modules.reverse-proxy.enable = true; system.stateVersion = "26.11"; } ]; }; result = builtins.tryEval (builtins.deepSeq testConfig.config.services.nginx.virtualHosts true); in pkgs.runCommand "reverse-proxy-duplicate-domain-fails-check" { } '' ${lib.optionalString result.success "exit 1"} touch $out ''; }; }; }