Skip to content

Commit b9bb389

Browse files
committed
feat(modules): Add host-info and secrets as nixosModules
1 parent 308c375 commit b9bb389

File tree

3 files changed

+180
-45
lines changed

3 files changed

+180
-45
lines changed

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@
203203
./modules/tailscale-autoconnect
204204
./modules/grafana-agent-flow
205205
./modules/pyroscope
206+
./modules/secrets.nix
207+
./modules/host-info.nix
206208
./packages
207209
];
208210
systems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];

modules/host-info.nix

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,67 @@
1-
{
2-
config,
3-
lib,
4-
...
5-
}: {
6-
options.mcl.host-info = with lib; {
7-
type = mkOption {
8-
type = types.nullOr (types.enum ["desktop" "server"]);
9-
default = null;
10-
example = ["desktop"];
11-
description = ''
12-
Whether this host is a desktop or a server.
13-
'';
14-
};
1+
{withSystem, ...}: {
2+
flake.nixosModules.mcl-host-info = {
3+
config,
4+
lib,
5+
dirs,
6+
...
7+
}: {
8+
options.mcl.host-info = with lib; {
9+
type = mkOption {
10+
type = types.nullOr (types.enum ["desktop" "server" "container"]);
11+
default = null;
12+
example = ["desktop"];
13+
description = ''
14+
Whether this host is a desktop or a server.
15+
'';
16+
};
1517

16-
isVM = mkOption {
17-
type = types.nullOr types.bool;
18-
default = null;
19-
example = ["false"];
20-
description = ''
21-
Whether this configuration is a VM variant.
22-
'';
23-
};
18+
isDebugVM = mkOption {
19+
type = types.nullOr types.bool;
20+
default = null;
21+
example = ["false"];
22+
description = ''
23+
Whether this configuration is a VM variant with extra debug
24+
functionality.
25+
'';
26+
};
27+
28+
configPath = mkOption {
29+
type = types.nullOr types.path;
30+
default = null;
31+
example = ["machines/server/solunska-server"];
32+
description = ''
33+
The configuration path for this host relative to the repo root.
34+
'';
35+
};
2436

25-
configPath = mkOption {
26-
type = types.nullOr types.string;
27-
default = null;
28-
example = ["machines/server/solunska-server"];
29-
description = ''
30-
The configuration path for this host relative to the repo root.
31-
'';
37+
sshKey = mkOption {
38+
type = types.nullOr types.str;
39+
default = null;
40+
example = "ssh-ed25519 AAAAC3Nza";
41+
description = ''
42+
The public ssh key for this host.
43+
'';
44+
};
45+
};
46+
config = {
47+
assertions = [
48+
{
49+
assertion = config.mcl.host-info.type != null;
50+
message = "mcl.host-info.type must be defined for every host";
51+
}
52+
{
53+
assertion = config.mcl.host-info.isDebugVM != null;
54+
message = "mcl.host-info.isDebugVM must be defined for every host";
55+
}
56+
{
57+
assertion = config.mcl.host-info.configPath != null;
58+
message = "mcl.host-info.configPath must be defined for every host";
59+
}
60+
{
61+
assertion = config.mcl.host-info.sshKey != null;
62+
message = "mcl.host-info.sshKey must be defined for every host";
63+
}
64+
];
3265
};
33-
};
34-
config = {
35-
assertions = [
36-
{
37-
assertion = config.mcl.host-info.type != null;
38-
message = "mcl.host-info.type must be defined for every host";
39-
}
40-
{
41-
assertion = config.mcl.host-info.isVM != null;
42-
message = "mcl.host-info.isVM must be defined for every host";
43-
}
44-
{
45-
assertion = config.mcl.host-info.configPath != null;
46-
message = "mcl.host-info.configPath must be defined for every host";
47-
}
48-
];
4966
};
5067
}

modules/secrets.nix

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{withSystem, ...}: {
2+
flake.nixosModules.mcl-secrets = {
3+
config,
4+
options,
5+
lib,
6+
dirs,
7+
mcl-modules,
8+
...
9+
}: let
10+
eachServiceCfg = config.mcl.secrets.services;
11+
isDebugVM = config.mcl.host-info.isDebugVM;
12+
13+
sshKey = config.mcl.host-info.sshKey;
14+
15+
ageSecretOpts =
16+
builtins.head
17+
(builtins.head
18+
options.age.secrets.type.nestedTypes.elemType.getSubModules)
19+
.imports;
20+
21+
secretDir = let
22+
machineConfigPath = config.mcl.host-info.configPath;
23+
machineSecretDir = machineConfigPath + "/secrets";
24+
vmConfig = dirs.modules + "/default-vm-config";
25+
vmSecretDir = vmConfig + "/secrets";
26+
in
27+
if isDebugVM
28+
then vmSecretDir
29+
else machineSecretDir;
30+
in {
31+
options.mcl.secrets = with lib; {
32+
services = mkOption {
33+
type = types.attrsOf (types.submodule ({config, ...}: let
34+
serviceName = config._module.args.name;
35+
in {
36+
options = {
37+
encryptedSecretDir = mkOption {
38+
type = types.path;
39+
default = secretDir;
40+
};
41+
secrets = mkOption {
42+
default = {};
43+
type = types.attrsOf (types.submoduleWith {
44+
modules = [
45+
ageSecretOpts
46+
({name, ...}: let
47+
secretName = name;
48+
in {
49+
config = {
50+
name = "${serviceName}/${secretName}";
51+
file =
52+
lib.mkDefault (config.encryptedSecretDir + "/${serviceName}/${secretName}.age");
53+
};
54+
})
55+
];
56+
});
57+
};
58+
extraGroups = mkOption {
59+
type = types.listOf types.str;
60+
default = [];
61+
example = ["devops" "secretsAccess"];
62+
description = "Groups which have access to decrypt the secrets.";
63+
};
64+
extraKeys = mkOption {
65+
type = types.listOf types.str;
66+
default = [];
67+
example = ["ssh-ed25519 AAAAC3Nza" "ssh-ed25519 AAAACSNss"];
68+
description = "Groups which have access to decrypt the secrets.";
69+
};
70+
nix-file = mkOption {
71+
default =
72+
if (pathIsRegularFile (config.encryptedSecretDir + "/${serviceName}/secrets.nix"))
73+
then config.encryptedSecretDir + "/${serviceName}/secrets.nix"
74+
else
75+
builtins.toFile "${serviceName}-secrets.nix" ''
76+
let
77+
hostKey = ["${sshKey}"];
78+
groupsKeys = ["${concatStringsSep "\"\"" (mcl-modules.libs.utils.allUserKeysForGroup config.extraGroups)}"];
79+
extraKeys = ["${concatStringsSep "\"\"" config.extraKeys}"];
80+
in {
81+
${concatMapStringsSep "\n"
82+
(n: "\"${n}\".publicKeys = hostKey ++ groupKeys ++ extraKeys;")
83+
(builtins.attrNames config.secrets)}
84+
}
85+
'';
86+
type = types.path;
87+
};
88+
};
89+
}));
90+
default = {};
91+
example = {
92+
service1.secrets.secretA = {};
93+
service1.secrets.secretB = {};
94+
service2.secrets.secretC = {};
95+
cachix-deploy.secrets.token = {
96+
path = "/etc/cachix-agent.token";
97+
};
98+
};
99+
description = mdDoc "Per-service attrset of encryptedSecretDir and secrets";
100+
};
101+
};
102+
103+
config = lib.mkIf (eachServiceCfg != {}) {
104+
age.secrets = lib.pipe eachServiceCfg [
105+
(lib.mapAttrsToList (serviceName: service:
106+
lib.mapAttrsToList (
107+
secretName: config:
108+
lib.nameValuePair "${serviceName}/${secretName}" config
109+
)
110+
service.secrets))
111+
lib.concatLists
112+
lib.listToAttrs
113+
];
114+
};
115+
};
116+
}

0 commit comments

Comments
 (0)