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