ALPHA: wire is a tool to deploy nixos systems
wire.althaea.zone/
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# Copyright 2024-2025 wire Contributors
3
4{
5 lib,
6 name,
7 ...
8}:
9let
10 inherit (lib) types;
11in
12{
13 imports =
14 let
15 inherit (lib) mkAliasOptionModule;
16 in
17 [
18 (mkAliasOptionModule [ "deployment" "targetHost" ] [ "deployment" "target" "hosts" ])
19 (mkAliasOptionModule [ "deployment" "targetUser" ] [ "deployment" "target" "user" ])
20 (mkAliasOptionModule [ "deployment" "targetPort" ] [ "deployment" "target" "port" ])
21 ];
22
23 options.deployment = {
24 target = lib.mkOption {
25 type = types.submodule {
26 imports = [
27 (lib.mkAliasOptionModule [ "host" ] [ "hosts" ])
28 ];
29 options = {
30 hosts = lib.mkOption {
31 type = types.coercedTo types.str lib.singleton (types.listOf types.str);
32 description = "IPs or hostnames to attempt to connect to. They are tried in order.";
33 default = lib.singleton name;
34 apply = lib.unique;
35 };
36 user = lib.mkOption {
37 type = types.str;
38 description = "User to use for SSH. The user must be atleast `wheel` and must use an SSH key or similar
39 non-interactive login method. More information can be found at https://wire.althaea.zone/guides/non-root-user";
40 default = "root";
41 };
42 port = lib.mkOption {
43 type = types.int;
44 default = 22;
45 description = "SSH port to use.";
46 };
47 };
48 };
49 description = "Describes the target for this node";
50 default = { };
51 };
52
53 rollback = lib.mkOption {
54 type = types.bool;
55 default = true;
56 description = "Attempt to rollback this node if it cannot be contacted after activation.";
57 };
58
59 buildOnTarget = lib.mkOption {
60 type = types.bool;
61 default = false;
62 description = "Whether to build the system on the target host or not.";
63 };
64
65 allowLocalDeployment = lib.mkOption {
66 type = types.bool;
67 default = true;
68 description = "Whether to allow or deny this node being applied to localhost when the host's hostname matches the
69 node's name.";
70 };
71
72 tags = lib.mkOption {
73 type = types.listOf types.str;
74 default = [ ];
75 description = "Tags for node.";
76 example = [
77 "arm"
78 "cloud"
79 ];
80 };
81
82 privilegeEscalationCommand = lib.mkOption {
83 type = types.listOf types.str;
84 description = "Command to elevate.";
85 default = [
86 "sudo"
87 "--"
88 ];
89 };
90
91 replaceUnknownProfiles = lib.mkOption {
92 type = types.bool;
93 description = "No-op, colmena compatibility";
94 default = true;
95 };
96
97 sshOptions = lib.mkOption {
98 type = types.listOf types.str;
99 description = "No-op, colmena compatibility";
100 default = [ ];
101 };
102
103 _keys = lib.mkOption {
104 internal = true;
105 readOnly = true;
106 };
107
108 _hostPlatform = lib.mkOption {
109 internal = true;
110 readOnly = true;
111 };
112
113 keys = lib.mkOption {
114 type = types.attrsOf (
115 types.submodule (
116 {
117 name,
118 config,
119 ...
120 }:
121 {
122 imports =
123 let
124 inherit (lib) mkAliasOptionModule;
125 in
126 [
127 (mkAliasOptionModule [ "keyFile" ] [ "source" ])
128 (mkAliasOptionModule [ "keyCommand" ] [ "source" ])
129 (mkAliasOptionModule [ "text" ] [ "source" ])
130 ];
131 options = {
132 name = lib.mkOption {
133 type = types.str;
134 default = name;
135 description = "Filename of the secret.";
136 };
137 destDir = lib.mkOption {
138 type = types.path;
139 default = "/run/keys/";
140 description = "Destination directory for the secret. Change this to something other than `/run/keys/` for keys to persist past reboots.";
141 };
142 path = lib.mkOption {
143 internal = true;
144 type = types.path;
145 default =
146 if lib.hasSuffix "/" config.destDir then
147 "${config.destDir}${config.name}"
148 else
149 "${config.destDir}/${config.name}";
150 description = "Path that the key is deployed to.";
151 };
152 service = lib.mkOption {
153 internal = true;
154 type = types.str;
155 default = "${config.name}-key.service";
156 description = "Name of the systemd service that represents this key.";
157 };
158 group = lib.mkOption {
159 type = types.str;
160 default = "root";
161 description = "Group to own the key. If this group does not exist this will silently fail and the key will be owned by gid 0.";
162 };
163 user = lib.mkOption {
164 type = types.str;
165 default = "root";
166 description = "User to own the key. If this user does not exist this will silently fail and the key will be owned by uid 0.";
167 };
168 permissions = lib.mkOption {
169 type = types.str;
170 default = "0600";
171 description = "Unix Octal permissions, in string format, for the key.";
172 };
173 source = lib.mkOption {
174 type = types.oneOf [
175 types.str
176 types.path
177 (types.listOf types.str)
178 ];
179 description = "Source of the key. Either a path to a file, a literal string, or a command to generate the key.";
180 };
181 uploadAt = lib.mkOption {
182 type = types.enum [
183 "pre-activation"
184 "post-activation"
185 ];
186 default = "pre-activation";
187 description = "When to upload the key. Either `pre-activation` or `post-activation`.";
188 };
189 environment = lib.mkOption {
190 type = types.attrsOf types.str;
191 default = { };
192 description = "Key-Value environment variables to use when creating the key if the key source is a command.";
193 };
194 };
195 }
196 )
197 );
198 description = "Secrets to be deployed to the node.";
199 default = { };
200 example = {
201 "wireless.env" = {
202 source = [
203 "gpg"
204 "--decrypt"
205 "secrets/wireless.env.gpg"
206 ];
207 destDir = "/etc/keys/";
208 };
209
210 "arbfile.txt" = {
211 source = ./arbfile.txt;
212 destDir = "/etc/arbs/";
213 };
214
215 "arberfile.txt" = {
216 source = ''
217 Hello World
218 '';
219 destDir = "/etc/arbs/";
220 };
221 };
222 };
223 };
224}