nixos/ssh: use correct executable for grep in ssh-askpass-wrapper (#373746)
[NixPkgs.git] / nixos / modules / virtualisation / waagent.nix
blobafdcfd6ebe350ae94a804c773d5e716168f5f68c
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 with lib;
9 let
10   cfg = config.services.waagent;
12   # Format for waagent.conf
13   settingsFormat = {
14     type =
15       with types;
16       let
17         singleAtom =
18           (nullOr (oneOf [
19             bool
20             str
21             int
22             float
23           ]))
24           // {
25             description = "atom (bool, string, int or float) or null";
26           };
27         atom = either singleAtom (listOf singleAtom) // {
28           description = singleAtom.description + " or a list of them";
29         };
30       in
31       attrsOf (
32         either atom (attrsOf atom)
33         // {
34           description = atom.description + "or an attribute set of them";
35         }
36       );
37     generate =
38       name: value:
39       let
40         # Transform non-attribute values
41         transform =
42           x:
43           # Transform bool to "y" or "n"
44           if (isBool x) then
45             (if x then "y" else "n")
46           # Concatenate list items with comma
47           else if (isList x) then
48             concatStringsSep "," (map transform x)
49           else
50             toString x;
52         # Convert to format of waagent.conf
53         recurse =
54           path: value:
55           if builtins.isAttrs value then
56             pipe value [
57               (mapAttrsToList (k: v: recurse (path ++ [ k ]) v))
58               concatLists
59             ]
60           else
61             [
62               {
63                 name = concatStringsSep "." path;
64                 inherit value;
65               }
66             ];
67         convert =
68           attrs:
69           pipe (recurse [ ] attrs) [
70             # Filter out null values and emoty lists
71             (filter (kv: kv.value != null && kv.value != [ ]))
72             # Transform to Key=Value form, then concatenate
73             (map (kv: "${kv.name}=${transform kv.value}"))
74             (concatStringsSep "\n")
75           ];
76       in
77       pkgs.writeText name (convert value);
78   };
80   settingsType = types.submodule {
81     freeformType = settingsFormat.type;
82     options = {
83       Provisioning = {
84         Enable = mkOption {
85           type = types.bool;
86           default = !config.services.cloud-init.enable;
87           defaultText = literalExpression "!config.services.cloud-init.enable";
88           description = ''
89             Whether to enable provisioning functionality in the agent.
91             If provisioning is disabled, SSH host and user keys in the image are preserved
92             and configuration in the Azure provisioning API is ignored.
94             Set to `false` if cloud-init is used for provisioning tasks.
95           '';
96         };
98         Agent = mkOption {
99           type = types.enum [
100             "auto"
101             "waagent"
102             "cloud-init"
103             "disabled"
104           ];
105           default = "auto";
106           description = ''
107             Which provisioning agent to use.
108           '';
109         };
110       };
112       ResourceDisk = {
113         Format = mkEnableOption ''
114           If set to `true`, waagent formats and mounts the resource disk that the platform provides,
115           unless the file system type in `ResourceDisk.FileSystem` is set to `ntfs`.
116           The agent makes a single Linux partition (ID 83) available on the disk.
117           This partition isn't formatted if it can be successfully mounted.
119           This configuration has no effect if resource disk is managed by cloud-init.
120         '';
122         FileSystem = mkOption {
123           type = types.str;
124           default = "ext4";
125           description = ''
126             The file system type for the resource disk.
127             If the string is `X`, then `mkfs.X` should be present in the environment.
128             You can add additional filesystem packages using `services.waagent.extraPackages`.
130             This configuration has no effect if resource disk is managed by cloud-init.
131           '';
132         };
134         MountPoint = mkOption {
135           type = types.str;
136           default = "/mnt/resource";
137           description = ''
138             This option specifies the path at which the resource disk is mounted.
139             The resource disk is a temporary disk and might be emptied when the VM is deprovisioned.
141             This configuration has no effect if resource disk is managed by cloud-init.
142           '';
143         };
145         MountOptions = mkOption {
146           type = with types; listOf str;
147           default = [ ];
148           example = [
149             "nodev"
150             "nosuid"
151           ];
152           description = ''
153             This option specifies disk mount options to be passed to the `mount -o` command.
154             For more information, see the `mount(8)` manual page.
155           '';
156         };
158         EnableSwap = mkEnableOption ''
159           If enabled, the agent creates a swap file (`/swapfile`) on the resource disk
160           and adds it to the system swap space.
162           This configuration has no effect if resource disk is managed by cloud-init.
163         '';
165         SwapSizeMB = mkOption {
166           type = types.int;
167           default = 0;
168           description = ''
169             Specifies the size of the swap file in megabytes.
171             This configuration has no effect if resource disk is managed by cloud-init.
172           '';
173         };
174       };
176       Logs.Verbose = lib.mkEnableOption ''
177         If you set this option, log verbosity is boosted.
178         Waagent logs to `/var/log/waagent.log` and uses the system logrotate functionality to rotate logs.
179       '';
181       OS = {
182         EnableRDMA = lib.mkEnableOption ''
183           If enabled, the agent attempts to install and then load an RDMA kernel driver
184           that matches the version of the firmware on the underlying hardware.
185         '';
187         RootDeviceScsiTimeout = lib.mkOption {
188           type = types.nullOr types.int;
189           default = 300;
190           description = ''
191             Configures the SCSI timeout in seconds on the OS disk and data drives.
192             If set to `null`, the system defaults are used.
193           '';
194         };
195       };
197       HttpProxy = {
198         Host = lib.mkOption {
199           type = types.nullOr types.str;
200           default = null;
201           description = ''
202             If you set http proxy, waagent will use is proxy to access the Internet.
203           '';
204         };
206         Port = lib.mkOption {
207           type = types.nullOr types.int;
208           default = null;
209           description = ''
210             If you set http proxy, waagent will use this proxy to access the Internet.
211           '';
212         };
213       };
215       AutoUpdate.Enable = lib.mkEnableOption ''
216         Enable or disable autoupdate for goal state processing.
217       '';
218     };
219   };
222   options.services.waagent = {
223     enable = lib.mkEnableOption ''
224       Whether to enable the Windows Azure Linux Agent.
225     '';
227     package = lib.mkPackageOption pkgs "waagent" { };
229     extraPackages = lib.mkOption {
230       default = [ ];
231       description = ''
232         Additional packages to add to the waagent {env}`PATH`.
233       '';
234       example = lib.literalExpression "[ pkgs.powershell ]";
235       type = lib.types.listOf lib.types.package;
236     };
238     settings = lib.mkOption {
239       type = settingsType;
240       default = { };
241       description = ''
242         The waagent.conf configuration, see https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux for documentation.
243       '';
244     };
245   };
247   config = lib.mkIf cfg.enable {
248     assertions = [
249       {
250         assertion = (cfg.settings.HttpProxy.Host != null) -> (cfg.settings.HttpProxy.Port != null);
251         message = "Option services.waagent.settings.HttpProxy.Port must be set if services.waagent.settings.HttpProxy.Host is set.";
252       }
253     ];
255     boot.initrd.kernelModules = [ "ata_piix" ];
256     networking.firewall.allowedUDPPorts = [ 68 ];
258     services.udev.packages = with pkgs; [ waagent ];
260     boot.initrd.services.udev = with pkgs; {
261       # Provide waagent-shipped udev rules in initrd too.
262       packages = [ waagent ];
263       # udev rules shell out to chmod, cut and readlink, which are all
264       # provided by pkgs.coreutils, which is in services.udev.path, but not
265       # boot.initrd.services.udev.binPackages.
266       binPackages = [ coreutils ];
267     };
269     networking.dhcpcd.persistent = true;
271     services.logrotate = {
272       enable = true;
273       settings."/var/log/waagent.log" = {
274         compress = true;
275         frequency = "monthly";
276         rotate = 6;
277       };
278     };
280     # Write settings to /etc/waagent.conf
281     environment.etc."waagent.conf".source = settingsFormat.generate "waagent.conf" cfg.settings;
283     systemd.targets.provisioned = {
284       description = "Services Requiring Azure VM provisioning to have finished";
285     };
287     systemd.services.consume-hypervisor-entropy = {
288       description = "Consume entropy in ACPI table provided by Hyper-V";
290       wantedBy = [
291         "sshd.service"
292         "waagent.service"
293       ];
294       before = [
295         "sshd.service"
296         "waagent.service"
297       ];
299       path = [ pkgs.coreutils ];
300       script = ''
301         echo "Fetching entropy..."
302         cat /sys/firmware/acpi/tables/OEM0 > /dev/random
303       '';
304       serviceConfig.Type = "oneshot";
305       serviceConfig.RemainAfterExit = true;
306       serviceConfig.StandardError = "journal+console";
307       serviceConfig.StandardOutput = "journal+console";
308     };
310     systemd.services.waagent = {
311       wantedBy = [ "multi-user.target" ];
312       after = [
313         "network-online.target"
314       ] ++ lib.optionals config.services.cloud-init.enable [ "cloud-init.service" ];
315       wants = [
316         "network-online.target"
317         "sshd.service"
318         "sshd-keygen.service"
319       ];
321       path =
322         with pkgs;
323         [
324           e2fsprogs
325           bash
326           findutils
327           gnugrep
328           gnused
329           iproute2
330           iptables
331           openssh
332           openssl
333           parted
335           # for hostname
336           nettools
337           # for pidof
338           procps
339           # for useradd, usermod
340           shadow
342           util-linux # for (u)mount, fdisk, sfdisk, mkswap
343           # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
344           lsof
345         ]
346         ++ cfg.extraPackages;
347       description = "Windows Azure Agent Service";
348       unitConfig.ConditionPathExists = "/etc/waagent.conf";
349       serviceConfig = {
350         ExecStart = "${lib.getExe cfg.package} -daemon";
351         Type = "simple";
352         Restart = "always";
353         Slice = "azure.slice";
354         CPUAccounting = true;
355         MemoryAccounting = true;
356       };
357     };
359     # waagent will generate files under /etc/sudoers.d during provisioning
360     security.sudo.extraConfig = ''
361       #includedir /etc/sudoers.d
362     '';
363   };