{ nixpkgs, overlays, inputs, }: name: { system, user ? null, features ? [ ], }: let inherit (nixpkgs) lib; hasUser = user != null; # path helpers featurePath = f: let file = ../features/${f}.nix; dir = ../features/${f}; in if builtins.pathExists file then file else dir; userFeaturePath = u: ../features/user-${u}.nix; hostConfig = ../hosts/${name}/configuration.nix; hostHWConfig = ../hosts/${name}/hardware-configuration.nix; # auto-discover all features, excluding user-* and default.nix featureDir = builtins.readDir ../features; allFeatureNames = lib.pipe featureDir [ (lib.filterAttrs ( n: t: (t == "regular" && lib.hasSuffix ".nix" n && n != "default.nix" && !lib.hasPrefix "user-" n) || (t == "directory" && builtins.pathExists ../features/${n}/default.nix) )) builtins.attrNames (map (n: lib.removeSuffix ".nix" n)) ]; # load all features unconditionally loadFeature = f: assert builtins.pathExists (featurePath f) || throw "feature '${f}' not found at ${toString (featurePath f)}"; import (featurePath f); loadedFeatures = map loadFeature allFeatureNames; # load user feature with path check userFeature = if hasUser then assert builtins.pathExists (userFeaturePath user) || throw "user feature 'user-${user}' not found at ${toString (userFeaturePath user)}"; import (userFeaturePath user) else null; allFeatures = loadedFeatures ++ lib.optional (userFeature != null) userFeature; # extract keys from user feature for specialArgs userKeys = if userFeature != null then (userFeature.keys or { }) else { }; # collect nixos and home modules from all features nixosMods = map (f: f.nixos) (builtins.filter (f: f ? nixos) allFeatures); homeMods = map (f: f.home) (builtins.filter (f: f ? home) allFeatures); # translate features list to enable flags featureEnableModule = { lib, ... }: { config.features = lib.genAttrs features (_: { enable = true; }); }; in nixpkgs.lib.nixosSystem { inherit system; modules = [ inputs.sops-nix.nixosModules.sops inputs.stylix.nixosModules.stylix { nixpkgs.overlays = overlays; } { nixpkgs.config.allowUnfree = true; } { networking.hostName = name; } # TEMP:(@janezicmatej) temporary mitigation for dirty frag # blocks esp4/esp6 (CVE-2026-43284) and rxrpc (CVE-2026-43500) # remove once nixpkgs ships a kernel with f4c50a4034e6 and the rxrpc fix { boot.blacklistedKernelModules = [ "esp4" "esp6" "rxrpc" ]; } # cap unit stop timeout so a single misbehaving app (electron, etc) can't # block poweroff for the full 90s default. user-scope cap is required for # session-N.scope to honor it. see discourse/49711 { systemd.settings.Manager.DefaultTimeoutStopSec = "10s"; systemd.user.extraConfig = "DefaultTimeoutStopSec=10s"; } featureEnableModule hostConfig ] ++ lib.optional (builtins.pathExists hostHWConfig) hostHWConfig ++ nixosMods ++ lib.optionals hasUser [ inputs.home-manager.nixosModules.home-manager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.backupFileExtension = "backup"; home-manager.users.${user}.imports = homeMods; home-manager.extraSpecialArgs = { inherit inputs; }; } ]; specialArgs = { inherit inputs userKeys user; }; }