Files
nix-config/modules/nixos/network/default.nix
2026-04-05 19:10:23 -05:00

214 lines
6.6 KiB
Nix

{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.network;
profiles =
let
make =
name: profile:
nameValuePair "${name}" {
connection = {
inherit (profile) type autoconnect autoconnect-retries;
id = name;
autoconnect-priority = profile.priority;
interface-name = profile.interface or cfg.ipv4.interface;
};
ipv4 = {
inherit (cfg.ipv4) method;
}
// (
if (cfg.ipv4.method == "auto") then
{ }
else
{
inherit (cfg.ipv4) address gateway dns;
}
);
ipv6 = {
addr-gen-mode = "stable-privacy";
method = "auto";
};
wifi = mkIf (profile.type == "wifi") {
inherit (profile) ssid;
mode = "infrastructure";
roaming = "allowed";
};
wifi-security = mkIf (profile.type == "wifi") {
inherit (profile) psk;
key-mgmt = profile.keyMgmt;
};
};
userProfiles = mapAttrs' make cfg.networkmanager.profiles;
# When using manual IP with NM enabled, auto-generate an ethernet profile
# so NM actually assigns the static address (not just DHCP).
ethernetProfile =
optionalAttrs (cfg.ipv4.method == "manual" && cfg.ipv4.interface != "" && cfg.networkmanager.enable)
{
"static-${cfg.ipv4.interface}" = {
connection = {
id = "static-${cfg.ipv4.interface}";
type = "ethernet";
autoconnect = true;
interface-name = cfg.ipv4.interface;
};
ipv4 = {
inherit (cfg.ipv4) address gateway dns;
method = "manual";
};
ipv6 = {
addr-gen-mode = "stable-privacy";
method = "auto";
};
};
};
in
userProfiles // ethernetProfile;
in
{
imports = [
./options.nix
];
config = {
assertions = [
{
assertion = cfg.hostName != "";
message = "mjallen.network.hostName must be set to a non-empty string.";
}
{
assertion = cfg.ipv4.method == "auto" || cfg.ipv4.method == "manual";
message = "mjallen.network.ipv4.method must be either \"auto\" or \"manual\" (got \"${cfg.ipv4.method}\").";
}
{
assertion = cfg.ipv4.method != "manual" || cfg.ipv4.interface != "";
message = "mjallen.network.ipv4.interface must be set when ipv4.method is \"manual\".";
}
{
assertion = cfg.ipv4.method != "manual" || cfg.ipv4.address != "";
message = "mjallen.network.ipv4.address must be set when ipv4.method is \"manual\".";
}
{
assertion = cfg.ipv4.method != "manual" || cfg.ipv4.gateway != "";
message = "mjallen.network.ipv4.gateway must be set when ipv4.method is \"manual\".";
}
{
assertion = cfg.nat.enable -> cfg.nat.externalInterface != "";
message = "mjallen.network.nat.externalInterface must be set when NAT is enabled.";
}
];
systemd = {
services = {
NetworkManager-wait-online.enable = false;
systemd-networkd-wait-online.enable = lib.mkForce false;
systemd-networkd.stopIfChanged = false;
systemd-resolved.stopIfChanged = false;
};
network.wait-online.enable = false;
};
# Restrict Avahi to the configured LAN interface when one is explicitly set.
# This prevents Avahi from announcing on virtual/container interfaces (veth*,
# podman0, virbr0, etc.) which causes hostname conflicts and suffix mangling
# (e.g. "jallen-nas-4.local" instead of "jallen-nas.local").
services.avahi = lib.mkIf (cfg.ipv4.interface != "") {
allowInterfaces = [ cfg.ipv4.interface ];
};
networking = {
hostName = lib.mkForce cfg.hostName;
# Use networkd if enabled
useNetworkd = lib.mkDefault true;
# Set default gateway and nameservers if in manual mode
defaultGateway = lib.mkIf (cfg.ipv4.method == "manual") {
address = cfg.ipv4.gateway;
interface = lib.mkIf (cfg.ipv4.interface != "") cfg.ipv4.interface;
};
nameservers = lib.mkIf (cfg.ipv4.method == "manual") [ cfg.ipv4.dns ];
# Set hostId if provided
hostId = lib.mkIf (cfg.hostId != "") cfg.hostId;
# Configure NAT if enabled
nat = lib.mkIf cfg.nat.enable {
inherit (cfg.nat) internalInterfaces externalInterface enableIPv6;
enable = true;
};
# Configure firewall
firewall = {
inherit (cfg.firewall)
enable
allowPing
allowedTCPPorts
allowedUDPPorts
trustedInterfaces
;
# Default port ranges for KDE Connect
allowedTCPPortRanges = lib.mkIf cfg.firewall.kdeConnect.enable [
{
inherit (cfg.firewall.kdeConnect.tcpRange) from to;
}
];
allowedUDPPortRanges = lib.mkIf cfg.firewall.kdeConnect.enable [
{
inherit (cfg.firewall.kdeConnect.udpRange) from to;
}
];
# Extra firewall commands
extraCommands = lib.mkIf (cfg.extraFirewallCommands != "") cfg.extraFirewallCommands;
};
# Enable iwd daemon when requested.
# When iwd is enabled alongside NetworkManager, iwd acts as the WiFi
# backend for NM (iwd handles scanning/association; NM handles
# connection management). They are not mutually exclusive.
wireless.iwd = {
inherit (cfg.iwd)
enable
settings
;
};
# Configure NetworkManager when enabled
networkmanager = {
inherit (cfg.networkmanager) enable;
# Use iwd as the WiFi backend when iwd is also enabled
wifi.backend = mkIf cfg.iwd.enable "iwd";
wifi.powersave = cfg.networkmanager.powersave;
settings.connectivity.uri = "http://nmcheck.gnome.org/check_network_status.txt";
plugins = with pkgs; [
networkmanager-fortisslvpn
networkmanager-iodine
networkmanager-l2tp
networkmanager-openconnect
networkmanager-openvpn
networkmanager-sstp
networkmanager-strongswan
networkmanager-vpnc
];
# Configure profiles if any are defined
ensureProfiles = mkIf (profiles != { }) {
environmentFiles = lib.optional (config.sops.secrets ? wifi) config.sops.secrets.wifi.path;
inherit profiles;
};
};
};
};
}