1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.cloud-init;
9 path =
10 with pkgs;
11 [
12 cloud-init
13 iproute2
14 nettools
15 openssh
16 shadow
17 util-linux
18 busybox
19 ]
20 ++ lib.optional cfg.btrfs.enable btrfs-progs
21 ++ lib.optional cfg.ext4.enable e2fsprogs
22 ++ lib.optional cfg.xfs.enable xfsprogs
23 ++ cfg.extraPackages;
24 hasFs = fsName: lib.any (fs: fs.fsType == fsName) (lib.attrValues config.fileSystems);
25 settingsFormat = pkgs.formats.yaml { };
26 cfgfile = settingsFormat.generate "cloud.cfg" cfg.settings;
27in
28{
29 options = {
30 services.cloud-init = {
31 enable = lib.mkOption {
32 type = lib.types.bool;
33 default = false;
34 description = ''
35 Enable the cloud-init service. This services reads
36 configuration metadata in a cloud environment and configures
37 the machine according to this metadata.
38
39 This configuration is not completely compatible with the
40 NixOS way of doing configuration, as configuration done by
41 cloud-init might be overridden by a subsequent nixos-rebuild
42 call. However, some parts of cloud-init fall outside of
43 NixOS's responsibility, like filesystem resizing and ssh
44 public key provisioning, and cloud-init is useful for that
45 parts. Thus, be wary that using cloud-init in NixOS might
46 come as some cost.
47 '';
48 };
49
50 btrfs.enable = lib.mkOption {
51 type = lib.types.bool;
52 default = hasFs "btrfs";
53 defaultText = lib.literalExpression ''hasFs "btrfs"'';
54 description = ''
55 Allow the cloud-init service to operate `btrfs` filesystem.
56 '';
57 };
58
59 ext4.enable = lib.mkOption {
60 type = lib.types.bool;
61 default = hasFs "ext4";
62 defaultText = lib.literalExpression ''hasFs "ext4"'';
63 description = ''
64 Allow the cloud-init service to operate `ext4` filesystem.
65 '';
66 };
67
68 xfs.enable = lib.mkOption {
69 type = lib.types.bool;
70 default = hasFs "xfs";
71 defaultText = lib.literalExpression ''hasFs "xfs"'';
72 description = ''
73 Allow the cloud-init service to operate `xfs` filesystem.
74 '';
75 };
76
77 network.enable = lib.mkOption {
78 type = lib.types.bool;
79 default = false;
80 description = ''
81 Allow the cloud-init service to configure network interfaces
82 through systemd-networkd.
83 '';
84 };
85
86 extraPackages = lib.mkOption {
87 type = lib.types.listOf lib.types.package;
88 default = [ ];
89 description = ''
90 List of additional packages to be available within cloud-init jobs.
91 '';
92 };
93
94 settings = lib.mkOption {
95 description = ''
96 Structured cloud-init configuration.
97 '';
98 type = lib.types.submodule {
99 freeformType = settingsFormat.type;
100 };
101 default = { };
102 };
103
104 config = lib.mkOption {
105 type = lib.types.str;
106 default = "";
107 description = ''
108 raw cloud-init configuration.
109
110 Takes precedence over the `settings` option if set.
111 '';
112 };
113
114 };
115
116 };
117
118 config = lib.mkIf cfg.enable {
119 services.cloud-init.settings = {
120 system_info = lib.mkDefault {
121 distro = "nixos";
122 network = {
123 renderers = [ "networkd" ];
124 };
125 };
126
127 users = lib.mkDefault [ "root" ];
128 disable_root = lib.mkDefault false;
129 preserve_hostname = lib.mkDefault false;
130
131 cloud_init_modules = lib.mkDefault [
132 "migrator"
133 "seed_random"
134 "bootcmd"
135 "write-files"
136 "growpart"
137 "resizefs"
138 "update_hostname"
139 "resolv_conf"
140 "ca-certs"
141 "rsyslog"
142 "users-groups"
143 ];
144
145 cloud_config_modules = lib.mkDefault [
146 "disk_setup"
147 "mounts"
148 "ssh-import-id"
149 "set-passwords"
150 "timezone"
151 "disable-ec2-metadata"
152 "runcmd"
153 "ssh"
154 ];
155
156 cloud_final_modules = lib.mkDefault [
157 "rightscale_userdata"
158 "scripts-vendor"
159 "scripts-per-once"
160 "scripts-per-boot"
161 "scripts-per-instance"
162 "scripts-user"
163 "ssh-authkey-fingerprints"
164 "keys-to-console"
165 "phone-home"
166 "final-message"
167 "power-state-change"
168 ];
169 };
170
171 environment.etc."cloud/cloud.cfg" =
172 if cfg.config == "" then { source = cfgfile; } else { text = cfg.config; };
173
174 systemd.network.enable = lib.mkIf cfg.network.enable true;
175
176 systemd.services.cloud-init-local = {
177 description = "Initial cloud-init job (pre-networking)";
178 wantedBy = [ "multi-user.target" ];
179 # In certain environments (AWS for example), cloud-init-local will
180 # first configure an IP through DHCP, and later delete it.
181 # This can cause race conditions with anything else trying to set IP through DHCP.
182 before = [
183 "systemd-networkd.service"
184 "dhcpcd.service"
185 ];
186 path = path;
187 serviceConfig = {
188 Type = "oneshot";
189 ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
190 RemainAfterExit = "yes";
191 TimeoutSec = "infinity";
192 StandardOutput = "journal+console";
193 };
194 };
195
196 systemd.services.cloud-init = {
197 description = "Initial cloud-init job (metadata service crawler)";
198 wantedBy = [ "multi-user.target" ];
199 wants = [
200 "network-online.target"
201 "cloud-init-local.service"
202 "sshd.service"
203 "sshd-keygen.service"
204 ];
205 after = [
206 "network-online.target"
207 "cloud-init-local.service"
208 ];
209 before = [
210 "sshd.service"
211 "sshd-keygen.service"
212 ];
213 requires = [ "network.target" ];
214 path = path;
215 serviceConfig = {
216 Type = "oneshot";
217 ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
218 RemainAfterExit = "yes";
219 TimeoutSec = "infinity";
220 StandardOutput = "journal+console";
221 };
222 };
223
224 systemd.services.cloud-config = {
225 description = "Apply the settings specified in cloud-config";
226 wantedBy = [ "multi-user.target" ];
227 wants = [ "network-online.target" ];
228 after = [
229 "network-online.target"
230 "cloud-config.target"
231 ];
232
233 path = path;
234 serviceConfig = {
235 Type = "oneshot";
236 ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
237 RemainAfterExit = "yes";
238 TimeoutSec = "infinity";
239 StandardOutput = "journal+console";
240 };
241 };
242
243 systemd.services.cloud-final = {
244 description = "Execute cloud user/final scripts";
245 wantedBy = [ "multi-user.target" ];
246 wants = [ "network-online.target" ];
247 after = [
248 "network-online.target"
249 "cloud-config.service"
250 "rc-local.service"
251 ];
252 requires = [ "cloud-config.target" ];
253 path = path;
254 serviceConfig = {
255 Type = "oneshot";
256 ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
257 RemainAfterExit = "yes";
258 TimeoutSec = "infinity";
259 StandardOutput = "journal+console";
260 };
261 };
262
263 systemd.targets.cloud-config = {
264 description = "Cloud-config availability";
265 requires = [
266 "cloud-init-local.service"
267 "cloud-init.service"
268 ];
269 };
270 };
271
272 meta.maintainers = [ lib.maintainers.zimbatm ];
273}