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