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