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