at master 11 kB view raw
1{ 2 lib, 3 config, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.networking.ifstate; 10 initrdCfg = config.boot.initrd.network.ifstate; 11 settingsFormat = { 12 # override generator in order to: 13 # - use yq and not remarshal because it matches yaml datatype handling with IfState 14 # - validate json schema 15 generate = 16 name: value: package: 17 pkgs.runCommand name 18 { 19 nativeBuildInputs = with pkgs; [ 20 yq 21 check-jsonschema 22 ]; 23 value = builtins.toJSON value; 24 passAsFile = [ "value" ]; 25 } 26 '' 27 yq --yaml-output . $valuePath > $out 28 check-jsonschema --schemafile "${cfg.package.passthru.jsonschema}" "$out" 29 sed -i $'s|\'!include |!include \'|' $out 30 ''; 31 32 inherit (pkgs.formats.yaml { }) type; 33 }; 34 initrdInterfaceTypes = builtins.map (interface: interface.link.kind) ( 35 builtins.attrValues initrdCfg.settings.interfaces 36 ); 37 # IfState interface kind to kernel modules mapping 38 interfaceKernelModules = { 39 "ifb" = [ "ifb" ]; 40 "ip6tnl" = [ "ip6tnl" ]; 41 "ipoib" = [ "ib_ipoib" ]; 42 "ipvlan" = [ "ipvlan" ]; 43 "macvlan" = [ "macvlan" ]; 44 "macvtap" = [ "macvtap" ]; 45 "team" = [ "team" ]; 46 "tun" = [ "tun" ]; 47 "vrf" = [ "vrf" ]; 48 "vti" = [ "ip_vti" ]; 49 "vti6" = [ "ip6_vti" ]; 50 "bond" = [ "bonding" ]; 51 "bridge" = [ "bridge" ]; 52 # "physical" = ...; 53 "dsa" = [ "dsa_core" ]; 54 "dummy" = [ "dummy" ]; 55 "veth" = [ "veth" ]; 56 "vxcan" = [ "vxcan" ]; 57 "vlan" = [ "8021q" ]; 58 "vxlan" = [ "vxlan" ]; 59 "ipip" = [ "ipip" ]; 60 "sit" = [ "sit" ]; 61 "gre" = [ "ip_gre" ]; 62 "gretap" = [ "ip_gre" ]; 63 "ip6gre" = [ "ip6_gre" ]; 64 "ip6gretap" = [ "ip6_gre" ]; 65 "geneve" = [ "geneve" ]; 66 "wireguard" = [ "wireguard" ]; 67 "xfrm" = [ "xfrm_interface" ]; 68 }; 69 # https://github.com/systemd/systemd/blob/main/units/systemd-networkd.service.in 70 commonServiceConfig = { 71 after = [ 72 "systemd-udevd.service" 73 "network-pre.target" 74 "systemd-sysusers.service" 75 "systemd-sysctl.service" 76 ]; 77 before = [ 78 "network.target" 79 "multi-user.target" 80 "shutdown.target" 81 "initrd-switch-root.target" 82 ]; 83 conflicts = [ 84 "shutdown.target" 85 "initrd-switch-root.target" 86 ]; 87 wants = [ 88 "network.target" 89 ]; 90 91 unitConfig = { 92 # Avoid default dependencies like "basic.target", which prevents ifstate from starting before luks is unlocked. 93 DefaultDependencies = "no"; 94 }; 95 }; 96in 97{ 98 meta.maintainers = with lib.maintainers; [ marcel ]; 99 100 options = { 101 networking.ifstate = { 102 enable = lib.mkEnableOption "networking using IfState"; 103 104 package = lib.mkPackageOption pkgs "ifstate" { }; 105 106 settings = lib.mkOption { 107 inherit (settingsFormat) type; 108 default = { }; 109 description = "Content of IfState's configuration file. See <https://ifstate.net/2.0/schema/> for details."; 110 }; 111 }; 112 113 boot.initrd.network.ifstate = { 114 enable = lib.mkEnableOption "initrd networking using IfState"; 115 116 allowIfstateToDrasticlyIncreaseInitrdSize = lib.mkOption { 117 type = lib.types.bool; 118 default = false; 119 description = "IfState in initrd drastically increases the size of initrd, your boot partition may be too small and/or you may have significantly fewer generations. By setting this option, you acknowledge this fact and keep it in mind when reporting issues."; 120 }; 121 122 package = lib.mkOption { 123 type = lib.types.package; 124 default = cfg.package.override { 125 withConfigValidation = false; 126 withWireguard = false; 127 }; 128 defaultText = lib.literalExpression "pkgs.ifstate.override { withConfigValidation = false; withWireguard = false; }"; 129 description = "The initrd IfState package to use."; 130 }; 131 132 settings = lib.mkOption { 133 inherit (settingsFormat) type; 134 default = { }; 135 description = "Content of IfState's initrd configuration file. See <https://ifstate.net/2.0/schema/> for details."; 136 }; 137 138 cleanupSettings = lib.mkOption { 139 inherit (settingsFormat) type; 140 # required by json schema 141 default.interfaces = { }; 142 description = "Content of IfState's initrd cleanup configuration file. See <https://ifstate.net/2.0/schema/> for details. This configuration gets applied before systemd switches to stage two. The goas is to deconfigurate the whole network in order to prevent access to services, before the firewall is configured. The stage two IfState configuration will start after the firewall is configured."; 143 }; 144 }; 145 }; 146 147 config = lib.mkMerge [ 148 (lib.mkIf cfg.enable { 149 assertions = [ 150 { 151 assertion = !config.networking.networkmanager.enable; 152 message = "IfState and NetworkManager cannot be used at the same time, as both configure the network in a conflicting manner."; 153 } 154 { 155 assertion = !config.networking.useDHCP; 156 message = "IfState and networking.useDHCP cannot be used at the same time, as both configure the network. Please look into IfState hooks to integrate DHCP: https://codeberg.org/liske/ifstate/issues/111"; 157 } 158 ]; 159 160 networking.useDHCP = lib.mkDefault false; 161 162 # sane defaults to not let IfState work against the kernel 163 boot.extraModprobeConfig = '' 164 options bonding max_bonds=0 165 options dummy numdummies=0 166 options ifb numifbs=0 167 ''; 168 169 environment = { 170 # ifstatecli command should be available to use user, there are other useful subcommands like check or show 171 systemPackages = [ cfg.package ]; 172 # match the default value of the --config flag of IfState 173 etc."ifstate/ifstate.yaml".source = settingsFormat.generate "ifstate.yaml" cfg.settings cfg.package; 174 }; 175 176 systemd.services.ifstate = commonServiceConfig // { 177 description = "IfState"; 178 179 wantedBy = [ 180 "multi-user.target" 181 ]; 182 183 # mount is always available on nixos, avoid adding additional store paths to the closure 184 path = [ "/run/wrappers" ]; 185 186 serviceConfig = { 187 Type = "oneshot"; 188 ExecStart = "${lib.getExe cfg.package} --config ${ 189 config.environment.etc."ifstate/ifstate.yaml".source 190 } apply"; 191 # because oneshot services do not have a timeout by default 192 TimeoutStartSec = "2min"; 193 }; 194 }; 195 }) 196 (lib.mkIf initrdCfg.enable { 197 assertions = [ 198 { 199 assertion = 200 initrdCfg.package.passthru.features.withWireguard 201 || !(builtins.any (kind: kind == "wireguard") initrdInterfaceTypes); 202 message = "IfState initrd package is configured without the `wireguard` feature, but wireguard interfaces are configured. Please see the `boot.initrd.network.ifstate.package` option."; 203 } 204 { 205 assertion = initrdCfg.allowIfstateToDrasticlyIncreaseInitrdSize; 206 message = "IfState in initrd drastically increases the size of initrd, your boot partition may be too small and/or you may have significantly fewer generations. By setting boot.initrd.network.initrd.allowIfstateToDrasticlyIncreaseInitrdSize to true, you acknowledge this fact and keep it in mind when reporting issues."; 207 } 208 { 209 assertion = cfg.enable; 210 message = "If IfState is used in initrd, it should also be used for the stage 2 system (networking.ifstate), as initrd IfState does not clean up the network stack like it was before after execution."; 211 } 212 { 213 assertion = config.boot.initrd.systemd.enable; 214 message = "IfState only supports systemd stage one. See `boot.initrd.systemd.enable` option."; 215 } 216 ]; 217 218 environment.etc = { 219 "ifstate/ifstate.initrd.yaml".source = 220 settingsFormat.generate "ifstate.initrd.yaml" initrdCfg.settings 221 initrdCfg.package; 222 "ifstate/ifstate.initrd-cleanup.yaml".source = 223 settingsFormat.generate "ifstate.initrd-cleanup.yaml" initrdCfg.cleanupSettings 224 initrdCfg.package; 225 }; 226 227 boot.initrd = { 228 network.udhcpc.enable = lib.mkDefault false; 229 230 # automatic configuration of kernel modules of virtual interface types 231 availableKernelModules = 232 let 233 enableModule = 234 type: 235 if builtins.hasAttr type interfaceKernelModules then interfaceKernelModules."${type}" else [ ]; 236 in 237 lib.flatten (builtins.map enableModule initrdInterfaceTypes); 238 239 systemd = { 240 storePaths = [ 241 (pkgs.runCommand "ifstate-closure" 242 { 243 info = pkgs.closureInfo { 244 rootPaths = [ 245 initrdCfg.package 246 # copy whole config closure, because it can reference other files using !include 247 config.environment.etc."ifstate/ifstate.initrd.yaml".source 248 config.environment.etc."ifstate/ifstate.initrd-cleanup.yaml".source 249 ]; 250 }; 251 } 252 '' 253 mkdir $out 254 cat "$info"/store-paths | while read path; do 255 ln -s "$path" "$out/$(basename "$path")" 256 done 257 '' 258 ) 259 ]; 260 261 # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/system/boot/networkd.nix#L3444 262 additionalUpstreamUnits = [ 263 "network-online.target" 264 "network-pre.target" 265 "network.target" 266 "nss-lookup.target" 267 "nss-user-lookup.target" 268 "remote-fs-pre.target" 269 "remote-fs.target" 270 ]; 271 272 services.ifstate-initrd = commonServiceConfig // { 273 description = "IfState initrd"; 274 275 wantedBy = [ 276 "initrd.target" 277 ]; 278 279 # mount is always available on nixos, avoid adding additional store paths to the closure 280 # https://github.com/NixOS/nixpkgs/blob/2b8e2457ebe576ebf41ddfa8452b5b07a8d493ad/nixos/modules/system/boot/systemd/initrd.nix#L550-L551 281 path = [ 282 config.boot.initrd.systemd.package.util-linux 283 ]; 284 285 serviceConfig = { 286 Type = "oneshot"; 287 # Otherwise systemd starts ifstate again, after the encryption password was entered by the user 288 # and we are able to implement the cleanup using ExecStop rather than a separate unit. 289 RemainAfterExit = true; 290 ExecStart = "${lib.getExe initrdCfg.package} --config ${ 291 config.environment.etc."ifstate/ifstate.initrd.yaml".source 292 } apply"; 293 ExecStop = "${lib.getExe initrdCfg.package} --config ${ 294 config.environment.etc."ifstate/ifstate.initrd-cleanup.yaml".source 295 } apply"; 296 # because oneshot services do not have a timeout by default 297 TimeoutStartSec = "2min"; 298 }; 299 }; 300 }; 301 }; 302 }) 303 ]; 304}