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: config: with config; let
12 key = if psk != null
13 then ''"${psk}"''
14 else pskRaw;
15 baseAuth = if key != null
16 then ''psk=${key}''
17 else ''key_mgmt=NONE'';
18 in ''
19 network={
20 ssid="${ssid}"
21 ${optionalString (priority != null) ''priority=${toString priority}''}
22 ${optionalString hidden "scan_ssid=1"}
23 ${if (auth != null) then auth else baseAuth}
24 ${extraConfig}
25 }
26 '') cfg.networks)}
27 '' else "/etc/wpa_supplicant.conf";
28in {
29 options = {
30 networking.wireless = {
31 enable = mkEnableOption "wpa_supplicant";
32
33 interfaces = mkOption {
34 type = types.listOf types.str;
35 default = [];
36 example = [ "wlan0" "wlan1" ];
37 description = ''
38 The interfaces <command>wpa_supplicant</command> will use. If empty, it will
39 automatically use all wireless interfaces.
40 '';
41 };
42
43 driver = mkOption {
44 type = types.str;
45 default = "nl80211,wext";
46 description = "Force a specific wpa_supplicant driver.";
47 };
48
49 networks = mkOption {
50 type = types.attrsOf (types.submodule {
51 options = {
52 psk = mkOption {
53 type = types.nullOr types.str;
54 default = null;
55 description = ''
56 The network's pre-shared key in plaintext defaulting
57 to being a network without any authentication.
58
59 Be aware that these will be written to the nix store
60 in plaintext!
61
62 Mutually exclusive with <varname>pskRaw</varname>.
63 '';
64 };
65
66 pskRaw = mkOption {
67 type = types.nullOr types.str;
68 default = null;
69 description = ''
70 The network's pre-shared key in hex defaulting
71 to being a network without any authentication.
72
73 Mutually exclusive with <varname>psk</varname>.
74 '';
75 };
76
77 auth = mkOption {
78 type = types.nullOr types.str;
79 default = null;
80 example = ''
81 key_mgmt=WPA-EAP
82 eap=PEAP
83 identity="user@example.com"
84 password="secret"
85 '';
86 description = ''
87 Use this option to configure advanced authentication methods like EAP.
88 See wpa_supplicant.conf(5) for example configurations.
89
90 Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
91 '';
92 };
93
94 hidden = mkOption {
95 type = types.bool;
96 default = false;
97 description = ''
98 Set this to <literal>true</literal> if the SSID of the network is hidden.
99 '';
100 };
101
102 priority = mkOption {
103 type = types.nullOr types.int;
104 default = null;
105 description = ''
106 By default, all networks will get same priority group (0). If some of the
107 networks are more desirable, this field can be used to change the order in
108 which wpa_supplicant goes through the networks when selecting a BSS. The
109 priority groups will be iterated in decreasing priority (i.e., the larger the
110 priority value, the sooner the network is matched against the scan results).
111 Within each priority group, networks will be selected based on security
112 policy, signal strength, etc.
113 '';
114 };
115
116 extraConfig = mkOption {
117 type = types.str;
118 default = "";
119 example = ''
120 bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66
121 '';
122 description = ''
123 Extra configuration lines appended to the network block.
124 See wpa_supplicant.conf(5) for available options.
125 '';
126 };
127
128 };
129 });
130 description = ''
131 The network definitions to automatically connect to when
132 <command>wpa_supplicant</command> is running. If this
133 parameter is left empty wpa_supplicant will use
134 /etc/wpa_supplicant.conf as the configuration file.
135 '';
136 default = {};
137 example = literalExample ''
138 { echelon = {
139 psk = "abcdefgh";
140 };
141 "free.wifi" = {};
142 }
143 '';
144 };
145
146 userControlled = {
147 enable = mkOption {
148 type = types.bool;
149 default = false;
150 description = ''
151 Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
152 This is useful for laptop users that switch networks a lot and don't want
153 to depend on a large package such as NetworkManager just to pick nearby
154 access points.
155
156 When using a declarative network specification you cannot persist any
157 settings via wpa_gui or wpa_cli.
158 '';
159 };
160
161 group = mkOption {
162 type = types.str;
163 default = "wheel";
164 example = "network";
165 description = "Members of this group can control wpa_supplicant.";
166 };
167 };
168 };
169 };
170
171 config = mkIf cfg.enable {
172 assertions = flip mapAttrsToList cfg.networks (name: cfg: {
173 assertion = with cfg; count (x: x != null) [ psk pskRaw auth ] <= 1;
174 message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive'';
175 });
176
177 environment.systemPackages = [ pkgs.wpa_supplicant ];
178
179 services.dbus.packages = [ pkgs.wpa_supplicant ];
180
181 # FIXME: start a separate wpa_supplicant instance per interface.
182 systemd.services.wpa_supplicant = let
183 ifaces = cfg.interfaces;
184 deviceUnit = interface: [ "sys-subsystem-net-devices-${interface}.device" ];
185 in {
186 description = "WPA Supplicant";
187
188 after = lib.concatMap deviceUnit ifaces;
189 before = [ "network.target" ];
190 wants = [ "network.target" ];
191 requires = lib.concatMap deviceUnit ifaces;
192 wantedBy = [ "multi-user.target" ];
193 stopIfChanged = false;
194
195 path = [ pkgs.wpa_supplicant ];
196
197 script = ''
198 ${if ifaces == [] then ''
199 for i in $(cd /sys/class/net && echo *); do
200 DEVTYPE=
201 source /sys/class/net/$i/uevent
202 if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then
203 ifaces="$ifaces''${ifaces:+ -N} -i$i"
204 fi
205 done
206 '' else ''
207 ifaces="${concatStringsSep " -N " (map (i: "-i${i}") ifaces)}"
208 ''}
209 exec wpa_supplicant -s -u -D${cfg.driver} -c ${configFile} $ifaces
210 '';
211 };
212
213 powerManagement.resumeCommands = ''
214 ${config.systemd.package}/bin/systemctl try-restart wpa_supplicant
215 '';
216
217 # Restart wpa_supplicant when a wlan device appears or disappears.
218 services.udev.extraRules = ''
219 ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="${config.systemd.package}/bin/systemctl try-restart wpa_supplicant.service"
220 '';
221 };
222
223 meta.maintainers = with lib.maintainers; [ globin ];
224}