1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.unifi-poller;
7
8 configFile = pkgs.writeText "unifi-poller.json" (generators.toJSON {} {
9 inherit (cfg) poller influxdb loki prometheus unifi;
10 });
11
12in {
13 options.services.unifi-poller = {
14 enable = mkEnableOption "unifi-poller";
15
16 poller = {
17 debug = mkOption {
18 type = types.bool;
19 default = false;
20 description = ''
21 Turns on line numbers, microsecond logging, and a per-device log.
22 This may be noisy if you have a lot of devices. It adds one line per device.
23 '';
24 };
25 quiet = mkOption {
26 type = types.bool;
27 default = false;
28 description = ''
29 Turns off per-interval logs. Only startup and error logs will be emitted.
30 '';
31 };
32 plugins = mkOption {
33 type = with types; listOf str;
34 default = [];
35 description = ''
36 Load additional plugins.
37 '';
38 };
39 };
40
41 prometheus = {
42 disable = mkOption {
43 type = types.bool;
44 default = false;
45 description = ''
46 Whether to disable the prometheus ouput plugin.
47 '';
48 };
49 http_listen = mkOption {
50 type = types.str;
51 default = "[::]:9130";
52 description = ''
53 Bind the prometheus exporter to this IP or hostname.
54 '';
55 };
56 report_errors = mkOption {
57 type = types.bool;
58 default = false;
59 description = ''
60 Whether to report errors.
61 '';
62 };
63 };
64
65 influxdb = {
66 disable = mkOption {
67 type = types.bool;
68 default = false;
69 description = ''
70 Whether to disable the influxdb ouput plugin.
71 '';
72 };
73 url = mkOption {
74 type = types.str;
75 default = "http://127.0.0.1:8086";
76 description = ''
77 URL of the influxdb host.
78 '';
79 };
80 user = mkOption {
81 type = types.str;
82 default = "unifipoller";
83 description = ''
84 Username for the influxdb.
85 '';
86 };
87 pass = mkOption {
88 type = types.path;
89 default = pkgs.writeText "unifi-poller-influxdb-default.password" "unifipoller";
90 defaultText = literalExpression "unifi-poller-influxdb-default.password";
91 description = ''
92 Path of a file containing the password for influxdb.
93 This file needs to be readable by the unifi-poller user.
94 '';
95 apply = v: "file://${v}";
96 };
97 db = mkOption {
98 type = types.str;
99 default = "unifi";
100 description = ''
101 Database name. Database should exist.
102 '';
103 };
104 verify_ssl = mkOption {
105 type = types.bool;
106 default = true;
107 description = ''
108 Verify the influxdb's certificate.
109 '';
110 };
111 interval = mkOption {
112 type = types.str;
113 default = "30s";
114 description = ''
115 Setting this lower than the Unifi controller's refresh
116 interval may lead to zeroes in your database.
117 '';
118 };
119 };
120
121 loki = {
122 url = mkOption {
123 type = types.str;
124 default = "";
125 description = ''
126 URL of the Loki host.
127 '';
128 };
129 user = mkOption {
130 type = types.str;
131 default = "";
132 description = ''
133 Username for Loki.
134 '';
135 };
136 pass = mkOption {
137 type = types.path;
138 default = pkgs.writeText "unifi-poller-loki-default.password" "";
139 defaultText = "unifi-poller-influxdb-default.password";
140 description = ''
141 Path of a file containing the password for Loki.
142 This file needs to be readable by the unifi-poller user.
143 '';
144 apply = v: "file://${v}";
145 };
146 verify_ssl = mkOption {
147 type = types.bool;
148 default = false;
149 description = ''
150 Verify Loki's certificate.
151 '';
152 };
153 tenant_id = mkOption {
154 type = types.str;
155 default = "";
156 description = ''
157 Tenant ID to use in Loki.
158 '';
159 };
160 interval = mkOption {
161 type = types.str;
162 default = "2m";
163 description = ''
164 How often the events are polled and pushed to Loki.
165 '';
166 };
167 timeout = mkOption {
168 type = types.str;
169 default = "10s";
170 description = ''
171 Should be increased in case of timeout errors.
172 '';
173 };
174 };
175
176 unifi = let
177 controllerOptions = {
178 user = mkOption {
179 type = types.str;
180 default = "unifi";
181 description = ''
182 Unifi service user name.
183 '';
184 };
185 pass = mkOption {
186 type = types.path;
187 default = pkgs.writeText "unifi-poller-unifi-default.password" "unifi";
188 defaultText = literalExpression "unifi-poller-unifi-default.password";
189 description = ''
190 Path of a file containing the password for the unifi service user.
191 This file needs to be readable by the unifi-poller user.
192 '';
193 apply = v: "file://${v}";
194 };
195 url = mkOption {
196 type = types.str;
197 default = "https://unifi:8443";
198 description = ''
199 URL of the Unifi controller.
200 '';
201 };
202 sites = mkOption {
203 type = with types; either (enum [ "default" "all" ]) (listOf str);
204 default = "all";
205 description = ''
206 List of site names for which statistics should be exported.
207 Or the string "default" for the default site or the string "all" for all sites.
208 '';
209 apply = toList;
210 };
211 save_ids = mkOption {
212 type = types.bool;
213 default = false;
214 description = ''
215 Collect and save data from the intrusion detection system to influxdb and Loki.
216 '';
217 };
218 save_events = mkOption {
219 type = types.bool;
220 default = false;
221 description = ''
222 Collect and save data from UniFi events to influxdb and Loki.
223 '';
224 };
225 save_alarms = mkOption {
226 type = types.bool;
227 default = false;
228 description = ''
229 Collect and save data from UniFi alarms to influxdb and Loki.
230 '';
231 };
232 save_anomalies = mkOption {
233 type = types.bool;
234 default = false;
235 description = ''
236 Collect and save data from UniFi anomalies to influxdb and Loki.
237 '';
238 };
239 save_dpi = mkOption {
240 type = types.bool;
241 default = false;
242 description = ''
243 Collect and save data from deep packet inspection.
244 Adds around 150 data points and impacts performance.
245 '';
246 };
247 save_sites = mkOption {
248 type = types.bool;
249 default = true;
250 description = ''
251 Collect and save site data.
252 '';
253 };
254 hash_pii = mkOption {
255 type = types.bool;
256 default = false;
257 description = ''
258 Hash, with md5, client names and MAC addresses. This attempts
259 to protect personally identifiable information.
260 '';
261 };
262 verify_ssl = mkOption {
263 type = types.bool;
264 default = true;
265 description = ''
266 Verify the Unifi controller's certificate.
267 '';
268 };
269 };
270
271 in {
272 dynamic = mkOption {
273 type = types.bool;
274 default = false;
275 description = ''
276 Let prometheus select which controller to poll when scraping.
277 Use with default credentials. See unifi-poller wiki for more.
278 '';
279 };
280
281 defaults = controllerOptions;
282
283 controllers = mkOption {
284 type = with types; listOf (submodule { options = controllerOptions; });
285 default = [];
286 description = ''
287 List of Unifi controllers to poll. Use defaults if empty.
288 '';
289 apply = map (flip removeAttrs [ "_module" ]);
290 };
291 };
292 };
293
294 config = mkIf cfg.enable {
295 users.groups.unifi-poller = { };
296 users.users.unifi-poller = {
297 description = "unifi-poller Service User";
298 group = "unifi-poller";
299 isSystemUser = true;
300 };
301
302 systemd.services.unifi-poller = {
303 wantedBy = [ "multi-user.target" ];
304 after = [ "network.target" ];
305 serviceConfig = {
306 ExecStart = "${pkgs.unifi-poller}/bin/unifi-poller --config ${configFile}";
307 Restart = "always";
308 PrivateTmp = true;
309 ProtectHome = true;
310 ProtectSystem = "full";
311 DevicePolicy = "closed";
312 NoNewPrivileges = true;
313 User = "unifi-poller";
314 WorkingDirectory = "/tmp";
315 };
316 };
317 };
318}