Running NetworkManager in initrd

I am currently running an encrypted root. I would like to open an ssh server on reboot in initrd, log in, decrypt the disk, and continue booting (very standard stuff).

However, I also want to use the same networking stack (NetworkManager) both in the initrd and in the final system so that I can reuse .nmconnection files in the initrd.

I have not set up ssh yet, but that is not the hard part (I will most likely use the boot.initrd.ssh built-in options).

About the system: I am also using disko and the initrdUnlock option of disko. Ignoring this networking issue, it is performing great. It runs on wifi. The hostname is β€œargon”

My issue is that I think this is above my skill level. I have tried many permutations but I think I am missing something. I think the NetworkManager-provided dbus .conf files in system.d are somehow incompatible with initrd. But IDK what to do about that.

I am looking for a fresh perspective on this issue. My end goal is simply to reuse the .nmconnection files between the initrd and the full OS.

Here is my current initrd.nix file:


{
  config,
  lib,
  pkgs,
  modulesPath,
  ...
}:
{

  boot.initrd =
    let

      linkScript = pkgs.writeShellApplication {
        name = "linkScript";
        runtimeInputs = [
          pkgs.busybox
          pkgs.networkmanager
        ];

        text = ''
          #!/usr/bin/env bash
          mkdir -p /etc/dbus-1/system.d
          ln -sf ${pkgs.networkmanager}/share/dbus-1/system.d/org.freedesktop.NetworkManager.conf /etc/dbus-1/system.d/org.freedesktop.NetworkManager.conf;
          ln -sf ${pkgs.networkmanager}/share/dbus-1/system.d/nm-dispatcher.conf /etc/dbus-1/system.d/nm-dispatcher.conf;
          ln -sf ${pkgs.networkmanager}/share/dbus-1/system.d/nm-priv-helper.conf /etc/dbus-1/system.d/nm-priv-helper.conf;

        '';

      };

    in
    {

      services.udev.rules = config.services.udev.extraRules;

      availableKernelModules = [
        "ccm"
        "ctr"
        "iwlmvm"
        "iwlwifi"
      ];

      secrets = {

        "/usr/lol/initrd.nix" = ./initrd.nix;

      };

      systemd = {

        enable = true;

        initrdBin = [
          pkgs.networkmanager
          pkgs.dbus
          pkgs.busybox
          linkScript
        ];

        packages = [ pkgs.networkmanager ];

        strip = false;

        dbus.enable = true;

        sockets."dbus".unitConfig.DefaultDependencies = false;

        sockets.dbus.after = lib.mkForce [ ];
        sockets.dbus.requires = lib.mkForce [ ];
        sockets.dbus.wants = lib.mkForce [ ];
        sockets.dbus.before = lib.mkForce [ ];
        sockets.dbus.requiredBy = lib.mkForce [ ];
        sockets.dbus.wantedBy = lib.mkForce [ ];

        services."dbus".unitConfig.DefaultDependencies = false;

        services.dbus.after = lib.mkForce [ ];
        services.dbus.requires = lib.mkForce [ ];
        services.dbus.wants = lib.mkForce [ ];
        services.dbus.before = lib.mkForce [ "systemd-cryptsetup@argon_zfs_root.service" ];
        services.dbus.wantedBy = lib.mkForce [ ];
        services.dbus.requiredBy = lib.mkForce [ "systemd-cryptsetup@argon_zfs_root.service" ];


        services.NetworkManager.enable = true;
        services.NetworkManager.unitConfig.DefaultDependencies = false;

        services.NetworkManager.after = lib.mkForce [
          "dbus.service"
          "initrd-nixos-copy-secrets.service"
        ];
        services.NetworkManager.requires = lib.mkForce [ ];
        services.NetworkManager.wants = lib.mkForce [ ];
        services.NetworkManager.before = lib.mkForce [ "systemd-cryptsetup@argon_zfs_root.service" ];
        services.NetworkManager.requiredBy = lib.mkForce [ ];
        services.NetworkManager.wantedBy = lib.mkForce [ "systemd-cryptsetup@argon_zfs_root.service" ];

        storePaths = [ pkgs.networkmanager ];

        users.root.shell = "/bin/bash";

        services.link-dbus-nm = {
          enable = true;
          unitConfig.DefaultDependencies = false;

          after = [ "initrd-nixos-copy-secrets.service" ];
          before = [
            "dbus.service"
            "NetworkManager.service"
          ];
          wantedBy = [ "dbus.service" ];

          description = "Link in NetworkManager D-Bus rules";

          serviceConfig = {

            Type = "oneshot";
            RemainAfterExit = "no";
            ExecStart = "${linkScript}/bin/linkScript";

          };

        };

      };

    };

}

