greenhouse

kore

my public infrastructure monorepo

git clone [email protected]:kore.git

kore / nixos / features / kore.nix120 lines · 3551B · raw

1{ config, lib, kore, ... }:
2
3let
4  cfg = config.kore;
5  hostname = config.networking.hostName;
6  hasHome = lib.elem "home" cfg.currentHost.roles;
7  hostKey = name: ../../keys/hosts + "/${name}.pub";
8
9  identityOptions = { name, ... }:
10    let
11      dir = ../../keys/identities + "/${name}";
12      sshDir = dir + "/ssh";
13      gpgFile = dir + "/gpg.asc";
14
15      sshFiles = lib.optionals (builtins.pathExists sshDir)
16        (lib.filter (lib.hasSuffix ".pub") (lib.attrNames (builtins.readDir sshDir)));
17    in {
18      options = with lib; {
19        email = mkOption {
20          type = types.nullOr types.str;
21          default = null;
22        };
23        gpg = mkOption {
24          type = types.nullOr types.str;
25          default = null;
26        };
27        sshKeys = mkOption {
28          type = types.listOf types.str;
29          default = map (file: lib.fileContents (sshDir + "/${file}")) sshFiles;
30        };
31        gpgKey = mkOption {
32          type = types.nullOr types.path;
33          default = if builtins.pathExists gpgFile then gpgFile else null;
34        };
35      };
36    };
37
38  hostOptions = with lib; {
39    system = mkOption {
40      type = types.str;
41    };
42    stateVersion = mkOption {
43      type = types.str;
44    };
45    roles = mkOption {
46      type = types.listOf types.str;
47      default = [];
48    };
49    users = mkOption {
50      type = types.listOf types.str;
51      default = [];
52    };
53    ipv4 = mkOption {
54      type = types.nullOr types.str;
55      default = null;
56    };
57  };
58in {
59  options.kore = with lib; {
60    identities = mkOption {
61      type = types.attrsOf (types.submodule identityOptions);
62      default = {};
63    };
64    hosts = mkOption {
65      type = types.attrsOf (types.submodule [{ options = hostOptions; }]);
66      default = {};
67    };
68    currentHost = mkOption {
69      type = types.submodule [{ options = hostOptions; }];
70    };
71
72    impermanence.enable = mkOption {
73      type = types.bool;
74      default = true;
75    };
76  };
77
78  config = {
79    kore = {
80      identities = kore.identities or {};
81      hosts = kore.hosts;
82      currentHost = kore.hosts.${hostname};
83    };
84
85    assertions = map (user: {
86      assertion = cfg.identities ? ${user};
87      message = "kore: ${hostname} has user ${user}, but kore.json has no identity for it";
88    }) cfg.currentHost.users;
89
90    nixpkgs.hostPlatform = cfg.currentHost.system;
91    system.stateVersion = cfg.currentHost.stateVersion;
92
93    users.users = lib.listToAttrs (lib.imap0 (i: user: lib.nameValuePair user {
94      isNormalUser = true;
95      password = "tmp";
96      extraGroups = [ "wheel" ];
97
98      # Only hosts with the home role get home folders.
99      createHome = hasHome;
100      home = if hasHome then "/home/${user}" else "/var/empty";
101
102      subUidRanges = [{ startUid = 100000 + i * 65536; count = 65536; }];
103      subGidRanges = [{ startGid = 100000 + i * 65536; count = 65536; }];
104
105      openssh.authorizedKeys.keys = cfg.identities.${user}.sshKeys or [];
106    }) cfg.currentHost.users);
107
108    # /etc/hosts so machines resolve each other by name.
109    networking.hosts = lib.mkMerge (lib.mapAttrsToList (name: host:
110      lib.optionalAttrs (host.ipv4 != null) {
111        "${host.ipv4}" = [ "${name}.${config.networking.domain}" name ];
112      }) cfg.hosts);
113
114    programs.ssh.knownHosts = lib.mapAttrs (name: host: {
115      hostNames = [ name "${name}.${config.networking.domain}" ]
116        ++ lib.optional (host.ipv4 != null) host.ipv4;
117      publicKeyFile = hostKey name;
118    }) (lib.filterAttrs (name: _: builtins.pathExists (hostKey name)) cfg.hosts);
119  };
120}