From a587e539e926ee2c45e3fe58e4dcd4cb1c3c9348 Mon Sep 17 00:00:00 2001 From: Gabriel Fontes Date: Thu, 13 Aug 2026 13:43:32 -0300 Subject: [PATCH] feat(system-manager): port networking.wireless and generic DHCP Run the upstream NixOS wpa_supplicant module under system-manager through a compat shim. system-manager already reuses nixpkgs' systemdUtils, environment.etc and userborn, so the unit, the generated config and the wpa_supplicant user come out unchanged; only four NixOS-only options needed stubbing, two of which are re-expressed on top of environment.etc. Extract the wireless config to hosts/wireless.nix so both trees share one source: they sit at the same depth, so every path resolves identically. electra's generated wpa_supplicant.conf is now byte-identical to atlas' except for the country line. Take over networkd from netplan on electra with the same generic match rules NixOS uses, which also covers ethernet and USB tethering, neither of which was configured before. netplan is neutered by masking its generator rather than uninstalling it, since purging netplan.io would take cloud-init, ubuntu-minimal and ubuntu-server-minimal with it. Assisted-by: pi (claude-opus-5) --- .../common/optional => }/eduroam-cert.pem | 0 hosts/nixos/common/optional/wireless.nix | 62 +---------------- .../system-manager/common/global/default.nix | 1 + .../system-manager/common/global/network.nix | 45 ++++++++++++ .../common/optional/wireless.nix | 18 +++++ .../common/users/gabriel/default.nix | 1 + hosts/system-manager/electra/cloud-init.yaml | 10 +++ hosts/system-manager/electra/default.nix | 6 ++ hosts/wireless.nix | 55 +++++++++++++++ modules/system-manager/default.nix | 1 + modules/system-manager/wireless.nix | 69 +++++++++++++++++++ 11 files changed, 209 insertions(+), 59 deletions(-) rename hosts/{nixos/common/optional => }/eduroam-cert.pem (100%) create mode 100644 hosts/system-manager/common/global/network.nix create mode 100644 hosts/system-manager/common/optional/wireless.nix create mode 100644 hosts/wireless.nix create mode 100644 modules/system-manager/wireless.nix diff --git a/hosts/nixos/common/optional/eduroam-cert.pem b/hosts/eduroam-cert.pem similarity index 100% rename from hosts/nixos/common/optional/eduroam-cert.pem rename to hosts/eduroam-cert.pem diff --git a/hosts/nixos/common/optional/wireless.nix b/hosts/nixos/common/optional/wireless.nix index 37a06194..367fa7b0 100644 --- a/hosts/nixos/common/optional/wireless.nix +++ b/hosts/nixos/common/optional/wireless.nix @@ -1,63 +1,7 @@ -{ - config, - ... -}: { +{...}: { + imports = [../../../wireless.nix]; + hardware.bluetooth = { enable = true; }; - - # Wireless secrets stored through sops - sops.secrets.wireless = { - sopsFile = ../../../secrets.yaml; - owner = config.users.users.wpa_supplicant.name; - group = config.users.users.wpa_supplicant.group; - }; - - networking.wireless = { - enable = true; - fallbackToWPA2 = false; - # Declarative - secretsFile = config.sops.secrets.wireless.path; - networks = { - "CAT_HOUSE" = { - pskRaw = "ext:cat_house"; - }; - "Marcos_2.4Ghz" = { - pskRaw = "ext:marcos_24"; - }; - "Marcos_5Ghz" = { - pskRaw = "ext:marcos_50"; - }; - "Misterio" = { - pskRaw = "ext:misterio"; - authProtocols = ["WPA-PSK"]; - # extraConfig = '' - # mesh_fwding=1 - # ''; - }; - "VIVOFIBRA-FC41-5G" = { - pskRaw = "ext:marcos_santos_5g"; - }; - "Nijland" = { - pskRaw = "ext:nijland"; - }; - "eduroam" = { - authProtocols = ["WPA-EAP"]; - auth = '' - pairwise=CCMP - group=CCMP TKIP - eap=TTLS - domain_suffix_match="semfio.usp.br" - ca_cert="${./eduroam-cert.pem}" - identity="10856803@usp.br" - password=ext:eduroam - phase2="auth=MSCHAPV2" - ''; - }; - }; - - # Imperative - allowAuxiliaryImperativeNetworks = true; - userControlled = true; - }; } diff --git a/hosts/system-manager/common/global/default.nix b/hosts/system-manager/common/global/default.nix index 6532ccc1..d3c4f834 100644 --- a/hosts/system-manager/common/global/default.nix +++ b/hosts/system-manager/common/global/default.nix @@ -15,6 +15,7 @@ in { inputs.home-manager.nixosModules.home-manager inputs.nix-system-graphics.systemModules.default ./greetd.nix + ./network.nix ./nix.nix ./pam.nix ./sops.nix diff --git a/hosts/system-manager/common/global/network.nix b/hosts/system-manager/common/global/network.nix new file mode 100644 index 00000000..d8aba9e5 --- /dev/null +++ b/hosts/system-manager/common/global/network.nix @@ -0,0 +1,45 @@ +{...}: { + # systemd-networkd has no implicit "manage everything" default: a link that + # matches no .network file stays unmanaged and is never even brought up. On + # NixOS this file is generated for you (nixos/modules/tasks/ + # network-interfaces-systemd.nix, genericDhcpNetworks); here netplan used to + # do it. These mirror the upstream match rules, so nothing is named + # explicitly and new hardware works on plug-in. + environment.etc = { + # Type=ether with no kind covers physical ethernet, USB dongles and USB + # tethered phones alike. + "systemd/network/99-ethernet-default-dhcp.network".text = '' + [Match] + Type=ether + Kind=!* + + [Network] + DHCP=yes + IPv6PrivacyExtensions=kernel + ''; + + # One above ethernet's default of 1024, so wired wins when both are up. + "systemd/network/99-wireless-client-dhcp.network".text = '' + [Match] + WLANInterfaceType=station + + [Network] + DHCP=yes + IPv6PrivacyExtensions=kernel + + [DHCPv4] + RouteMetric=1025 + + [IPv6AcceptRA] + RouteMetric=1025 + ''; + + # Neuter netplan without uninstalling it: purging netplan.io would take + # cloud-init, ubuntu-minimal and ubuntu-server-minimal with it. Masking the + # generator is reverted by `system-manager deactivate`, unlike apt state. + # + # NOTE: systemd.generators would be the natural home for this, but + # system-manager declares that option without ever wiring it up. + "systemd/system-generators/netplan".source = "/dev/null"; + }; +} diff --git a/hosts/system-manager/common/optional/wireless.nix b/hosts/system-manager/common/optional/wireless.nix new file mode 100644 index 00000000..9a4983c5 --- /dev/null +++ b/hosts/system-manager/common/optional/wireless.nix @@ -0,0 +1,18 @@ +{...}: { + imports = [../../../wireless.nix]; + + # The upstream module emits no country line; Ubuntu's netplan-generated + # config used to set this, and dropping it would silently relax the + # regulatory limits. + networking.wireless.extraConfig = "country=BR"; + + # Ubuntu ships two supplicants that would fight ours over the interface and + # over /run/wpa_supplicant (which our unit claims via RuntimeDirectory=): + # the templated one, and the DBus-activated one. The latter must be masked + # rather than merely disabled, since any client touching + # fi.w1.wpa_supplicant1 would otherwise start it again. + systemd.maskedUnits = [ + "wpa_supplicant.service" + "wpa_supplicant@.service" + ]; +} diff --git a/hosts/system-manager/common/users/gabriel/default.nix b/hosts/system-manager/common/users/gabriel/default.nix index 3d57c04c..165d1caa 100644 --- a/hosts/system-manager/common/users/gabriel/default.nix +++ b/hosts/system-manager/common/users/gabriel/default.nix @@ -34,6 +34,7 @@ in { "render" "sudo" "video" + "wpa_supplicant" ]; }; }; diff --git a/hosts/system-manager/electra/cloud-init.yaml b/hosts/system-manager/electra/cloud-init.yaml index 3186f3e7..02cd5731 100644 --- a/hosts/system-manager/electra/cloud-init.yaml +++ b/hosts/system-manager/electra/cloud-init.yaml @@ -37,6 +37,16 @@ autoinstall: - wpasupplicant runcmd: + # The installer's netplan wifi config is load-bearing for exactly one + # boot: the bootstrap below needs the network before system-manager + # exists to configure it. Once activated, system-manager owns + # /etc/systemd/network and wpa_supplicant (see hosts/system-manager/ + # common/optional/network.nix), and netplan's generator is masked. + # Stop cloud-init from re-rendering network config on later boots. + - | + printf 'network: {config: disabled}\n' \ + > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg + # This laptop must not expose an SSH server. Keep the client for Git and # ssh-keygen, but remove and mask the server even if the image included it. - | diff --git a/hosts/system-manager/electra/default.nix b/hosts/system-manager/electra/default.nix index c0fddf11..3c464c7b 100644 --- a/hosts/system-manager/electra/default.nix +++ b/hosts/system-manager/electra/default.nix @@ -2,8 +2,14 @@ imports = [ ../common/global ../common/users/gabriel + ../common/optional/wireless.nix ]; _module.args.systemManagerHostName = "electra"; nixpkgs.hostPlatform = "x86_64-linux"; + + # Pinned rather than auto-detected: with an empty list the upstream module + # installs a udev rule that calls /run/current-system/systemd/bin/systemctl, + # a path that does not exist outside NixOS. + networking.wireless.interfaces = ["wlp0s20f3"]; } diff --git a/hosts/wireless.nix b/hosts/wireless.nix new file mode 100644 index 00000000..87a3caaa --- /dev/null +++ b/hosts/wireless.nix @@ -0,0 +1,55 @@ +{config, ...}: { + sops.secrets.wireless = { + sopsFile = ./secrets.yaml; + owner = config.users.users.wpa_supplicant.name; + group = config.users.users.wpa_supplicant.group; + }; + + networking.wireless = { + enable = true; + fallbackToWPA2 = false; + # Declarative + secretsFile = config.sops.secrets.wireless.path; + networks = { + "CAT_HOUSE" = { + pskRaw = "ext:cat_house"; + }; + "Marcos_2.4Ghz" = { + pskRaw = "ext:marcos_24"; + }; + "Marcos_5Ghz" = { + pskRaw = "ext:marcos_50"; + }; + "Misterio" = { + pskRaw = "ext:misterio"; + authProtocols = ["WPA-PSK"]; + # extraConfig = '' + # mesh_fwding=1 + # ''; + }; + "VIVOFIBRA-FC41-5G" = { + pskRaw = "ext:marcos_santos_5g"; + }; + "Nijland" = { + pskRaw = "ext:nijland"; + }; + "eduroam" = { + authProtocols = ["WPA-EAP"]; + auth = '' + pairwise=CCMP + group=CCMP TKIP + eap=TTLS + domain_suffix_match="semfio.usp.br" + ca_cert="${./eduroam-cert.pem}" + identity="10856803@usp.br" + password=ext:eduroam + phase2="auth=MSCHAPV2" + ''; + }; + }; + + # Imperative + allowAuxiliaryImperativeNetworks = true; + userControlled = true; + }; +} diff --git a/modules/system-manager/default.nix b/modules/system-manager/default.nix index 3e02ad85..56cf6c5e 100644 --- a/modules/system-manager/default.nix +++ b/modules/system-manager/default.nix @@ -2,4 +2,5 @@ hydra-auto-upgrade = import ./hydra-auto-upgrade.nix; nix-registry = import ./nix-registry.nix; unix-chkpwd = import ./unix-chkpwd.nix; + wireless = import ./wireless.nix; } diff --git a/modules/system-manager/wireless.nix b/modules/system-manager/wireless.nix new file mode 100644 index 00000000..02176a2a --- /dev/null +++ b/modules/system-manager/wireless.nix @@ -0,0 +1,69 @@ +# Compat shim: lets the upstream NixOS `networking.wireless` (wpa_supplicant) +# module evaluate under system-manager. +# +# system-manager reuses nixpkgs' systemdUtils, environment.etc and userborn, so +# the unit, the generated wpa_supplicant.conf and the wpa_supplicant user all +# come out unchanged. Only a handful of NixOS-only options are missing: three +# are read but never acted on, and the two that do matter are re-expressed on +# top of environment.etc, which the host distro reads from the same paths. +{ + config, + lib, + inputs, + ... +}: let + cfg = config.services; +in { + imports = [ + "${inputs.nixpkgs}/nixos/modules/services/networking/wpa_supplicant.nix" + ]; + + options = { + # Set by the wpa_supplicant module; the host distro ships its own regdb + # (Ubuntu: /lib/firmware/regulatory.db). + hardware.wirelessRegulatoryDatabase = lib.mkOption { + type = lib.types.bool; + default = false; + internal = true; + }; + + # Only read by the module's assertions. + networking.networkmanager.enable = lib.mkOption { + type = lib.types.bool; + default = false; + internal = true; + }; + services.connman.enable = lib.mkOption { + type = lib.types.bool; + default = false; + internal = true; + }; + + services.dbus.packages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = []; + description = "Packages whose DBus system policy should be linked into /etc."; + }; + services.udev.extraRules = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Extra udev rules, written to /etc/udev/rules.d."; + }; + }; + + config = { + environment.etc = lib.mkMerge [ + (lib.mkIf (cfg.udev.extraRules != "") { + "udev/rules.d/99-system-manager.rules".text = cfg.udev.extraRules; + }) + # The attribute name may not carry string context, hence the discard; the + # value keeps its reference to the store path. + (lib.listToAttrs (map (file: { + name = "dbus-1/system.d/" + builtins.unsafeDiscardStringContext (baseNameOf file); + value = {source = file;}; + }) + (lib.concatMap (pkg: lib.filesystem.listFilesRecursive "${pkg}/share/dbus-1/system.d") + (lib.filter (pkg: builtins.pathExists "${pkg}/share/dbus-1/system.d") cfg.dbus.packages)))) + ]; + }; +}