# Network Manager: # i manage this myself because the nixos service is not flexible enough. # - it unconditionally puts modemmanager onto the system path, preventing me from patching modemmanager's service file (without an overlay). # # XXX: it's normal to see error messages on an ethernet-only host, even when using nixos' official networkmanager service: # - `Couldn't initialize supplicant interface: Failed to D-Bus activate wpa_supplicant service` { config, lib, pkgs, ... }: let cfg = config.sane.programs.networkmanager; in { config = lib.mkMerge [ { sane.programs.networkmanager = { packageUnwrapped = pkgs.networkmanager.overrideAttrs (upstream: { postPatch = (upstream.postPatch or "") + '' substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \ 'user="root"' 'user="networkmanager"' ''; # remove unused services to prevent any unexpected interactions postFixup = (upstream.postFixup or "") + '' rm $out/etc/systemd/system/{nm-cloud-setup.service,nm-cloud-setup.timer,nm-priv-helper.service} ''; }); suggestedPrograms = [ "wpa_supplicant" ]; enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true; # this contains both the NetworkManager service and the NetworkManager-dispatcher service # the latter of which calls a lot of user code. # as a result, this needs all the perms which my hook in modules/services/trust-dns/trust-dns-nmhook needs. sandbox.method = "landlock"; sandbox.capabilities = [ # "dac_override" "net_admin" "net_raw" "net_bind_service" #< TODO: is this needed? why? (DNS?) # "sys_module" "audit_write" #< allow writing to the audit log # "kill" ]; sandbox.extraPaths = [ "/proc/net" "/proc/sys/net" "/run/NetworkManager" "/run/systemd" # for trust-dns-nmhook "/run/udev" # "/run/wg-home.priv" "/sys/class" #< TODO: specify this more precisely "/sys/devices" "/var/lib/NetworkManager" "/var/lib/trust-dns" #< for trust-dns-nmhook ]; sandbox.whitelistDbus = [ "system" ]; }; } (lib.mkIf cfg.enabled { # add to systemd.packages so we get the service file it ships, then override what we need to customize (taken from nixpkgs) systemd.packages = [ cfg.package ]; systemd.services.NetworkManager = { wantedBy = [ "network.target" ]; aliases = [ "dbus-org.freedesktop.NetworkManager.service" ]; path = [ "/run/current-system/sw" ]; #< so it can find `sanebox` serviceConfig.RuntimeDirectory = "NetworkManager"; #< tells systemd to create /run/NetworkManager serviceConfig.StateDirectory = "NetworkManager"; #< tells systemd to create /var/lib/NetworkManager serviceConfig.User = "networkmanager"; serviceConfig.Group = "networkmanager"; serviceConfig.AmbientCapabilities = [ # "CAP_DAC_OVERRIDE" "CAP_NET_ADMIN" "CAP_NET_RAW" "CAP_NET_BIND_SERVICE" #< this *does* seem to be necessary, though i don't understand why. DHCP? # "CAP_SYS_MODULE" "CAP_AUDIT_WRITE" #< allow writing to the audit log # "CAP_KILL" ]; }; systemd.services.NetworkManager-wait-online = { path = [ "/run/current-system/sw" ]; #< so `nm-online` can find `sanebox` wantedBy = [ "network-online.target" ]; serviceConfig.User = "networkmanager"; serviceConfig.Group = "networkmanager"; }; systemd.services.NetworkManager-dispatcher = { wantedBy = [ "NetworkManager.service" ]; after = [ "trust-dns-localhost.service" ]; #< so that /var/lib/trust-dns will exist path = [ "/run/current-system/sw" ]; #< so it can find `sanebox` # to debug, add NM_DISPATCHER_DEBUG_LOG=1 serviceConfig.ExecStart = [ "" # first blank line is to clear the upstream `ExecStart` field. "${cfg.package}/libexec/nm-dispatcher --persist" # --persist is needed for it to actually run as a daemon ]; serviceConfig.Restart = "always"; serviceConfig.RestartSec = "1s"; serviceConfig.User = "networkmanager"; serviceConfig.Group = "networkmanager"; }; environment.etc = { "NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections"; "NetworkManager/NetworkManager.conf".text = '' [device] # wifi.backend: wpa_supplicant or iwd wifi.backend=wpa_supplicant wifi.scan-rand-mac-address=true [logging] audit=false # level: TRACE, DEBUG, INFO, WARN, ERR, OFF level=INFO # domain=... [main] # dhcp: # - `internal` (default) # - `dhclient` (requires dhclient to be installed) # - `dhcpcd` (requires dhcpcd to be installed) dhcp=internal # dns: # - `default`: update /etc/resolv.conf with nameservers provided by the active connection # - `none`: NM won't update /etc/resolv.conf # - `systemd-resolved`: push DNS config to systemd-resolved # - `dnsmasq`: run a local caching nameserver dns=${if config.services.resolved.enable then "systemd-resolved" else if config.sane.services.trust-dns.enable && config.sane.services.trust-dns.asSystemResolver then "none" else "internal" } plugins=keyfile # rc-manager: how NM should write to /etc/resolv.conf # - regardless of this setting, NM will write /var/lib/NetworkManager/resolv.conf rc-manager=unmanaged # systemd-resolved: send DNS config to systemd-resolved? # this setting has no effect if dns="systemd-resolved"; it's supplementary, not absolute. systemd-resolved=false # debug=... (see also: NM_DEBUG env var) ''; }; hardware.wirelessRegulatoryDatabase = true; networking.useDHCP = false; services.udev.packages = [ cfg.package ]; security.polkit.enable = lib.mkDefault true; security.polkit.extraConfig = lib.concatStringsSep "\n" [ # allow networkmanager unbounded control over modemmanager. # i believe this was sourced from the default nixpkgs config. '' polkit.addRule(function(action, subject) { if (subject.isInGroup("networkmanager") && ( action.id.indexOf("org.freedesktop.NetworkManager.") == 0 || action.id.indexOf("org.freedesktop.ModemManager") == 0 ) ) { return polkit.Result.YES; } }); '' # allow networkmanager to control systemd-resolved, # which it needs to do to apply new DNS settings when using systemd-resolved. '' polkit.addRule(function(action, subject) { if (subject.isInGroup("networkmanager") && action.id.indexOf("org.freedesktop.resolve1.") == 0) { return polkit.Result.YES; } }); '' ]; users.groups.networkmanager.gid = config.ids.gids.networkmanager; users.users.networkmanager = { isSystemUser = true; group = "networkmanager"; extraGroups = [ "trust-dns" ]; }; boot.kernelModules = [ "ctr" ]; #< TODO: needed (what even is this)? # TODO: NetworkManager-ensure-profiles? }) ]; }