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