1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.networking.wireless; 7 configFile = if cfg.networks != {} then pkgs.writeText "wpa_supplicant.conf" '' 8 ${optionalString cfg.userControlled.enable '' 9 ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=${cfg.userControlled.group} 10 update_config=1''} 11 ${concatStringsSep "\n" (mapAttrsToList (ssid: networkConfig: let 12 psk = if networkConfig.psk != null 13 then ''"${networkConfig.psk}"'' 14 else networkConfig.pskRaw; 15 in '' 16 network={ 17 ssid="${ssid}" 18 ${optionalString (psk != null) ''psk=${psk}''} 19 ${optionalString (psk == null) ''key_mgmt=NONE''} 20 } 21 '') cfg.networks)} 22 '' else "/etc/wpa_supplicant.conf"; 23in { 24 options = { 25 networking.wireless = { 26 enable = mkEnableOption "wpa_supplicant"; 27 28 interfaces = mkOption { 29 type = types.listOf types.str; 30 default = []; 31 example = [ "wlan0" "wlan1" ]; 32 description = '' 33 The interfaces <command>wpa_supplicant</command> will use. If empty, it will 34 automatically use all wireless interfaces. 35 ''; 36 }; 37 38 driver = mkOption { 39 type = types.str; 40 default = "nl80211,wext"; 41 description = "Force a specific wpa_supplicant driver."; 42 }; 43 44 networks = mkOption { 45 type = types.attrsOf (types.submodule { 46 options = { 47 psk = mkOption { 48 type = types.nullOr types.str; 49 default = null; 50 description = '' 51 The network's pre-shared key in plaintext defaulting 52 to being a network without any authentication. 53 54 Be aware that these will be written to the nix store 55 in plaintext! 56 57 Mutually exclusive with <varname>pskRaw</varname>. 58 ''; 59 }; 60 61 pskRaw = mkOption { 62 type = types.nullOr types.str; 63 default = null; 64 description = '' 65 The network's pre-shared key in hex defaulting 66 to being a network without any authentication. 67 68 Mutually exclusive with <varname>psk</varname>. 69 ''; 70 }; 71 }; 72 }); 73 description = '' 74 The network definitions to automatically connect to when 75 <command>wpa_supplicant</command> is running. If this 76 parameter is left empty wpa_supplicant will use 77 /etc/wpa_supplicant.conf as the configuration file. 78 ''; 79 default = {}; 80 example = literalExample '' 81 echelon = { 82 psk = "abcdefgh"; 83 }; 84 "free.wifi" = {}; 85 ''; 86 }; 87 88 userControlled = { 89 enable = mkOption { 90 type = types.bool; 91 default = false; 92 description = '' 93 Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli. 94 This is useful for laptop users that switch networks a lot and don't want 95 to depend on a large package such as NetworkManager just to pick nearby 96 access points. 97 98 When using a declarative network specification you cannot persist any 99 settings via wpa_gui or wpa_cli. 100 ''; 101 }; 102 103 group = mkOption { 104 type = types.str; 105 default = "wheel"; 106 example = "network"; 107 description = "Members of this group can control wpa_supplicant."; 108 }; 109 }; 110 }; 111 }; 112 113 config = mkMerge [ 114 (mkIf cfg.enable { 115 assertions = flip mapAttrsToList cfg.networks (name: cfg: { 116 assertion = cfg.psk == null || cfg.pskRaw == null; 117 message = ''networking.wireless."${name}".psk and networking.wireless."${name}".pskRaw are mutually exclusive''; 118 }); 119 120 environment.systemPackages = [ pkgs.wpa_supplicant ]; 121 122 services.dbus.packages = [ pkgs.wpa_supplicant ]; 123 124 # FIXME: start a separate wpa_supplicant instance per interface. 125 systemd.services.wpa_supplicant = let 126 ifaces = cfg.interfaces; 127 in { 128 description = "WPA Supplicant"; 129 130 after = [ "network-interfaces.target" ]; 131 wantedBy = [ "network.target" ]; 132 133 path = [ pkgs.wpa_supplicant ]; 134 135 script = '' 136 ${if ifaces == [] then '' 137 for i in $(cd /sys/class/net && echo *); do 138 DEVTYPE= 139 source /sys/class/net/$i/uevent 140 if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then 141 ifaces="$ifaces''${ifaces:+ -N} -i$i" 142 fi 143 done 144 '' else '' 145 ifaces="${concatStringsSep " -N " (map (i: "-i${i}") ifaces)}" 146 ''} 147 exec wpa_supplicant -s -u -D${cfg.driver} -c ${configFile} $ifaces 148 ''; 149 }; 150 151 powerManagement.resumeCommands = '' 152 ${config.systemd.package}/bin/systemctl try-restart wpa_supplicant 153 ''; 154 155 # Restart wpa_supplicant when a wlan device appears or disappears. 156 services.udev.extraRules = '' 157 ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="${config.systemd.package}/bin/systemctl try-restart wpa_supplicant.service" 158 ''; 159 }) 160 { 161 meta.maintainers = with lib.maintainers; [ globin ]; 162 } 163 ]; 164}