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