nix-files/hosts/common/programs/networkmanager.nix

190 lines
7.6 KiB
Nix

# 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?
})
];
}