The output of journalctl -xeu dbus after logging in:

Nov 21 15:11:28 argon dbus-daemon[143]: dbus[143]: Unknown username "systemd-timesync" in message bus configuration file
Nov 21 15:11:28 argon dbus-daemon[143]: dbus[143]: Unknown username "systemd-resolve" in message bus configuration file
Nov 21 15:11:28 argon dbus-daemon[143]: dbus[143]: Unknown username "systemd-oom" in message bus configuration file
Nov 21 15:11:28 argon dbus-daemon[143]: dbus[143]: Unknown username "systemd-network" in message bus configuration file
Nov 21 15:11:28 argon dbus-daemon[143]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.1' (uid=0 pid=152 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:25 argon dbus-daemon[143]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.hostname1.service': Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:26 argon dbus-daemon[143]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.2' (uid=0 pid=274 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:26 argon dbus-daemon[143]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.hostname1.service': Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.3' (uid=0 pid=289 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.hostname1.service': Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.4' (uid=0 pid=310 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.hostname1.service': Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.5' (uid=0 pid=320 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:27 argon dbus-daemon[143]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.hostname1.service': Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:39 argon systemd[1]: Stopping D-Bus System Message Bus...
β–‘β–‘ Subject: A stop job for unit dbus.service has begun execution
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ A stop job for unit dbus.service has begun execution.
β–‘β–‘ 
β–‘β–‘ The job identifier is 186.
Nov 21 15:11:39 argon systemd[1]: dbus.service: Deactivated successfully.
β–‘β–‘ Subject: Unit succeeded
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ The unit dbus.service has successfully entered the 'dead' state.
Nov 21 15:11:39 argon systemd[1]: Stopped D-Bus System Message Bus.
β–‘β–‘ Subject: A stop job for unit dbus.service has finished
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ A stop job for unit dbus.service has finished.
β–‘β–‘ 
β–‘β–‘ The job identifier is 186 and the job result is done.
Nov 21 15:11:43 argon systemd[1]: Starting D-Bus System Message Bus...
β–‘β–‘ Subject: A start job for unit dbus.service has begun execution
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ A start job for unit dbus.service has begun execution.
β–‘β–‘ 
β–‘β–‘ The job identifier is 317.
Nov 21 15:11:43 argon dbus-daemon[1039]: dbus[1039]: Unknown username "nm-openconnect" in message bus configuration file
Nov 21 15:11:43 argon dbus-daemon[1039]: dbus[1039]: Unknown username "nm-openconnect" in message bus configuration file
Nov 21 15:11:43 argon systemd[1]: Started D-Bus System Message Bus.
β–‘β–‘ Subject: A start job for unit dbus.service has finished successfully
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ A start job for unit dbus.service has finished successfully.
β–‘β–‘ 
β–‘β–‘ The job identifier is 317.
Nov 21 15:11:43 argon dbus-daemon[1039]: [system] Activating via systemd: service name='org.freedesktop.hostname1' unit='dbus-org.freedesktop.hostname1.service' requested by ':1.5' (uid=0 pid=1149 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:43 argon dbus-daemon[1039]: [system] Successfully activated service 'org.freedesktop.hostname1'
Nov 21 15:11:43 argon dbus-daemon[1039]: [system] Activating via systemd: service name='org.freedesktop.machine1' unit='dbus-org.freedesktop.machine1.service' requested by ':1.7' (uid=0 pid=1223 comm="machinectl terminate db-forgejo" label="kernel")
Nov 21 15:11:43 argon dbus-daemon[1039]: [system] Successfully activated service 'org.freedesktop.machine1'
Nov 21 15:11:45 argon dbus-daemon[1039]: [system] Activating via systemd: service name='org.freedesktop.resolve1' unit='dbus-org.freedesktop.resolve1.service' requested by ':1.5' (uid=0 pid=1149 comm="/nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networ" label="kernel")
Nov 21 15:11:45 argon dbus-daemon[1039]: [system] Activation via systemd failed for unit 'dbus-org.freedesktop.resolve1.service': Unit dbus-org.freedesktop.resolve1.service not found.

The output of journalctl -xeu NetworkManager (I cut off some of the output because of character number limits. basically it just keeps failing until I decrypt the disk and log in, at which point it succeeds.):

Nov 21 15:11:28 argon systemd[1]: Starting Network Manager...
β–‘β–‘ Subject: A start job for unit NetworkManager.service has begun execution
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ A start job for unit NetworkManager.service has begun execution.
β–‘β–‘ 
β–‘β–‘ The job identifier is 37.
Nov 21 15:11:28 argon NetworkManager[152]: <info>  [1732219888.3407] NetworkManager (version 1.48.10) is starting... (boot:78d2ad2a-00bf-4c1a-a3b8-ed3375e0acdf)
Nov 21 15:11:28 argon NetworkManager[152]: <info>  [1732219888.3407] Read config: /etc/NetworkManager/NetworkManager.conf
Nov 21 15:11:28 argon NetworkManager[152]: <info>  [1732219888.3458] manager[0xf9ae8f0]: monitoring kernel firmware directory '/run/current-system/firmware'.
Nov 21 15:11:25 argon NetworkManager[152]: <info>  [1732219885.5815] hostname: hostname: hostnamed not used as proxy creation failed with: Error calling StartServiceByName for org.freedesktop.hostname1: Unit dbus-org.freedesktop.hostname1.service not found.
Nov 21 15:11:25 argon NetworkManager[152]: <info>  [1732219885.5816] hostname: static hostname changed from (none) to "argon"
Nov 21 15:11:25 argon NetworkManager[152]: <info>  [1732219885.5819] dns-mgr: init: dns=default,systemd-resolved rc-manager=symlink (auto)
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0221] manager[0xf9ae8f0]: rfkill: Wi-Fi hardware radio set enabled
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0221] manager[0xf9ae8f0]: rfkill: WWAN hardware radio set enabled
Nov 21 15:11:26 argon NetworkManager[152]: <warn>  [1732219886.0241] plugin: skip invalid file /nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networkmanager-1.48.10/lib/NetworkManager/1.48.10/libnm-device-plugin-wwan.so: file has invalid permissions
Nov 21 15:11:26 argon NetworkManager[152]: <warn>  [1732219886.0242] plugin: skip invalid file /nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networkmanager-1.48.10/lib/NetworkManager/1.48.10/libnm-device-plugin-wifi.so: file has invalid permissions
Nov 21 15:11:26 argon NetworkManager[152]: <warn>  [1732219886.0242] plugin: skip invalid file /nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networkmanager-1.48.10/lib/NetworkManager/1.48.10/libnm-device-plugin-ovs.so: file has invalid permissions
Nov 21 15:11:26 argon NetworkManager[152]: <warn>  [1732219886.0242] plugin: skip invalid file /nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networkmanager-1.48.10/lib/NetworkManager/1.48.10/libnm-device-plugin-bluetooth.so: file has invalid permissions
Nov 21 15:11:26 argon NetworkManager[152]: <warn>  [1732219886.0242] plugin: skip invalid file /nix/store/ryq93s931bx5rj2ra6cxx3mbmk51pr6r-networkmanager-1.48.10/lib/NetworkManager/1.48.10/libnm-device-plugin-adsl.so: file has invalid permissions
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0242] manager: rfkill: Wi-Fi enabled by radio killswitch; enabled by state file
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0243] manager: rfkill: WWAN enabled by radio killswitch; enabled by state file
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0244] manager: Networking is enabled by state file
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0245] settings: Loaded settings plugin: keyfile (internal)
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0276] dhcp: init: Using DHCP client 'internal'
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0278] manager: (lo): new Loopback device (/org/freedesktop/NetworkManager/Devices/1)
Nov 21 15:11:26 argon NetworkManager[152]: <error> [1732219886.0292] bus-manager: fatal failure to acquire D-Bus service "org.freedesktop.NetworkManager: GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: Connection ":1.1" is not allowed to own the service "org.freedesktop.NetworkManager" due to security policies in the configuration file
Nov 21 15:11:26 argon NetworkManager[152]: <info>  [1732219886.0297] exiting (error)
Nov 21 15:11:26 argon systemd[1]: NetworkManager.service: Main process exited, code=exited, status=1/FAILURE
β–‘β–‘ Subject: Unit process exited
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ An ExecStart= process belonging to unit NetworkManager.service has exited.
β–‘β–‘ 
β–‘β–‘ The process' exit code is 'exited' and its exit status is 1.
Nov 21 15:11:26 argon systemd[1]: NetworkManager.service: Failed with result 'exit-code'.
β–‘β–‘ Subject: Unit failed
β–‘β–‘ Defined-By: systemd
β–‘β–‘ Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
β–‘β–‘ 
β–‘β–‘ The unit NetworkManager.service has entered the 'failed' state with result 'exit-code'.

