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