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 (lib.mdDoc "unpoller");
19
20 poller = {
21 debug = mkOption {
22 type = types.bool;
23 default = false;
24 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
40 Load additional plugins.
41 '';
42 };
43 };
44
45 prometheus = {
46 disable = mkOption {
47 type = types.bool;
48 default = false;
49 description = lib.mdDoc ''
50 Whether to disable the prometheus output plugin.
51 '';
52 };
53 http_listen = mkOption {
54 type = types.str;
55 default = "[::]:9130";
56 description = lib.mdDoc ''
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 = lib.mdDoc ''
64 Whether to report errors.
65 '';
66 };
67 };
68
69 influxdb = {
70 disable = mkOption {
71 type = types.bool;
72 default = false;
73 description = lib.mdDoc ''
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 = lib.mdDoc ''
81 URL of the influxdb host.
82 '';
83 };
84 user = mkOption {
85 type = types.str;
86 default = "unifipoller";
87 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
105 Database name. Database should exist.
106 '';
107 };
108 verify_ssl = mkOption {
109 type = types.bool;
110 default = true;
111 description = lib.mdDoc ''
112 Verify the influxdb's certificate.
113 '';
114 };
115 interval = mkOption {
116 type = types.str;
117 default = "30s";
118 description = lib.mdDoc ''
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 = lib.mdDoc ''
130 URL of the Loki host.
131 '';
132 };
133 user = mkOption {
134 type = types.str;
135 default = "";
136 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
154 Verify Loki's certificate.
155 '';
156 };
157 tenant_id = mkOption {
158 type = types.str;
159 default = "";
160 description = lib.mdDoc ''
161 Tenant ID to use in Loki.
162 '';
163 };
164 interval = mkOption {
165 type = types.str;
166 default = "2m";
167 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
255 Collect and save site data.
256 '';
257 };
258 hash_pii = mkOption {
259 type = types.bool;
260 default = false;
261 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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}