From 0d7c515332cac9439581da6ffd9f83919edf17d7 Mon Sep 17 00:00:00 2001 From: codgician <15964984+codgician@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:32:16 +0800 Subject: [PATCH] waagent: init module --- .../manual/release-notes/rl-2505.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/virtualisation/azure-agent.nix | 343 +++-------------- nixos/modules/virtualisation/waagent.nix | 364 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/waagent.nix | 72 ++++ pkgs/by-name/wa/waagent/package.nix | 10 +- 7 files changed, 502 insertions(+), 291 deletions(-) create mode 100644 nixos/modules/virtualisation/waagent.nix create mode 100644 nixos/tests/waagent.nix diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 32f60ddcf7d9..da3fb519b1f4 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -70,6 +70,8 @@ - `nodePackages.ganache` has been removed, as the package has been deprecated by upstream. +- `services.waagent` option has been added, making `/etc/waagent.conf` customizable. `virtualisation.azure.agent` option provided by `azure-agent.nix` module has been deprecated and the module will be removed in a future release. + - `containerd` has been updated to v2, which contains breaking changes. See the [containerd 2.0](https://github.com/containerd/containerd/blob/main/docs/containerd-2.0.md) documentation for more details. diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index eaff9dc318df..2062229b98fb 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1757,6 +1757,7 @@ ./virtualisation/virtualbox-host.nix ./virtualisation/vmware-guest.nix ./virtualisation/vmware-host.nix + ./virtualisation/waagent.nix ./virtualisation/waydroid.nix ./virtualisation/xe-guest-utilities.nix ./virtualisation/xen-dom0.nix diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix index 8903bf0985a2..b64900bdd2e0 100644 --- a/nixos/modules/virtualisation/azure-agent.nix +++ b/nixos/modules/virtualisation/azure-agent.nix @@ -1,291 +1,56 @@ -{ config, lib, pkgs, ... }: +{ lib, ... }: with lib; -let - - cfg = config.virtualisation.azure.agent; - - provisionedHook = pkgs.writeScript "provisioned-hook" '' - #!${pkgs.runtimeShell} - /run/current-system/systemd/bin/systemctl start provisioned.target - ''; - -in -{ - - ###### interface - - options.virtualisation.azure.agent = { - enable = mkOption { - default = false; - description = "Whether to enable the Windows Azure Linux Agent."; - }; - verboseLogging = mkOption { - default = false; - description = "Whether to enable verbose logging."; - }; - mountResourceDisk = mkOption { - default = true; - description = "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource."; - }; - }; - - ###### implementation - - config = lib.mkIf cfg.enable { - assertions = [{ - assertion = config.networking.networkmanager.enable == false; - message = "Windows Azure Linux Agent is not compatible with NetworkManager"; - }]; - - boot.initrd.kernelModules = [ "ata_piix" ]; - networking.firewall.allowedUDPPorts = [ 68 ]; - - - environment.etc."waagent.conf".text = '' - # - # Microsoft Azure Linux Agent Configuration - # - - # Enable extension handling. Do not disable this unless you do not need password reset, - # backup, monitoring, or any extension handling whatsoever. - Extensions.Enabled=y - - # How often (in seconds) to poll for new goal states - Extensions.GoalStatePeriod=6 - - # Which provisioning agent to use. Supported values are "auto" (default), "waagent", - # "cloud-init", or "disabled". - Provisioning.Agent=auto - - # Password authentication for root account will be unavailable. - Provisioning.DeleteRootPassword=n - - # Generate fresh host key pair. - Provisioning.RegenerateSshHostKeyPair=n - - # Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto". - # The "auto" option is supported on OpenSSH 5.9 (2011) and later. - Provisioning.SshHostKeyPairType=ed25519 - - # Monitor host name changes and publish changes via DHCP requests. - Provisioning.MonitorHostName=y - - # How often (in seconds) to monitor host name changes. - Provisioning.MonitorHostNamePeriod=30 - - # Decode CustomData from Base64. - Provisioning.DecodeCustomData=n - - # Execute CustomData after provisioning. - Provisioning.ExecuteCustomData=n - - # Algorithm used by crypt when generating password hash. - #Provisioning.PasswordCryptId=6 - - # Length of random salt used when generating password hash. - #Provisioning.PasswordCryptSaltLength=10 - - # Allow reset password of sys user - Provisioning.AllowResetSysUser=n - - # Format if unformatted. If 'n', resource disk will not be mounted. - ResourceDisk.Format=${if cfg.mountResourceDisk then "y" else "n"} - - # File system on the resource disk - # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here. - ResourceDisk.Filesystem=ext4 - - # Mount point for the resource disk - ResourceDisk.MountPoint=/mnt/resource - - # Create and use swapfile on resource disk. - ResourceDisk.EnableSwap=n - - # Size of the swapfile. - ResourceDisk.SwapSizeMB=0 - - # Comma-separated list of mount options. See mount(8) for valid options. - ResourceDisk.MountOptions=None - - # Enable verbose logging (y|n) - Logs.Verbose=${if cfg.verboseLogging then "y" else "n"} - - # Enable Console logging, default is y - # Logs.Console=y - - # Enable periodic log collection, default is n - Logs.Collect=n - - # How frequently to collect logs, default is each hour - Logs.CollectPeriod=3600 - - # Is FIPS enabled - OS.EnableFIPS=n - - # Root device timeout in seconds. - OS.RootDeviceScsiTimeout=300 - - # How often (in seconds) to set the root device timeout. - OS.RootDeviceScsiTimeoutPeriod=30 - - # If "None", the system default version is used. - OS.OpensslPath=${pkgs.openssl_3.bin}/bin/openssl - - # Set the SSH ClientAliveInterval - # OS.SshClientAliveInterval=180 - - # Set the path to SSH keys and configuration files - OS.SshDir=/etc/ssh - - # If set, agent will use proxy server to access internet - #HttpProxy.Host=None - #HttpProxy.Port=None - - # Detect Scvmm environment, default is n - # DetectScvmmEnv=n - - # - # Lib.Dir=/var/lib/waagent - - # - # DVD.MountPoint=/mnt/cdrom/secure - - # - # Pid.File=/var/run/waagent.pid - - # - # Extension.LogDir=/var/log/azure - - # - # Home.Dir=/home - - # Enable RDMA management and set up, should only be used in HPC images - OS.EnableRDMA=n - - # Enable checking RDMA driver version and update - # OS.CheckRdmaDriver=y - - # Enable or disable goal state processing auto-update, default is enabled - AutoUpdate.Enabled=n - - # Determine the update family, this should not be changed - # AutoUpdate.GAFamily=Prod - - # Determine if the overprovisioning feature is enabled. If yes, hold extension - # handling until inVMArtifactsProfile.OnHold is false. - # Default is enabled - EnableOverProvisioning=n - - # Allow fallback to HTTP if HTTPS is unavailable - # Note: Allowing HTTP (vs. HTTPS) may cause security risks - # OS.AllowHTTP=n - - # Add firewall rules to protect access to Azure host node services - OS.EnableFirewall=n - - # How often (in seconds) to check the firewall rules - OS.EnableFirewallPeriod=30 - - # How often (in seconds) to remove the udev rules for persistent network interface - # names (75-persistent-net-generator.rules and /etc/udev/rules.d/70-persistent-net.rules) - OS.RemovePersistentNetRulesPeriod=30 - - # How often (in seconds) to monitor for DHCP client restarts - OS.MonitorDhcpClientRestartPeriod=30 - ''; - - services.udev.packages = [ pkgs.waagent ]; - - # Provide waagent-shipped udev rules in initrd too. - boot.initrd.services.udev.packages = [ pkgs.waagent ]; - # udev rules shell out to chmod, cut and readlink, which are all - # provided by pkgs.coreutils, which is in services.udev.path, but not - # boot.initrd.services.udev.binPackages. - boot.initrd.services.udev.binPackages = [ pkgs.coreutils ]; - - networking.dhcpcd.persistent = true; - - services.logrotate = { - enable = true; - settings."/var/log/waagent.log" = { - compress = true; - frequency = "monthly"; - rotate = 6; - }; - }; - - systemd.targets.provisioned = { - description = "Services Requiring Azure VM provisioning to have finished"; - }; - - systemd.services.consume-hypervisor-entropy = - { - description = "Consume entropy in ACPI table provided by Hyper-V"; - - wantedBy = [ "sshd.service" "waagent.service" ]; - before = [ "sshd.service" "waagent.service" ]; - - path = [ pkgs.coreutils ]; - script = - '' - echo "Fetching entropy..." - cat /sys/firmware/acpi/tables/OEM0 > /dev/random - ''; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - serviceConfig.StandardError = "journal+console"; - serviceConfig.StandardOutput = "journal+console"; - }; - - systemd.services.waagent = { - wantedBy = [ "multi-user.target" ]; - after = [ "network-online.target" "sshd.service" ]; - wants = [ "network-online.target" ]; - - path = [ - pkgs.e2fsprogs - pkgs.bash - - pkgs.findutils - pkgs.gnugrep - pkgs.gnused - pkgs.iproute2 - pkgs.iptables - - # for hostname - pkgs.nettools - - pkgs.openssh - pkgs.openssl - pkgs.parted - - # for pidof - pkgs.procps - - # for useradd, usermod - pkgs.shadow - - pkgs.util-linux # for (u)mount, fdisk, sfdisk, mkswap - - # waagent's Microsoft.OSTCExtensions.VMAccessForLinux needs Python 3 - pkgs.python39 - - # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof - pkgs.lsof - ]; - description = "Windows Azure Agent Service"; - unitConfig.ConditionPathExists = "/etc/waagent.conf"; - serviceConfig = { - ExecStart = "${pkgs.waagent}/bin/waagent -daemon"; - Type = "simple"; - }; - }; - - # waagent will generate files under /etc/sudoers.d during provisioning - security.sudo.extraConfig = '' - #includedir /etc/sudoers.d - ''; - - }; -} +warn + '' + `virtualisation.azure.agent` provided by `azure-agent.nix` module has been replaced + by `services.waagent` options, and will be removed in a future release. + '' + { + + imports = [ + (mkRenamedOptionModule + [ + "virtualisation" + "azure" + "agent" + "enable" + ] + [ + "services" + "waagent" + "enable" + ] + ) + (mkRenamedOptionModule + [ + "virtualisation" + "azure" + "agent" + "verboseLogging" + ] + [ + "services" + "waagent" + "settings" + "Logs" + "Verbose" + ] + ) + (mkRenamedOptionModule + [ + "virtualisation" + "azure" + "agent" + "mountResourceDisk" + ] + [ + "services" + "waagent" + "settings" + "ResourceDisk" + "Format" + ] + ) + ]; + } diff --git a/nixos/modules/virtualisation/waagent.nix b/nixos/modules/virtualisation/waagent.nix new file mode 100644 index 000000000000..af6ae5bc642d --- /dev/null +++ b/nixos/modules/virtualisation/waagent.nix @@ -0,0 +1,364 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; +let + cfg = config.services.waagent; + + # Format for waagent.conf + settingsFormat = { + type = + with types; + let + singleAtom = + (nullOr (oneOf [ + bool + str + int + float + ])) + // { + description = "atom (bool, string, int or float) or null"; + }; + atom = either singleAtom (listOf singleAtom) // { + description = singleAtom.description + " or a list of them"; + }; + in + attrsOf ( + either atom (attrsOf atom) + // { + description = atom.description + "or an attribute set of them"; + } + ); + generate = + name: value: + let + # Transform non-attribute values + transform = + x: + # Transform bool to "y" or "n" + if (isBool x) then + (if x then "y" else "n") + # Concatenate list items with comma + else if (isList x) then + concatStringsSep "," (map transform x) + else + toString x; + + # Convert to format of waagent.conf + recurse = + path: value: + if builtins.isAttrs value then + pipe value [ + (mapAttrsToList (k: v: recurse (path ++ [ k ]) v)) + concatLists + ] + else + [ + { + name = concatStringsSep "." path; + inherit value; + } + ]; + convert = + attrs: + pipe (recurse [ ] attrs) [ + # Filter out null values and emoty lists + (filter (kv: kv.value != null && kv.value != [ ])) + # Transform to Key=Value form, then concatenate + (map (kv: "${kv.name}=${transform kv.value}")) + (concatStringsSep "\n") + ]; + in + pkgs.writeText name (convert value); + }; + + settingsType = types.submodule { + freeformType = settingsFormat.type; + options = { + Provisioning = { + Enable = mkOption { + type = types.bool; + default = !config.services.cloud-init.enable; + defaultText = literalExpression "!config.services.cloud-init.enable"; + description = '' + Whether to enable provisioning functionality in the agent. + + If provisioning is disabled, SSH host and user keys in the image are preserved + and configuration in the Azure provisioning API is ignored. + + Set to `false` if cloud-init is used for provisioning tasks. + ''; + }; + + Agent = mkOption { + type = types.enum [ + "auto" + "waagent" + "cloud-init" + "disabled" + ]; + default = "auto"; + description = '' + Which provisioning agent to use. + ''; + }; + }; + + ResourceDisk = { + Format = mkEnableOption '' + If set to `true`, waagent formats and mounts the resource disk that the platform provides, + unless the file system type in `ResourceDisk.FileSystem` is set to `ntfs`. + The agent makes a single Linux partition (ID 83) available on the disk. + This partition isn't formatted if it can be successfully mounted. + + This configuration has no effect if resource disk is managed by cloud-init. + ''; + + FileSystem = mkOption { + type = types.str; + default = "ext4"; + description = '' + The file system type for the resource disk. + If the string is `X`, then `mkfs.X` should be present in the environment. + You can add additional filesystem packages using `services.waagent.extraPackages`. + + This configuration has no effect if resource disk is managed by cloud-init. + ''; + }; + + MountPoint = mkOption { + type = types.str; + default = "/mnt/resource"; + description = '' + This option specifies the path at which the resource disk is mounted. + The resource disk is a temporary disk and might be emptied when the VM is deprovisioned. + + This configuration has no effect if resource disk is managed by cloud-init. + ''; + }; + + MountOptions = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ + "nodev" + "nosuid" + ]; + description = '' + This option specifies disk mount options to be passed to the `mount -o` command. + For more information, see the `mount(8)` manual page. + ''; + }; + + EnableSwap = mkEnableOption '' + If enabled, the agent creates a swap file (`/swapfile`) on the resource disk + and adds it to the system swap space. + + This configuration has no effect if resource disk is managed by cloud-init. + ''; + + SwapSizeMB = mkOption { + type = types.int; + default = 0; + description = '' + Specifies the size of the swap file in megabytes. + + This configuration has no effect if resource disk is managed by cloud-init. + ''; + }; + }; + + Logs.Verbose = lib.mkEnableOption '' + If you set this option, log verbosity is boosted. + Waagent logs to `/var/log/waagent.log` and uses the system logrotate functionality to rotate logs. + ''; + + OS = { + EnableRDMA = lib.mkEnableOption '' + If enabled, the agent attempts to install and then load an RDMA kernel driver + that matches the version of the firmware on the underlying hardware. + ''; + + RootDeviceScsiTimeout = lib.mkOption { + type = types.nullOr types.int; + default = 300; + description = '' + Configures the SCSI timeout in seconds on the OS disk and data drives. + If set to `null`, the system defaults are used. + ''; + }; + }; + + HttpProxy = { + Host = lib.mkOption { + type = types.nullOr types.str; + default = null; + description = '' + If you set http proxy, waagent will use is proxy to access the Internet. + ''; + }; + + Port = lib.mkOption { + type = types.nullOr types.int; + default = null; + description = '' + If you set http proxy, waagent will use this proxy to access the Internet. + ''; + }; + }; + + AutoUpdate.Enable = lib.mkEnableOption '' + Enable or disable autoupdate for goal state processing. + ''; + }; + }; +in +{ + options.services.waagent = { + enable = lib.mkEnableOption '' + Whether to enable the Windows Azure Linux Agent. + ''; + + package = lib.mkPackageOption pkgs "waagent" { }; + + extraPackages = lib.mkOption { + default = [ ]; + description = '' + Additional packages to add to the waagent {env}`PATH`. + ''; + example = lib.literalExpression "[ pkgs.powershell ]"; + type = lib.types.listOf lib.types.package; + }; + + settings = lib.mkOption { + type = settingsType; + default = { }; + description = '' + The waagent.conf configuration, see https://learn.microsoft.com/en-us/azure/virtual-machines/extensions/agent-linux for documentation. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.settings.HttpProxy.Host == null || cfg.settings.HttpProxy.Port != null; + message = "Option services.waagent.settings.HttpProxy.Port must be set if services.waagent.settings.HttpProxy.Host is set."; + } + ]; + + boot.initrd.kernelModules = [ "ata_piix" ]; + networking.firewall.allowedUDPPorts = [ 68 ]; + + services.udev.packages = with pkgs; [ waagent ]; + + boot.initrd.services.udev = with pkgs; { + # Provide waagent-shipped udev rules in initrd too. + packages = [ waagent ]; + # udev rules shell out to chmod, cut and readlink, which are all + # provided by pkgs.coreutils, which is in services.udev.path, but not + # boot.initrd.services.udev.binPackages. + binPackages = [ coreutils ]; + }; + + networking.dhcpcd.persistent = true; + + services.logrotate = { + enable = true; + settings."/var/log/waagent.log" = { + compress = true; + frequency = "monthly"; + rotate = 6; + }; + }; + + # Write settings to /etc/waagent.conf + environment.etc."waagent.conf".source = settingsFormat.generate "waagent.conf" cfg.settings; + + systemd.targets.provisioned = { + description = "Services Requiring Azure VM provisioning to have finished"; + }; + + systemd.services.consume-hypervisor-entropy = { + description = "Consume entropy in ACPI table provided by Hyper-V"; + + wantedBy = [ + "sshd.service" + "waagent.service" + ]; + before = [ + "sshd.service" + "waagent.service" + ]; + + path = [ pkgs.coreutils ]; + script = '' + echo "Fetching entropy..." + cat /sys/firmware/acpi/tables/OEM0 > /dev/random + ''; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + serviceConfig.StandardError = "journal+console"; + serviceConfig.StandardOutput = "journal+console"; + }; + + systemd.services.waagent = { + wantedBy = [ "multi-user.target" ]; + after = [ + "network-online.target" + ] ++ lib.optionals config.services.cloud-init.enable [ "cloud-init.service" ]; + wants = [ + "network-online.target" + "sshd.service" + "sshd-keygen.service" + ]; + + path = + with pkgs; + [ + e2fsprogs + bash + findutils + gnugrep + gnused + iproute2 + iptables + openssh + openssl + parted + + # for hostname + nettools + # for pidof + procps + # for useradd, usermod + shadow + + util-linux # for (u)mount, fdisk, sfdisk, mkswap + # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof + lsof + ] + ++ cfg.extraPackages; + description = "Windows Azure Agent Service"; + unitConfig.ConditionPathExists = "/etc/waagent.conf"; + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} -daemon"; + Type = "simple"; + Restart = "always"; + Slice = "azure.slice"; + CPUAccounting = true; + MemoryAccounting = true; + }; + }; + + # waagent will generate files under /etc/sudoers.d during provisioning + security.sudo.extraConfig = '' + #includedir /etc/sudoers.d + ''; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index fae441d28e20..ba48ae769be7 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1114,6 +1114,7 @@ in { vscode-remote-ssh = handleTestOn ["x86_64-linux"] ./vscode-remote-ssh.nix {}; vscodium = discoverTests (import ./vscodium.nix); vsftpd = handleTest ./vsftpd.nix {}; + waagent = handleTest ./waagent.nix {}; wakapi = handleTest ./wakapi.nix {}; warzone2100 = handleTest ./warzone2100.nix {}; wasabibackend = handleTest ./wasabibackend.nix {}; diff --git a/nixos/tests/waagent.nix b/nixos/tests/waagent.nix new file mode 100644 index 000000000000..54fa645e8304 --- /dev/null +++ b/nixos/tests/waagent.nix @@ -0,0 +1,72 @@ +import ./make-test-python.nix ( + { lib, pkgs, ... }: + let + confPath = "/etc/waagent.conf"; + in + { + name = "waagent"; + + meta = { + maintainers = with lib.maintainers; [ codgician ]; + }; + + nodes.machine = { + services.waagent = { + enable = true; + settings = { + Provisioning = { + Enable = false; + Agent = "waagent"; + DeleteRootPassword = false; + RegenerateSshHostKeyPair = false; + SshHostKeyPairType = "ed25519"; + MonitorHostName = false; + }; + ResourceDisk = { + Format = false; + MountOptions = [ + "compress=lzo" + "mode=0600" + ]; + }; + OS.RootDeviceScsiTimeout = 300; + HttpProxy = { + Host = null; + Port = null; + }; + CGroups = { + EnforceLimits = false; + Excluded = [ ]; + }; + }; + }; + }; + + testScript = '' + # Defined values should be reflected in waagent.conf + machine.succeed("grep -q '^Provisioning.Enable=n$' '${confPath}'") + machine.succeed("grep -q '^Provisioning.Agent=waagent$' '${confPath}'") + machine.succeed("grep -q '^Provisioning.DeleteRootPassword=n$' '${confPath}'") + machine.succeed("grep -q '^Provisioning.RegenerateSshHostKeyPair=n$' '${confPath}'") + machine.succeed("grep -q '^Provisioning.SshHostKeyPairType=ed25519$' '${confPath}'") + machine.succeed("grep -q '^Provisioning.MonitorHostName=n$' '${confPath}'") + machine.succeed("grep -q '^ResourceDisk.Format=n$' '${confPath}'") + machine.succeed("grep -q '^ResourceDisk.MountOptions=compress=lzo,mode=0600$' '${confPath}'") + machine.succeed("grep -q '^OS.RootDeviceScsiTimeout=300$' '${confPath}'") + + # Undocumented options should also be supported + machine.succeed("grep -q '^CGroups.EnforceLimits=n$' '${confPath}'") + + # Null values should be skipped and not exist in waagent.conf + machine.fail("grep -q '^HttpProxy.Host=' '${confPath}'") + machine.fail("grep -q '^HttpProxy.Port=' '${confPath}'") + + # Empty lists should be skipped and not exist in waagent.conf + machine.fail("grep -q '^CGroups.Excluded=' '${confPath}'") + + # Test service start + # Skip testing actual functionality due to lacking Azure infrasturcture + machine.wait_for_unit("waagent.service") + ''; + } +) diff --git a/pkgs/by-name/wa/waagent/package.nix b/pkgs/by-name/wa/waagent/package.nix index cbfbc9e026da..6f5246de6811 100644 --- a/pkgs/by-name/wa/waagent/package.nix +++ b/pkgs/by-name/wa/waagent/package.nix @@ -4,6 +4,7 @@ lib, python3, bash, + gitUpdater, }: let @@ -63,7 +64,11 @@ python.pkgs.buildPythonApplication rec { dontWrapPythonPrograms = false; - meta = { + passthru.updateScript = gitUpdater { + rev-prefix = "v"; + }; + + meta = with lib; { description = "Microsoft Azure Linux Agent (waagent)"; mainProgram = "waagent"; longDescription = '' @@ -71,6 +76,7 @@ python.pkgs.buildPythonApplication rec { manages Linux provisioning and VM interaction with the Azure Fabric Controller''; homepage = "https://github.com/Azure/WALinuxAgent"; - license = with lib.licenses; [ asl20 ]; + maintainers = with maintainers; [ codgician ]; + license = with licenses; [ asl20 ]; }; }