From 8c4dc9cb748644fef954b9cafa7e5d26f3cdaf36 Mon Sep 17 00:00:00 2001 From: Zuckerberg Date: Sat, 29 Mar 2025 22:48:14 -0700 Subject: [PATCH] Improve usage of roles. It should be much easier to read and use now. --- common/default.nix | 4 +- common/machine-info/default.nix | 191 +++++++++++++------------ common/machine-info/roles.nix | 42 +++++- common/machine-info/ssh.nix | 2 +- common/nix-builder.nix | 20 ++- common/server/gitea-actions-runner.nix | 5 +- flake.nix | 11 +- home/googlebot.nix | 5 +- 8 files changed, 152 insertions(+), 128 deletions(-) diff --git a/common/default.nix b/common/default.nix index 7aa02b5..d6d3e5d 100644 --- a/common/default.nix +++ b/common/default.nix @@ -100,7 +100,5 @@ security.acme.defaults.email = "zuckerberg@neet.dev"; # Enable Desktop Environment if this is a PC (machine role is "personal") - de.enable = ( - builtins.elem config.networking.hostName config.machines.roles.personal - ); + de.enable = lib.mkDefault (config.thisMachine.hasRole."personal"); } diff --git a/common/machine-info/default.nix b/common/machine-info/default.nix index 67c2df8..13b36db 100644 --- a/common/machine-info/default.nix +++ b/common/machine-info/default.nix @@ -5,6 +5,90 @@ let machines = config.machines.hosts; + + hostOptionsSubmoduleType = lib.types.submodule { + options = { + hostNames = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = '' + List of hostnames for this machine. The first one is the default so it is the target of deployments. + Used for automatically trusting hosts for ssh connections. + ''; + }; + arch = lib.mkOption { + type = lib.types.enum [ "x86_64-linux" "aarch64-linux" ]; + description = '' + The architecture of this machine. + ''; + }; + systemRoles = lib.mkOption { + type = lib.types.listOf lib.types.str; # TODO: maybe use an enum? + description = '' + The set of roles this machine holds. Affects secrets available. (TODO add service config as well using this info) + ''; + }; + hostKey = lib.mkOption { + type = lib.types.str; + description = '' + The system ssh host key of this machine. Used for automatically trusting hosts for ssh connections + and for decrypting secrets with agenix. + ''; + }; + remoteUnlock = lib.mkOption { + default = null; + type = lib.types.nullOr (lib.types.submodule { + options = { + + hostKey = lib.mkOption { + type = lib.types.str; + description = '' + The system ssh host key of this machine used for luks boot unlocking only. + ''; + }; + + clearnetHost = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; + description = '' + The hostname resolvable over clearnet used to luks boot unlock this machine + ''; + }; + + onionHost = lib.mkOption { + default = null; + type = lib.types.nullOr lib.types.str; + description = '' + The hostname resolvable over tor used to luks boot unlock this machine + ''; + }; + + }; + }); + }; + userKeys = lib.mkOption { + default = [ ]; + type = lib.types.listOf lib.types.str; + description = '' + The list of user keys. Each key here can be used to log into all other systems as `googlebot`. + + TODO: consider auto populating other programs that use ssh keys such as gitea + ''; + }; + deployKeys = lib.mkOption { + default = [ ]; + type = lib.types.listOf lib.types.str; + description = '' + The list of deployment keys. Each key here can be used to log into all other systems as `root`. + ''; + }; + configurationPath = lib.mkOption { + type = lib.types.path; + description = '' + The path to this machine's configuration directory. + ''; + }; + }; + }; in { imports = [ @@ -13,104 +97,16 @@ in ]; options.machines = { - hosts = lib.mkOption { - type = lib.types.attrsOf - (lib.types.submodule { - options = { - - hostNames = lib.mkOption { - type = lib.types.listOf lib.types.str; - description = '' - List of hostnames for this machine. The first one is the default so it is the target of deployments. - Used for automatically trusting hosts for ssh connections. - ''; - }; - - arch = lib.mkOption { - type = lib.types.enum [ "x86_64-linux" "aarch64-linux" ]; - description = '' - The architecture of this machine. - ''; - }; - - systemRoles = lib.mkOption { - type = lib.types.listOf lib.types.str; # TODO: maybe use an enum? - description = '' - The set of roles this machine holds. Affects secrets available. (TODO add service config as well using this info) - ''; - }; - - hostKey = lib.mkOption { - type = lib.types.str; - description = '' - The system ssh host key of this machine. Used for automatically trusting hosts for ssh connections - and for decrypting secrets with agenix. - ''; - }; - - remoteUnlock = lib.mkOption { - default = null; - type = lib.types.nullOr (lib.types.submodule { - options = { - - hostKey = lib.mkOption { - type = lib.types.str; - description = '' - The system ssh host key of this machine used for luks boot unlocking only. - ''; - }; - - clearnetHost = lib.mkOption { - default = null; - type = lib.types.nullOr lib.types.str; - description = '' - The hostname resolvable over clearnet used to luks boot unlock this machine - ''; - }; - - onionHost = lib.mkOption { - default = null; - type = lib.types.nullOr lib.types.str; - description = '' - The hostname resolvable over tor used to luks boot unlock this machine - ''; - }; - - }; - }); - }; - - userKeys = lib.mkOption { - default = [ ]; - type = lib.types.listOf lib.types.str; - description = '' - The list of user keys. Each key here can be used to log into all other systems as `googlebot`. - - TODO: consider auto populating other programs that use ssh keys such as gitea - ''; - }; - - deployKeys = lib.mkOption { - default = [ ]; - type = lib.types.listOf lib.types.str; - description = '' - The list of deployment keys. Each key here can be used to log into all other systems as `root`. - ''; - }; - - configurationPath = lib.mkOption { - type = lib.types.path; - description = '' - The path to this machine's configuration directory. - ''; - }; - - }; - }); + type = lib.types.attrsOf hostOptionsSubmoduleType; }; }; + options.thisMachine.config = lib.mkOption { + # For ease of use, a direct copy of the host config from machines.hosts.${hostName} + type = hostOptionsSubmoduleType; + }; + config = { assertions = (lib.concatLists (lib.mapAttrsToList ( @@ -196,5 +192,12 @@ in builtins.map (p: { "${dirName p}" = p; }) propFiles; in properties ../../machines; + + # Don't try to evaluate "thisMachine" when reflecting using moduleless.nix. + # When evaluated by moduleless.nix this will fail due to networking.hostName not + # existing. This is because moduleless.nix is not intended for reflection from the + # perspective of a perticular machine but is instead intended for reflecting on + # the properties of all machines as a whole system. + thisMachine.config = config.machines.hosts.${config.networking.hostName}; }; } diff --git a/common/machine-info/roles.nix b/common/machine-info/roles.nix index a491c73..bc6976f 100644 --- a/common/machine-info/roles.nix +++ b/common/machine-info/roles.nix @@ -1,19 +1,55 @@ { config, lib, ... }: -# Maps roles to their hosts +# Maps roles to their hosts. +# machines.withRole = { +# personal = [ +# "machine1" "machine3" +# ]; +# cache = [ +# "machine2" +# ]; +# }; +# +# A list of all possible roles +# machines.allRoles = [ +# "personal" +# "cache" +# ]; +# +# For each role has true or false if the current machine has that role +# thisMachine.hasRole = { +# personal = true; +# cache = false; +# }; { - options.machines.roles = lib.mkOption { + options.machines.withRole = lib.mkOption { type = lib.types.attrsOf (lib.types.listOf lib.types.str); }; + options.machines.allRoles = lib.mkOption { + type = lib.types.listOf lib.types.str; + }; + + options.thisMachine.hasRole = lib.mkOption { + type = lib.types.attrsOf lib.types.bool; + }; + config = { - machines.roles = lib.zipAttrs + machines.withRole = lib.zipAttrs (lib.mapAttrsToList (host: cfg: lib.foldl (lib.mergeAttrs) { } (builtins.map (role: { ${role} = host; }) cfg.systemRoles)) config.machines.hosts); + + machines.allRoles = lib.attrNames config.machines.withRole; + + thisMachine.hasRole = lib.mapAttrs + (role: cfg: + builtins.elem config.networking.hostName config.machines.withRole.${role} + ) + config.machines.withRole; }; } diff --git a/common/machine-info/ssh.nix b/common/machine-info/ssh.nix index b7775d4..b3ad908 100644 --- a/common/machine-info/ssh.nix +++ b/common/machine-info/ssh.nix @@ -39,6 +39,6 @@ in builtins.map (host: machines.hosts.${host}.hostKey) hosts) - machines.roles; + machines.withRole; }; } diff --git a/common/nix-builder.nix b/common/nix-builder.nix index b3849e5..64ea1a3 100644 --- a/common/nix-builder.nix +++ b/common/nix-builder.nix @@ -1,18 +1,14 @@ { config, lib, ... }: let - builderRole = "nix-builder"; builderUserName = "nix-builder"; - machinesByRole = role: lib.filterAttrs (hostname: cfg: builtins.elem role cfg.systemRoles) config.machines.hosts; - otherMachinesByRole = role: lib.filterAttrs (hostname: cfg: hostname != config.networking.hostName) (machinesByRole role); - thisMachineHasRole = role: builtins.hasAttr config.networking.hostName (machinesByRole role); - - builders = machinesByRole builderRole; - thisMachineIsABuilder = thisMachineHasRole builderRole; + builderRole = "nix-builder"; + builders = config.machines.withRole.${builderRole}; + thisMachineIsABuilder = config.thisMachine.hasRole.${builderRole}; # builders don't include themselves as a remote builder - otherBuilders = lib.filterAttrs (hostname: cfg: hostname != config.networking.hostName) builders; + otherBuilders = lib.filter (hostname: hostname != config.networking.hostName) builders; in lib.mkMerge [ # configure builder @@ -40,9 +36,9 @@ lib.mkMerge [ nix.distributedBuilds = true; nix.buildMachines = builtins.map - (builderCfg: { - hostName = builtins.elemAt builderCfg.hostNames 0; - system = builderCfg.arch; + (builderHostname: { + hostName = builderHostname; + system = config.machines.hosts.${builderHostname}.arch; protocol = "ssh-ng"; sshUser = builderUserName; sshKey = "/etc/ssh/ssh_host_ed25519_key"; @@ -50,7 +46,7 @@ lib.mkMerge [ speedFactor = 10; supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ]; }) - (builtins.attrValues otherBuilders); + otherBuilders; # It is very likely that the builder's internet is faster or just as fast nix.extraOptions = '' diff --git a/common/server/gitea-actions-runner.nix b/common/server/gitea-actions-runner.nix index 0dd1595..24c35e9 100644 --- a/common/server/gitea-actions-runner.nix +++ b/common/server/gitea-actions-runner.nix @@ -9,10 +9,7 @@ # TODO: skipping running inside of nixos container for now because of issues getting docker/podman running let - runnerRole = "gitea-actions-runner"; - runners = config.machines.roles.${runnerRole}; - thisMachineIsARunner = builtins.elem config.networking.hostName runners; - + thisMachineIsARunner = config.thisMachine.hasRole."gitea-actions-runner"; containerName = "gitea-runner"; in { diff --git a/flake.nix b/flake.nix index 5fc6d10..b0d4c79 100644 --- a/flake.nix +++ b/flake.nix @@ -84,13 +84,11 @@ outputs = { self, nixpkgs, ... }@inputs: let - machines = (import ./common/machine-info/moduleless.nix + machineHosts = (import ./common/machine-info/moduleless.nix { inherit nixpkgs; assertionsModule = "${nixpkgs}/nixos/modules/misc/assertions.nix"; - }).machines; - machineHosts = machines.hosts; - machineRoles = machines.roles; + }).machines.hosts; in { nixosConfigurations = @@ -115,10 +113,7 @@ home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; - home-manager.users.googlebot = import ./home/googlebot.nix { - inherit hostname; - inherit machineRoles; - }; + home-manager.users.googlebot = import ./home/googlebot.nix; }; # because nixos specialArgs doesn't work for containers... need to pass in inputs a different way diff --git a/home/googlebot.nix b/home/googlebot.nix index 667b0a4..1f81157 100644 --- a/home/googlebot.nix +++ b/home/googlebot.nix @@ -1,9 +1,8 @@ -{ hostname, machineRoles }: -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, osConfig, ... }: let # Check if the current machine has the role "personal" - thisMachineIsPersonal = builtins.elem hostname machineRoles.personal; + thisMachineIsPersonal = osConfig.thisMachine.hasRole."personal"; in { home.username = "googlebot";