kore / nixos / features / kore.nix151 lines · 4484B · 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 aliases = mkOption {
54 type = types.listOf types.str;
55 default = [];
56 };
57 ipv4 = mkOption {
58 type = types.nullOr types.str;
59 default = null;
60 };
61 };
62
63 repoOptions = with lib; {
64 write = mkOption {
65 type = types.listOf types.str;
66 default = [];
67 };
68 read = mkOption {
69 type = types.listOf types.str;
70 default = [];
71 };
72 };
73
74 hostNames = name: host:
75 let names = [ name ] ++ host.aliases;
76 in map (n: "${n}.${config.networking.domain}") names ++ names;
77in {
78 options.kore = with lib; {
79 identities = mkOption {
80 type = types.attrsOf (types.submodule identityOptions);
81 default = {};
82 };
83 hosts = mkOption {
84 type = types.attrsOf (types.submodule [{ options = hostOptions; }]);
85 default = {};
86 };
87 currentHost = mkOption {
88 type = types.submodule [{ options = hostOptions; }];
89 };
90 repos = mkOption {
91 type = types.attrsOf (types.submodule [{ options = repoOptions; }]);
92 default = {};
93 };
94
95 impermanence.enable = mkOption {
96 type = types.bool;
97 default = true;
98 };
99 };
100
101 config = {
102 kore = {
103 identities = kore.identities or {};
104 hosts = kore.hosts;
105 currentHost = kore.hosts.${hostname};
106 repos = kore.repos or {};
107 };
108
109 assertions = map (user: {
110 assertion = cfg.identities ? ${user};
111 message = "kore: ${hostname} has user ${user}, but kore.json has no identity for it";
112 }) cfg.currentHost.users
113 ++ lib.concatLists (lib.mapAttrsToList (repo: acl: [{
114 assertion = builtins.match "[A-Za-z0-9_][A-Za-z0-9._-]*(/[A-Za-z0-9_][A-Za-z0-9._-]*)*" repo != null
115 && !(lib.hasSuffix ".git" repo);
116 message = "kore: repo name ${repo} is invalid";
117 }] ++ map (id: {
118 assertion = cfg.identities ? ${id};
119 message = "kore: repo ${repo} lists ${id}, but kore.json has no identity for it";
120 }) (acl.write ++ acl.read)) cfg.repos);
121
122 nixpkgs.hostPlatform = cfg.currentHost.system;
123 system.stateVersion = cfg.currentHost.stateVersion;
124
125 users.users = lib.listToAttrs (lib.imap0 (i: user: lib.nameValuePair user {
126 isNormalUser = true;
127 password = "tmp";
128 extraGroups = [ "wheel" ];
129
130 createHome = hasHome;
131 home = if hasHome then "/home/${user}" else "/var/empty";
132
133 subUidRanges = [{ startUid = 100000 + i * 65536; count = 65536; }];
134 subGidRanges = [{ startGid = 100000 + i * 65536; count = 65536; }];
135
136 openssh.authorizedKeys.keys = cfg.identities.${user}.sshKeys or [];
137 }) cfg.currentHost.users);
138
139 # /etc/hosts so machines resolve each other by name.
140 networking.hosts = lib.mkMerge (lib.mapAttrsToList (name: host:
141 lib.optionalAttrs (host.ipv4 != null) {
142 "${host.ipv4}" = hostNames name host;
143 }) cfg.hosts);
144
145 programs.ssh.knownHosts = lib.mapAttrs (name: host: {
146 hostNames = hostNames name host
147 ++ lib.optional (host.ipv4 != null) host.ipv4;
148 publicKeyFile = hostKey name;
149 }) (lib.filterAttrs (name: _: builtins.pathExists (hostKey name)) cfg.hosts);
150 };
151}