{ description = "LAN NixOS configs"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nixos-conf-editor.url = "github:snowfallorg/nixos-conf-editor"; home-manager = { url = "github:nix-community/home-manager/release-26.05"; inputs.nixpkgs.follows = "nixpkgs"; }; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; sops-nix = { url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; clan-core = { url = "https://git.clan.lol/clan/clan-core/archive/26.05.tar.gz"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, nixpkgs, nixos-conf-editor, home-manager, sops-nix, ... } @ inputs: let system = "x86_64-linux"; inherit (nixpkgs) lib; pkgs = nixpkgs.legacyPackages.${system}; vars = import ./variables.nix; # Generates a nixosConfiguration from a platform (what it runs on) and # a build type (what it's for), plus the per-identity host.nix that # carries the bits that must stay fixed regardless of platform # (hostName, hostId, per-machine secrets). Every build type except # nix-cache itself consumes the nix-cache substituter and remote # builder. mkTarget = { platform, buildType, hostPath, homeFile ? ./modules/common/home.nix }: let flakeTarget = "${platform}-${buildType}"; in nixpkgs.lib.nixosSystem { inherit system; modules = [ inputs.disko.nixosModules.disko sops-nix.nixosModules.sops ./modules/common/configuration.nix ./modules/platforms/${platform}.nix ./modules/build-types/${buildType}.nix hostPath { environment.etc."flake-target".text = flakeTarget; } home-manager.nixosModules.home-manager { home-manager = { useGlobalPkgs = true; useUserPackages = true; extraSpecialArgs = { inherit vars; }; users.nixos = import homeFile; }; } ] ++ lib.optionals (buildType != "nix-cache") [ ./modules/nix-cache/client.nix ./modules/nix-cache/remote-builder-client.nix ]; # flakeTarget is passed via specialArgs (not read back from # config.environment.etc."flake-target" above) specifically so # modules/platforms/lxc.nix can use it to select its own host key # file without a same-option circular dependency (a module # contributing to environment.etc can't read the merged # environment.etc it's itself contributing to). specialArgs = { inherit inputs vars netbootSystem netbootMinimalSystem flakeTarget; }; }; # Generated platform x build-type matrix. pxe-boot has no linode # variant (PXE/DHCP/TFTP need LAN L2 adjacency, which a Linode VPS # doesn't have). generatedTargets = { linode-minimal = mkTarget { platform = "linode"; buildType = "minimal"; hostPath = ./hosts/nix-minimal/host.nix; }; proxmox-minimal = mkTarget { platform = "proxmox"; buildType = "minimal"; hostPath = ./hosts/nix-minimal/host.nix; }; lxc-minimal = mkTarget { platform = "lxc"; buildType = "minimal"; hostPath = ./hosts/nix-minimal/host.nix; }; linode-nix-cache = mkTarget { platform = "linode"; buildType = "nix-cache"; hostPath = ./hosts/nix-cache/host.nix; }; proxmox-nix-cache = mkTarget { platform = "proxmox"; buildType = "nix-cache"; hostPath = ./hosts/nix-cache/host.nix; }; lxc-nix-cache = mkTarget { platform = "lxc"; buildType = "nix-cache"; hostPath = ./hosts/nix-cache/host.nix; }; linode-server = mkTarget { platform = "linode"; buildType = "server"; hostPath = ./hosts/server/host.nix; }; proxmox-server = mkTarget { platform = "proxmox"; buildType = "server"; hostPath = ./hosts/server/host.nix; }; lxc-server = mkTarget { platform = "lxc"; buildType = "server"; hostPath = ./hosts/server/host.nix; }; linode-docker = mkTarget { platform = "linode"; buildType = "docker"; hostPath = ./hosts/docker/host.nix; }; proxmox-docker = mkTarget { platform = "proxmox"; buildType = "docker"; hostPath = ./hosts/docker/host.nix; }; lxc-docker = mkTarget { platform = "lxc"; buildType = "docker"; hostPath = ./hosts/docker/host.nix; }; linode-gui = mkTarget { platform = "linode"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; }; proxmox-gui = mkTarget { platform = "proxmox"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; }; lxc-gui = mkTarget { platform = "lxc"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; }; baremetal-gui = mkTarget { platform = "baremetal"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; }; proxmox-pxe-boot = mkTarget { platform = "proxmox"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; }; lxc-pxe-boot = mkTarget { platform = "lxc"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; }; linode-tailscale-router = mkTarget { platform = "linode"; buildType = "tailscale-router"; hostPath = ./hosts/tailscale-router/host.nix; }; proxmox-tailscale-router = mkTarget { platform = "proxmox"; buildType = "tailscale-router"; hostPath = ./hosts/tailscale-router/host.nix; }; lxc-tailscale-router = mkTarget { platform = "lxc"; buildType = "tailscale-router"; hostPath = ./hosts/tailscale-router/host.nix; }; lxc-tor-relay = mkTarget { platform = "lxc"; buildType = "tor-relay"; hostPath = ./hosts/tor-relay/host.nix; }; }; # Auto-install environments (migrated from the former nix-auto-installer # flake): a self-contained NixOS installer that boots, discovers this # flake's own nixosConfigurations over the network, and runs # nixos-install against whichever one the operator picks. These are # deliberately not part of the platform x build-type matrix above — # they're throwaway boot media, not persistent hosts, so they skip # disko/sops-nix/home-manager and just need `vars`. installerTargets = { installer = nixpkgs.lib.nixosSystem { inherit system; modules = [ ./modules/installer/iso.nix ]; specialArgs = { inherit vars; }; }; }; # Same installer environment, built as netboot (kernel + initrd + # iPXE script) instead of an ISO — this is what packages.pxe bundles. # # Deliberately imports common.nix directly, NOT ./modules/installer/iso.nix # (which pulls in nixpkgs' installation-cd-minimal.nix) -- confirmed live # that composing the ISO module together with netboot-minimal.nix hangs # every boot waiting for a device that can never exist on a netboot # client ("A start job is running for /dev/disk/by-label/nixos-minimal-..."). # Both installation-cd-base.nix and netboot.nix set fileSystems."/" via # the identical lib.mkImageMediaOverride (mkOverride 60) priority -- # genuinely conflicting root-filesystem strategies (ISO-by-label vs. # netboot-tmpfs) at the same priority, and the ISO one was winning. # netboot-minimal.nix's own chain (netboot-base.nix) already imports # profiles/installation-device.nix independently, so common.nix's # initialHashedPassword override (which assumes that profile is # present) still applies correctly without iso.nix in the mix. # # networking.hostName is set explicitly (rather than left at nixpkgs' # own "nixos" default) so this image's generated system name # (nixos-system-auto-installer-*) matches its iPXE menu entry — # see modules/build-types/pxe-boot.nix's :auto-installer item — and # its staged directory, /srv/pxe/http/auto-installer. netbootSystem = nixpkgs.lib.nixosSystem { inherit system; modules = [ ./modules/installer/common.nix ({ modulesPath, ... }: { imports = [ (modulesPath + "/installer/netboot/netboot-minimal.nix") ]; }) { networking.hostName = "auto-installer"; } ]; specialArgs = { inherit vars; }; }; # A genuinely vanilla NixOS minimal netboot image: nixpkgs' # netboot-minimal.nix on its own, with none of this flake's # auto-installer wiring (no common.nix — no auto-install.sh, no # baked host keys, no custom users/passwords). Built from source via # the same nixosSystem + netboot-minimal.nix path as netbootSystem # above, so both go through an identical build mechanism; the only # difference is what's composed in. hostName again matches this # image's iPXE menu entry (:nixos-minimal) and staged directory # (/srv/pxe/http/nixos-minimal). netbootMinimalSystem = nixpkgs.lib.nixosSystem { inherit system; modules = [ ({ modulesPath, ... }: { imports = [ (modulesPath + "/installer/netboot/netboot-minimal.nix") ]; }) { networking.hostName = "nixos-minimal"; } ]; }; in { nixosConfigurations = generatedTargets // installerTargets; # Buildable auto-installer artifacts (`nix build .#`). No `lxc` # variant (installer-boots-as-an-LXC-container) or `all` bundle # anymore — lxc-* and proxmox-* hosts deploy via their own tarball/ # disk-image outputs instead (see docs/auto-installer.md and # docs/proxmox-images.md), which left the installer's own LXC form # with no real use case: it's excluded from the install menu (same # bind-mount problem as any LXC nixos-install target) and nothing # else needed booting the installer itself as a container. packages.${system} = { iso = installerTargets.installer.config.system.build.isoImage; pxe = pkgs.linkFarm "pxe" [ { name = "netboot.ipxe"; path = netbootSystem.config.system.build.netbootIpxeScript; } { name = "initrd"; path = netbootSystem.config.system.build.netbootRamdisk; } { name = "kernel"; path = netbootSystem.config.system.build.kernel; } ]; # Vanilla NixOS minimal netboot bundle — see netbootMinimalSystem # above. Staged onto the pxe-boot host alongside packages.pxe by # modules/pxe-boot/stage-installer-artifacts.nix. pxe-minimal = pkgs.linkFarm "pxe-minimal" [ { name = "netboot.ipxe"; path = netbootMinimalSystem.config.system.build.netbootIpxeScript; } { name = "initrd"; path = netbootMinimalSystem.config.system.build.netbootRamdisk; } { name = "kernel"; path = netbootMinimalSystem.config.system.build.kernel; } ]; }; }; }