Have you had any luck with this since posting mate? I’m working on something similar with tailscale and systemd initrd. Personally I’ve abandoned the idea of using wifi with systemd initrd for now because the ecosystem just doesn’t seem to be there yet. Systemd initrd is not the default stage 1 currently and a lot of things aren’t as straightforward as just using the default nixos initrd system. Currently I’m trying for a wired systemd-networkd initrd for tailscale but I’m thinking that even that might be too high a stack for my skill level because of dbus.

What I can tell you is that on my end (and likely yours), the hiccup seems to be getting the systemd-timesyncd and systemd-oomd services going as dbus depends on them and the users they create.

No, I never got NetworkManager working in initrd. It is quite a shame because I wanted to standardize a networking stack on all my wireless devices. However, I easily got wifi working with wpa_supplicant which doesn’t need dbus. I ssh into my server remotely with yggdrasil and decrypt it. Yggrasil is basically an overlay network halfway between Tailscale and TOR. So this solution probably would work for Tailscale. To get yggdrasil working I am using a binary called β€œyggstack” which is not in nixpkgs, so that is why you see me making my own systemd service.

Here is my current initrd.nix:

{
  config,
  lib,
  pkgs,
  ...
}:

{
  boot.loader.grub.configurationLimit = 25;

  boot.initrd =
    let
      hostname = "argon";

      yggScript = pkgs.writeShellApplication {
        name = "yggscript";
        runtimeInputs = [ pkgs.yggstack ];
        text = ''
          #!/usr/bin/env bash
          yggstack -useconffile /yggstack.conf -remote-tcp 5123:127.0.0.1:5123
        '';
      };

    in
    {

      services.udev.rules = config.services.udev.extraRules;

      availableKernelModules = [
        "ccm"
        "ctr"
        "iwlmvm"
        "iwlwifi"
      ];

      secrets = {

        "/yggstack.conf" = "/run/secrets/yggstack-ssh-initrd.conf";

        "/ssh-hostkey" = config.sops.secrets."initrd-ssh-host-key".path;

        "/etc/wpa_supplicant/wpa_supplicant-zl_wifi0.conf" =
          config.sops.secrets."initrd_wpa_supplicant.conf".path;

      };

      systemd.network = {
        enable = true;
        networks."10-eth" = {
          matchConfig.Name = "zl_eth0";
          networkConfig.DHCP = "yes";
        };

        networks."10-wifi" = {
          matchConfig.Name = "zl_wifi0";
          networkConfig.DHCP = "yes";
        };

      };

      systemd.users.root.shell = "/bin/systemd-tty-ask-password-agent";
      network.enable = true;

      network.ssh.enable = true;
      network.ssh.port = 5123;

      #This will get copied into the nix store, but it is just a public key
      network.ssh.authorizedKeyFiles = [ "${config.zl.secrets.secretsPath}/clear/zach_authorized_keys" ];

      network.ssh.ignoreEmptyHostKeys = true;

      #For whatever reason "network.ssh.hostKeys" makes sops not work, so i set ignoreempyhostkeys and set the HostKey in "network.ssh.extraConfig"
      network.ssh.extraConfig = "HostKey /ssh-hostkey";

      systemd = {

        enable = true;

        initrdBin = [
          pkgs.busybox
          pkgs.yggstack
          yggScript
          pkgs.wpa_supplicant
        ];

        packages = [ pkgs.wpa_supplicant ];

        extraBin.yggstack = "${pkgs.yggstack}/bin/yggstack";

        services."wpa_supplicant@".unitConfig.DefaultDependencies = false;
        services."wpa_supplicant".unitConfig.DefaultDependencies = false;
        services."wpa_supplicant@".enable = true;

        services."sshd".unitConfig.WantedBy = [
          "systemd-cryptsetup@argon_zfs_root_1.service"
          "systemd-cryptsetup@argon_zfs_root_2.service"
        ];
        services."sshd".unitConfig.Before = [
          "systemd-cryptsetup@argon_zfs_root_1.service"
          "systemd-cryptsetup@argon_zfs_root_2.service"
        ];
        services."sshd".unitConfig.Wants = [ "wpa_supplicant@zl_wifi0.service" ];
        services."sshd".unitConfig.After = [ "wpa_supplicant@zl_wifi0.service" ];
        services."sshd".unitConfig.DefaultDependencies = false;

        targets."initrd".unitConfig.Wants = [ "yggstack-ssh-gate-initrd.service" ];

        services."yggstack-ssh-gate-initrd" = {
          enable = true;
          description = "Expose SSH to Yggdrasil Network";
          unitConfig.DefaultDependencies = false;
          serviceConfig = {
            Type = "simple";
            RemainAfterExit = "yes";
            ExecStart = "${yggScript}/bin/yggscript";
          };
        };
      };
    };
}

And in order to rename my wifi card to β€œzl-wifi0” I added some udev rules in a different file. Of course you will have to manually add the driver of your wifi card here for it to work:

services.udev.extraRules = ''
    SUBSYSTEMS=="pci", ACTION=="add", DRIVERS=="iwlwifi", NAME="zl-wifi%n"

    SUBSYSTEMS=="pci", ACTION=="add", DRIVERS=="e1000|e1000e|r8169", NAME="zl-eth%n"
  '';

My config is kind of bloated, I just threw stuff against the wall until something worked and didn’t really delete the extra stuff, so you can probably delete a lot of it and it will still work for you.

But for anyone reading I am really still looking for NetworkManger in initrd, bare wpa_supplicant is only a temporary solution for me.

If you can accept wpa_supplicant in initrd, then it’s relatively easy to make Wi-Fi and SSH work. This avoids dbus and I’ve just deployed it on my Raspberry Pi.
My working config:
https://git.sr.ht/~prince213/dotfiles/tree/main/item/systems/pegasus/boot.nix
Relavent discussion:

1 Like