1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5}:
6
7let
8 inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
9 inherit (pkgs.lib)
10 concatStringsSep
11 maintainers
12 mapAttrs
13 mkMerge
14 removeSuffix
15 replaceStrings
16 singleton
17 splitString
18 makeBinPath
19 ;
20
21 /*
22 * The attrset `exporterTests` contains one attribute
23 * for each exporter test. Each of these attributes
24 * is expected to be an attrset containing:
25 *
26 * `exporterConfig`:
27 * this attribute set contains config for the exporter itself
28 *
29 * `exporterTest`
30 * this attribute set contains test instructions
31 *
32 * `metricProvider` (optional)
33 * this attribute contains additional machine config
34 *
35 * `nodeName` (optional)
36 * override an incompatible testnode name
37 *
38 * Example:
39 * exporterTests.<exporterName> = {
40 * exporterConfig = {
41 * enable = true;
42 * };
43 * metricProvider = {
44 * services.<metricProvider>.enable = true;
45 * };
46 * exporterTest = ''
47 * wait_for_unit("prometheus-<exporterName>-exporter.service")
48 * wait_for_open_port(1234)
49 * succeed("curl -sSf 'localhost:1234/metrics'")
50 * '';
51 * };
52 *
53 * # this would generate the following test config:
54 *
55 * nodes.<exporterName> = {
56 * services.prometheus.<exporterName> = {
57 * enable = true;
58 * };
59 * services.<metricProvider>.enable = true;
60 * };
61 *
62 * testScript = ''
63 * <exporterName>.start()
64 * <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
65 * <exporterName>.wait_for_open_port(1234)
66 * <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
67 * <exporterName>.shutdown()
68 * '';
69 */
70
71 exporterTests = {
72 apcupsd = {
73 exporterConfig = {
74 enable = true;
75 };
76 metricProvider = {
77 services.apcupsd.enable = true;
78 };
79 exporterTest = ''
80 wait_for_unit("apcupsd.service")
81 wait_for_open_port(3551)
82 wait_for_unit("prometheus-apcupsd-exporter.service")
83 wait_for_open_port(9162)
84 succeed("curl -sSf http://localhost:9162/metrics | grep 'apcupsd_info'")
85 '';
86 };
87
88 artifactory = {
89 exporterConfig = {
90 enable = true;
91 artiUsername = "artifactory-username";
92 artiPassword = "artifactory-password";
93 };
94 exporterTest = ''
95 wait_for_unit("prometheus-artifactory-exporter.service")
96 wait_for_open_port(9531)
97 succeed(
98 "curl -sSf http://localhost:9531/metrics | grep 'artifactory_up'"
99 )
100 '';
101 };
102
103 bind = {
104 exporterConfig = {
105 enable = true;
106 };
107 metricProvider = {
108 services.bind.enable = true;
109 services.bind.extraConfig = ''
110 statistics-channels {
111 inet 127.0.0.1 port 8053 allow { localhost; };
112 };
113 '';
114 };
115 exporterTest = ''
116 wait_for_unit("prometheus-bind-exporter.service")
117 wait_for_open_port(9119)
118 succeed(
119 "curl -sSf http://localhost:9119/metrics | grep 'bind_query_recursions_total 0'"
120 )
121 '';
122 };
123
124 bird = {
125 exporterConfig = {
126 enable = true;
127 };
128 metricProvider = {
129 services.bird.enable = true;
130 services.bird.config = ''
131 router id 127.0.0.1;
132
133 protocol kernel MyObviousTestString {
134 ipv4 {
135 import all;
136 export none;
137 };
138 }
139
140 protocol device {
141 }
142 '';
143 };
144 exporterTest = ''
145 wait_for_unit("prometheus-bird-exporter.service")
146 wait_for_open_port(9324)
147 wait_until_succeeds(
148 "curl -sSf http://localhost:9324/metrics | grep 'MyObviousTestString'"
149 )
150 '';
151 };
152
153 bitcoin = {
154 exporterConfig = {
155 enable = true;
156 rpcUser = "bitcoinrpc";
157 rpcPasswordFile = pkgs.writeText "password" "hunter2";
158 };
159 metricProvider = {
160 services.bitcoind.default.enable = true;
161 services.bitcoind.default.rpc.users.bitcoinrpc.passwordHMAC =
162 "e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7";
163 };
164 exporterTest = ''
165 wait_for_unit("prometheus-bitcoin-exporter.service")
166 wait_for_unit("bitcoind-default.service")
167 wait_for_open_port(9332)
168 succeed("curl -sSf http://localhost:9332/metrics | grep '^bitcoin_blocks '")
169 '';
170 };
171
172 blackbox = {
173 exporterConfig = {
174 enable = true;
175 configFile = pkgs.writeText "config.yml" (
176 builtins.toJSON {
177 modules.icmp_v6 = {
178 prober = "icmp";
179 icmp.preferred_ip_protocol = "ip6";
180 };
181 }
182 );
183 };
184 exporterTest = ''
185 wait_for_unit("prometheus-blackbox-exporter.service")
186 wait_for_open_port(9115)
187 succeed(
188 "curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep 'probe_success 1'"
189 )
190 '';
191 };
192
193 borgmatic = {
194 exporterConfig = {
195 enable = true;
196 user = "root";
197 };
198 metricProvider = {
199 services.borgmatic.enable = true;
200 services.borgmatic.settings.source_directories = [ "/home" ];
201 services.borgmatic.settings.repositories = [
202 {
203 label = "local";
204 path = "/var/backup";
205 }
206 ];
207 services.borgmatic.settings.keep_daily = 10;
208 };
209 exporterTest = ''
210 succeed("borgmatic rcreate -e none")
211 succeed("borgmatic")
212 wait_for_unit("prometheus-borgmatic-exporter.service")
213 wait_for_open_port(9996)
214 succeed("curl -sSf localhost:9996/metrics | grep 'borg_total_backups{repository=\"/var/backup\"} 1'")
215 '';
216 };
217
218 collectd = {
219 exporterConfig = {
220 enable = true;
221 extraFlags = [ "--web.collectd-push-path /collectd" ];
222 };
223 exporterTest =
224 let
225 postData = replaceStrings [ "\n" ] [ "" ] ''
226 [{
227 "values":[23],
228 "dstypes":["gauge"],
229 "type":"gauge",
230 "interval":1000,
231 "host":"testhost",
232 "plugin":"testplugin",
233 "time":DATE
234 }]
235 '';
236 in
237 ''
238 wait_for_unit("prometheus-collectd-exporter.service")
239 wait_for_open_port(9103)
240 succeed(
241 'echo \'${postData}\'> /tmp/data.json'
242 )
243 succeed('sed -i -e "s DATE $(date +%s) " /tmp/data.json')
244 succeed(
245 "curl -sSfH 'Content-Type: application/json' -X POST --data @/tmp/data.json localhost:9103/collectd"
246 )
247 succeed(
248 "curl -sSf localhost:9103/metrics | grep 'collectd_testplugin_gauge{instance=\"testhost\"} 23'"
249 )
250 '';
251 };
252
253 deluge = {
254 exporterConfig = {
255 enable = true;
256 port = 1234;
257 listenAddress = "127.0.0.1";
258
259 delugeUser = "user";
260 delugePort = 2345;
261 delugePasswordFile = pkgs.writeText "password" "weak_password";
262 };
263 metricProvider = {
264 services.deluge.enable = true;
265 services.deluge.declarative = true;
266 services.deluge.config.daemon_port = 2345;
267 services.deluge.authFile = pkgs.writeText "authFile" ''
268 localclient:abcdef:10
269 user:weak_password:10
270 '';
271 };
272 exporterTest = ''
273 wait_for_unit("deluged.service")
274 wait_for_open_port(2345)
275 wait_for_unit("prometheus-deluge-exporter.service")
276 wait_for_open_port(1234)
277 succeed("curl -sSf http://localhost:1234 | grep 'deluge_torrents'")
278 '';
279 };
280
281 dnsmasq = {
282 exporterConfig = {
283 enable = true;
284 leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
285 };
286 metricProvider = {
287 services.dnsmasq.enable = true;
288 };
289 exporterTest = ''
290 wait_for_unit("dnsmasq.service")
291 wait_for_open_port(53)
292 wait_for_file("/var/lib/dnsmasq/dnsmasq.leases")
293 wait_for_unit("prometheus-dnsmasq-exporter.service")
294 wait_for_open_port(9153)
295 succeed("curl -sSf http://localhost:9153/metrics | grep 'dnsmasq_leases 0'")
296 '';
297 };
298
299 dnssec = {
300 exporterConfig = {
301 enable = true;
302 configuration = {
303 records = [
304 {
305 zone = "example.com";
306 record = "@";
307 type = "SOA";
308 }
309 ];
310 };
311 resolvers = [ "127.0.0.1:53" ];
312 };
313 metricProvider = {
314 services.knot = {
315 enable = true;
316 settingsFile = pkgs.writeText "knot.conf" ''
317 server:
318 listen: 127.0.0.1@53
319 template:
320 - id: default
321 storage: ${
322 pkgs.buildEnv {
323 name = "zones";
324 paths = [
325 (pkgs.writeTextDir "example.com.zone" ''
326 @ SOA ns1.example.com. noc.example.com. 2024032401 86400 7200 3600000 172800
327 @ NS ns1
328 ns1 A 192.168.0.1
329 '')
330 ];
331 }
332 }
333 zonefile-load: difference
334 zonefile-sync: -1
335 zone:
336 - domain: example.com
337 file: example.com.zone
338 dnssec-signing: on
339 '';
340 };
341 };
342 exporterTest = ''
343 wait_for_unit("knot.service")
344 wait_for_open_port(53)
345 wait_for_unit("prometheus-dnssec-exporter.service")
346 wait_for_open_port(9204)
347 succeed("curl -sSf http://localhost:9204/metrics | grep 'example.com'")
348 '';
349 };
350
351 # Access to WHOIS server is required to properly test this exporter, so
352 # just perform basic sanity check that the exporter is running and returns
353 # a failure.
354 domain = {
355 exporterConfig = {
356 enable = true;
357 };
358 exporterTest = ''
359 wait_for_unit("prometheus-domain-exporter.service")
360 wait_for_open_port(9222)
361 succeed("curl -sSf 'http://localhost:9222/probe?target=nixos.org'")
362 '';
363 };
364
365 dovecot = {
366 exporterConfig = {
367 enable = true;
368 scopes = [ "global" ];
369 socketPath = "/var/run/dovecot2/old-stats";
370 user = "root"; # <- don't use user root in production
371 };
372 metricProvider = {
373 services.dovecot2.enable = true;
374 };
375 exporterTest = ''
376 wait_for_unit("prometheus-dovecot-exporter.service")
377 wait_for_open_port(9166)
378 succeed(
379 "curl -sSf http://localhost:9166/metrics | grep 'dovecot_up{scope=\"global\"} 1'"
380 )
381 '';
382 };
383
384 exportarr-sonarr =
385 let
386 apikey = "eccff6a992bc2e4b88e46d064b26bb4e";
387 in
388 {
389 nodeName = "exportarr_sonarr";
390 exporterConfig = {
391 enable = true;
392 url = "http://127.0.0.1:8989";
393 apiKeyFile = pkgs.writeText "dummy-api-key" apikey;
394 };
395 metricProvider = {
396 services.sonarr = {
397 enable = true;
398 environmentFiles = [ (pkgs.writeText "sonarr-env" "SONARR__AUTH__APIKEY=${apikey}") ];
399 };
400 };
401 exporterTest = ''
402 wait_for_unit("sonarr.service")
403 wait_for_open_port(8989)
404 wait_for_unit("prometheus-exportarr-sonarr-exporter.service")
405 wait_for_open_port(9708)
406 succeed("curl -sSf http://localhost:9708/metrics | grep sonarr_series_total")
407 '';
408 };
409
410 ebpf = {
411 exporterConfig = {
412 enable = true;
413 names = [ "timers" ];
414 };
415 exporterTest = ''
416 wait_for_unit("prometheus-ebpf-exporter.service")
417 wait_for_open_port(9435)
418 succeed(
419 "curl -sSf http://localhost:9435/metrics | grep 'ebpf_exporter_enabled_configs{name=\"timers\"} 1'"
420 )
421 '';
422 };
423
424 fastly = {
425 exporterConfig = {
426 enable = true;
427 environmentFile = pkgs.writeText "fastly-exporter-env" "FASTLY_API_TOKEN=abc123";
428 };
429
430 exporterTest = ''
431 wait_for_unit("prometheus-fastly-exporter.service")
432 wait_for_open_port(9118)
433 '';
434 };
435
436 fritzbox = {
437 # TODO add proper test case
438 exporterConfig = {
439 enable = true;
440 };
441 exporterTest = ''
442 wait_for_unit("prometheus-fritzbox-exporter.service")
443 wait_for_open_port(9133)
444 succeed(
445 "curl -sSf http://localhost:9133/metrics | grep 'fritzbox_exporter_collect_errors 0'"
446 )
447 '';
448 };
449
450 graphite = {
451 exporterConfig = {
452 enable = true;
453 port = 9108;
454 graphitePort = 9109;
455 mappingSettings.mappings = [
456 {
457 match = "test.*.*";
458 name = "testing";
459 labels = {
460 protocol = "$1";
461 author = "$2";
462 };
463 }
464 ];
465 };
466 exporterTest = ''
467 wait_for_unit("prometheus-graphite-exporter.service")
468 wait_for_open_port(9108)
469 wait_for_open_port(9109)
470 succeed("echo test.tcp.foo-bar 1234 $(date +%s) | nc -w1 localhost 9109")
471 succeed("curl -sSf http://localhost:9108/metrics | grep 'testing{author=\"foo-bar\",protocol=\"tcp\"} 1234'")
472 '';
473 };
474
475 idrac = {
476 exporterConfig = {
477 enable = true;
478 port = 9348;
479 configuration = {
480 hosts = {
481 default = {
482 username = "username";
483 password = "password";
484 };
485 };
486 };
487 };
488 exporterTest = ''
489 wait_for_unit("prometheus-idrac-exporter.service")
490 wait_for_open_port(9348)
491 wait_until_succeeds("curl localhost:9348")
492 '';
493 };
494
495 influxdb = {
496 exporterConfig = {
497 enable = true;
498 sampleExpiry = "3s";
499 };
500 exporterTest = ''
501 wait_for_unit("prometheus-influxdb-exporter.service")
502 wait_for_open_port(9122)
503 succeed(
504 "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
505 )
506 succeed(
507 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
508 )
509 execute("sleep 5")
510 fail(
511 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
512 )
513 '';
514 };
515
516 ipmi = {
517 exporterConfig = {
518 enable = true;
519 };
520 exporterTest = ''
521 wait_for_unit("prometheus-ipmi-exporter.service")
522 wait_for_open_port(9290)
523 succeed(
524 "curl -sSf http://localhost:9290/metrics | grep 'ipmi_scrape_duration_seconds'"
525 )
526 '';
527 };
528
529 jitsi = {
530 exporterConfig = {
531 enable = true;
532 };
533 metricProvider = {
534 systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
535 services.jitsi-videobridge = {
536 enable = true;
537 colibriRestApi = true;
538 };
539 };
540 exporterTest = ''
541 wait_for_unit("jitsi-videobridge2.service")
542 wait_for_open_port(8080)
543 wait_for_unit("prometheus-jitsi-exporter.service")
544 wait_for_open_port(9700)
545 wait_until_succeeds(
546 'journalctl -eu prometheus-jitsi-exporter.service -o cat | grep "key=participants"'
547 )
548 succeed("curl -sSf 'localhost:9700/metrics' | grep 'jitsi_participants 0'")
549 '';
550 };
551
552 json = {
553 exporterConfig = {
554 enable = true;
555 configFile = pkgs.writeText "json-exporter-conf.json" (
556 builtins.toJSON {
557 modules = {
558 default = {
559 metrics = [
560 {
561 name = "json_test_metric";
562 path = "{ .test }";
563 }
564 ];
565 };
566 };
567 }
568 );
569 };
570 metricProvider = {
571 systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
572 services.nginx = {
573 enable = true;
574 virtualHosts.localhost.locations."/".extraConfig = ''
575 return 200 "{\"test\":1}";
576 '';
577 };
578 };
579 exporterTest = ''
580 wait_for_unit("nginx.service")
581 wait_for_open_port(80)
582 wait_for_unit("prometheus-json-exporter.service")
583 wait_for_open_port(7979)
584 succeed(
585 "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep 'json_test_metric 1'"
586 )
587 '';
588 };
589
590 kafka = {
591 exporterConfig = {
592 enable = true;
593 environmentFile = pkgs.writeTextFile {
594 name = "/tmp/prometheus-kafka-exporter.env";
595 text = ''
596 KAFKA_BROKERS="localhost:9092"
597 '';
598 };
599 };
600 metricProvider = {
601 services.apache-kafka = {
602 enable = true;
603
604 clusterId = "pHG8aWuXSfWAibHFDCnsCQ";
605
606 formatLogDirs = true;
607
608 settings = {
609 "node.id" = 1;
610 "process.roles" = [
611 "broker"
612 "controller"
613 ];
614 "listeners" = [
615 "PLAINTEXT://:9092"
616 "CONTROLLER://:9093"
617 ];
618 "listener.security.protocol.map" = [
619 "PLAINTEXT:PLAINTEXT"
620 "CONTROLLER:PLAINTEXT"
621 ];
622 "controller.quorum.voters" = [
623 "1@localhost:9093"
624 ];
625 "controller.listener.names" = [ "CONTROLLER" ];
626 "log.dirs" = [ "/var/lib/apache-kafka" ];
627 };
628 };
629
630 systemd.services.apache-kafka.serviceConfig.StateDirectory = "apache-kafka";
631 };
632 exporterTest = ''
633 wait_for_unit("apache-kafka")
634 wait_for_open_port(9092)
635 wait_for_open_port(9093)
636 wait_for_unit("prometheus-kafka-exporter.service")
637 wait_for_open_port(8080)
638 wait_until_succeeds(
639 "journalctl -o cat -u prometheus-kafka-exporter.service | grep '\"version\":\"${pkgs.kminion.version}\"'"
640 )
641 succeed(
642 "curl -sSf http://localhost:8080/metrics | grep 'kminion_exporter_up'"
643 )
644 '';
645 };
646
647 knot = {
648 exporterConfig = {
649 enable = true;
650 };
651 metricProvider = {
652 services.knot = {
653 enable = true;
654 extraArgs = [ "-v" ];
655 settingsFile = pkgs.writeText "knot.conf" ''
656 server:
657 listen: 127.0.0.1@53
658
659 template:
660 - id: default
661 global-module: mod-stats
662 dnssec-signing: off
663 zonefile-sync: -1
664 zonefile-load: difference
665 storage: ${
666 pkgs.buildEnv {
667 name = "foo";
668 paths = [
669 (pkgs.writeTextDir "test.zone" ''
670 @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
671 @ NS ns1
672 @ NS ns2
673 ns1 A 192.168.0.1
674 '')
675 ];
676 }
677 }
678
679 mod-stats:
680 - id: custom
681 edns-presence: on
682 query-type: on
683
684 zone:
685 - domain: test
686 file: test.zone
687 module: mod-stats/custom
688 '';
689 };
690 };
691 exporterTest = ''
692 wait_for_unit("knot.service")
693 wait_for_unit("prometheus-knot-exporter.service")
694 wait_for_open_port(9433)
695 succeed("curl -sSf 'localhost:9433' | grep '2\.019031301'")
696 '';
697 };
698
699 keylight = {
700 # A hardware device is required to properly test this exporter, so just
701 # perform a couple of basic sanity checks that the exporter is running
702 # and requires a target, but cannot reach a specified target.
703 exporterConfig = {
704 enable = true;
705 };
706 exporterTest = ''
707 wait_for_unit("prometheus-keylight-exporter.service")
708 wait_for_open_port(9288)
709 succeed(
710 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep '400'"
711 )
712 succeed(
713 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep '500'"
714 )
715 '';
716 };
717
718 lnd = {
719 exporterConfig = {
720 enable = true;
721 lndTlsPath = "/var/lib/lnd/tls.cert";
722 lndMacaroonDir = "/var/lib/lnd";
723 extraFlags = [ "--lnd.network=regtest" ];
724 };
725 metricProvider = {
726 systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
727 systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
728 services.bitcoind.regtest = {
729 enable = true;
730 extraConfig = ''
731 rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
732 zmqpubrawblock=tcp://127.0.0.1:28332
733 zmqpubrawtx=tcp://127.0.0.1:28333
734 # https://github.com/lightningnetwork/lnd/issues/9163
735 deprecatedrpc=warnings
736 '';
737 extraCmdlineOptions = [ "-regtest" ];
738 };
739 systemd.services.lnd = {
740 serviceConfig.ExecStart = ''
741 ${pkgs.lnd}/bin/lnd \
742 --datadir=/var/lib/lnd \
743 --tlscertpath=/var/lib/lnd/tls.cert \
744 --tlskeypath=/var/lib/lnd/tls.key \
745 --logdir=/var/log/lnd \
746 --bitcoin.active \
747 --bitcoin.regtest \
748 --bitcoin.node=bitcoind \
749 --bitcoind.rpcuser=bitcoinrpc \
750 --bitcoind.rpcpass=hunter2 \
751 --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
752 --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
753 --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
754 '';
755 serviceConfig.StateDirectory = "lnd";
756 wantedBy = [ "multi-user.target" ];
757 after = [ "network.target" ];
758 };
759 # initialize wallet, creates macaroon needed by exporter
760 systemd.services.lnd.postStart = ''
761 ${pkgs.curl}/bin/curl \
762 --retry 20 \
763 --retry-delay 1 \
764 --retry-connrefused \
765 --cacert /var/lib/lnd/tls.cert \
766 -X GET \
767 https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed
768 ${pkgs.curl}/bin/curl \
769 --retry 20 \
770 --retry-delay 1 \
771 --retry-connrefused \
772 --cacert /var/lib/lnd/tls.cert \
773 -X POST \
774 -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \
775 https://localhost:8080/v1/initwallet
776 '';
777 };
778 exporterTest = ''
779 wait_for_unit("lnd.service")
780 wait_for_open_port(10009)
781 wait_for_unit("prometheus-lnd-exporter.service")
782 wait_for_open_port(9092)
783 succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'")
784 '';
785 };
786
787 mail = {
788 exporterConfig = {
789 enable = true;
790 configuration = {
791 monitoringInterval = "2s";
792 mailCheckTimeout = "10s";
793 servers = [
794 {
795 name = "testserver";
796 server = "localhost";
797 port = 25;
798 from = "mail-exporter@localhost";
799 to = "mail-exporter@localhost";
800 detectionDir = "/var/spool/mail/mail-exporter/new";
801 }
802 ];
803 };
804 };
805 metricProvider = {
806 services.postfix.enable = true;
807 systemd.services.prometheus-mail-exporter = {
808 after = [ "postfix.service" ];
809 requires = [ "postfix.service" ];
810 serviceConfig = {
811 ExecStartPre = [
812 "${pkgs.writeShellScript "create-maildir" ''
813 mkdir -p -m 0700 mail-exporter/new
814 ''}"
815 ];
816 ProtectHome = true;
817 ReadOnlyPaths = "/";
818 ReadWritePaths = "/var/spool/mail";
819 WorkingDirectory = "/var/spool/mail";
820 };
821 };
822 users.users.mailexporter = {
823 isSystemUser = true;
824 group = "mailexporter";
825 };
826 users.groups.mailexporter = { };
827 };
828 exporterTest = ''
829 wait_for_unit("postfix.service")
830 wait_for_unit("prometheus-mail-exporter.service")
831 wait_for_open_port(9225)
832 wait_until_succeeds(
833 "curl -sSf http://localhost:9225/metrics | grep 'mail_deliver_success{configname=\"testserver\"} 1'"
834 )
835 '';
836 };
837
838 mikrotik = {
839 exporterConfig = {
840 enable = true;
841 extraFlags = [ "-timeout=1s" ];
842 configuration = {
843 devices = [
844 {
845 name = "router";
846 address = "192.168.42.48";
847 user = "prometheus";
848 password = "shh";
849 }
850 ];
851 features = {
852 bgp = true;
853 dhcp = true;
854 dhcpl = true;
855 dhcpv6 = true;
856 health = true;
857 routes = true;
858 poe = true;
859 pools = true;
860 optics = true;
861 w60g = true;
862 wlansta = true;
863 wlanif = true;
864 monitor = true;
865 ipsec = true;
866 };
867 };
868 };
869 exporterTest = ''
870 wait_for_unit("prometheus-mikrotik-exporter.service")
871 wait_for_open_port(9436)
872 succeed(
873 "curl -sSf http://localhost:9436/metrics | grep 'mikrotik_scrape_collector_success{device=\"router\"} 0'"
874 )
875 '';
876 };
877
878 modemmanager = {
879 exporterConfig = {
880 enable = true;
881 refreshRate = "10s";
882 };
883 metricProvider = {
884 # ModemManager is installed when NetworkManager is enabled. Ensure it is
885 # started and is wanted by NM and the exporter to start everything up
886 # in the right order.
887 networking.networkmanager.enable = true;
888 systemd.services.ModemManager = {
889 enable = true;
890 wantedBy = [
891 "NetworkManager.service"
892 "prometheus-modemmanager-exporter.service"
893 ];
894 };
895 };
896 exporterTest = ''
897 wait_for_unit("ModemManager.service")
898 wait_for_unit("prometheus-modemmanager-exporter.service")
899 wait_for_open_port(9539)
900 succeed(
901 "curl -sSf http://localhost:9539/metrics | grep 'modemmanager_info'"
902 )
903 '';
904 };
905
906 mqtt = {
907 exporterConfig = {
908 enable = true;
909 environmentFile = pkgs.writeText "mqtt-exporter-envfile" ''
910 MQTT_PASSWORD=testpassword
911 '';
912 };
913 metricProvider = {
914 services.mosquitto = {
915 enable = true;
916 listeners = [
917 {
918 users.exporter = {
919 acl = [ "read #" ];
920 passwordFile = pkgs.writeText "mosquitto-password" "testpassword";
921 };
922 }
923 ];
924 };
925 systemd.services.prometheus-mqtt-exporter = {
926 wants = [ "mosquitto.service" ];
927 after = [ "mosquitto.service" ];
928 };
929 };
930 exporterTest = ''
931 wait_for_unit("mosquitto.service")
932 wait_for_unit("prometheus-mqtt-exporter.service")
933 wait_for_open_port(9000)
934 succeed(
935 "curl -sSf http://localhost:9000/metrics | grep '^python_info'"
936 )
937 '';
938 };
939
940 mysqld = {
941 exporterConfig = {
942 enable = true;
943 runAsLocalSuperUser = true;
944 configFile = pkgs.writeText "test-prometheus-exporter-mysqld-config.my-cnf" ''
945 [client]
946 user = exporter
947 password = snakeoilpassword
948 '';
949 };
950 metricProvider = {
951 services.mysql = {
952 enable = true;
953 package = pkgs.mariadb;
954 initialScript = pkgs.writeText "mysql-init-script.sql" ''
955 CREATE USER 'exporter'@'localhost'
956 IDENTIFIED BY 'snakeoilpassword'
957 WITH MAX_USER_CONNECTIONS 3;
958 GRANT PROCESS, REPLICATION CLIENT, SLAVE MONITOR, SELECT ON *.* TO 'exporter'@'localhost';
959 '';
960 };
961 };
962 exporterTest = ''
963 wait_for_unit("prometheus-mysqld-exporter.service")
964 wait_for_open_port(9104)
965 wait_for_unit("mysql.service")
966 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
967 systemctl("stop mysql.service")
968 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 0'")
969 systemctl("start mysql.service")
970 wait_for_unit("mysql.service")
971 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
972 '';
973 };
974
975 nextcloud = {
976 exporterConfig = {
977 enable = true;
978 passwordFile = "/var/nextcloud-pwfile";
979 url = "http://localhost";
980 };
981 metricProvider = {
982 systemd.services.nc-pwfile =
983 let
984 passfile = (pkgs.writeText "pwfile" "snakeoilpw");
985 in
986 {
987 requiredBy = [ "prometheus-nextcloud-exporter.service" ];
988 before = [ "prometheus-nextcloud-exporter.service" ];
989 serviceConfig.ExecStart = ''
990 ${pkgs.coreutils}/bin/install -o nextcloud-exporter -m 0400 ${passfile} /var/nextcloud-pwfile
991 '';
992 };
993 services.nginx = {
994 enable = true;
995 virtualHosts."localhost" = {
996 basicAuth.nextcloud-exporter = "snakeoilpw";
997 locations."/" = {
998 root = "${pkgs.prometheus-nextcloud-exporter.src}/serverinfo/testdata";
999 tryFiles = "/negative-space.json =404";
1000 };
1001 };
1002 };
1003 };
1004 exporterTest = ''
1005 wait_for_unit("nginx.service")
1006 wait_for_unit("prometheus-nextcloud-exporter.service")
1007 wait_for_open_port(9205)
1008 succeed("curl -sSf http://localhost:9205/metrics | grep 'nextcloud_up 1'")
1009 '';
1010 };
1011
1012 nginx = {
1013 exporterConfig = {
1014 enable = true;
1015 constLabels = [ "foo=bar" ];
1016 };
1017 metricProvider = {
1018 services.nginx = {
1019 enable = true;
1020 statusPage = true;
1021 virtualHosts."test".extraConfig = "return 204;";
1022 };
1023 };
1024 exporterTest = ''
1025 wait_for_unit("nginx.service")
1026 wait_for_unit("prometheus-nginx-exporter.service")
1027 wait_for_open_port(9113)
1028 succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up{foo=\"bar\"} 1'")
1029 '';
1030 };
1031
1032 nginxlog = {
1033 exporterConfig = {
1034 enable = true;
1035 group = "nginx";
1036 settings = {
1037 namespaces = [
1038 {
1039 name = "filelogger";
1040 source = {
1041 files = [ "/var/log/nginx/filelogger.access.log" ];
1042 };
1043 }
1044 {
1045 name = "syslogger";
1046 source = {
1047 syslog = {
1048 listen_address = "udp://127.0.0.1:10000";
1049 format = "rfc3164";
1050 tags = [ "nginx" ];
1051 };
1052 };
1053 }
1054 ];
1055 };
1056 };
1057 metricProvider = {
1058 services.nginx = {
1059 enable = true;
1060 httpConfig = ''
1061 server {
1062 listen 80;
1063 server_name filelogger.local;
1064 access_log /var/log/nginx/filelogger.access.log;
1065 }
1066 server {
1067 listen 81;
1068 server_name syslogger.local;
1069 access_log syslog:server=127.0.0.1:10000,tag=nginx,severity=info;
1070 }
1071 '';
1072 };
1073 };
1074 exporterTest = ''
1075 wait_for_unit("nginx.service")
1076 wait_for_unit("prometheus-nginxlog-exporter.service")
1077 wait_for_open_port(9117)
1078 wait_for_open_port(80)
1079 wait_for_open_port(81)
1080 succeed("curl http://localhost")
1081 execute("sleep 1")
1082 succeed(
1083 "curl -sSf http://localhost:9117/metrics | grep 'filelogger_http_response_count_total' | grep 1"
1084 )
1085 succeed("curl http://localhost:81")
1086 execute("sleep 1")
1087 succeed(
1088 "curl -sSf http://localhost:9117/metrics | grep 'syslogger_http_response_count_total' | grep 1"
1089 )
1090 '';
1091 };
1092
1093 node = {
1094 exporterConfig = {
1095 enable = true;
1096 };
1097 exporterTest = ''
1098 wait_for_unit("prometheus-node-exporter.service")
1099 wait_for_open_port(9100)
1100 succeed(
1101 "curl -sSf http://localhost:9100/metrics | grep 'node_exporter_build_info{.\\+} 1'"
1102 )
1103 '';
1104 };
1105
1106 node-cert = {
1107 nodeName = "node_cert";
1108 exporterConfig = {
1109 enable = true;
1110 paths = [ "/run/certs" ];
1111 };
1112 exporterTest = ''
1113 wait_for_unit("prometheus-node-cert-exporter.service")
1114 wait_for_open_port(9141)
1115 wait_until_succeeds(
1116 "curl -sSf http://localhost:9141/metrics | grep 'ssl_certificate_expiry_seconds{.\\+path=\"/run/certs/node-cert\\.cert\".\\+}'"
1117 )
1118 '';
1119
1120 metricProvider = {
1121 system.activationScripts.cert.text = ''
1122 mkdir -p /run/certs
1123 cd /run/certs
1124
1125 cat >ca.template <<EOF
1126 organization = "prometheus-node-cert-exporter"
1127 cn = "prometheus-node-cert-exporter"
1128 expiration_days = 365
1129 ca
1130 cert_signing_key
1131 crl_signing_key
1132 EOF
1133
1134 ${pkgs.gnutls}/bin/certtool \
1135 --generate-privkey \
1136 --key-type rsa \
1137 --sec-param High \
1138 --outfile node-cert.key
1139
1140 ${pkgs.gnutls}/bin/certtool \
1141 --generate-self-signed \
1142 --load-privkey node-cert.key \
1143 --template ca.template \
1144 --outfile node-cert.cert
1145 '';
1146 };
1147 };
1148
1149 pgbouncer = {
1150 exporterConfig = {
1151 enable = true;
1152 connectionEnvFile = "${pkgs.writeText "connstr-env" ''
1153 PGBOUNCER_EXPORTER_CONNECTION_STRING=postgres://admin@localhost:6432/pgbouncer?sslmode=disable
1154 ''}";
1155 };
1156
1157 metricProvider = {
1158 services.postgresql.enable = true;
1159 services.pgbouncer = {
1160 enable = true;
1161 settings = {
1162 pgbouncer = {
1163 listen_addr = "*";
1164 auth_type = "any";
1165 max_client_conn = 99;
1166 # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
1167 ignore_startup_parameters = "extra_float_digits";
1168 };
1169 databases = {
1170 postgres = concatStringsSep " " [
1171 "host=/run/postgresql"
1172 "port=5432"
1173 "auth_user=postgres"
1174 "dbname=postgres"
1175 ];
1176 };
1177 };
1178 };
1179 };
1180 exporterTest = ''
1181 wait_for_unit("postgresql.target")
1182 wait_for_unit("pgbouncer.service")
1183 wait_for_unit("prometheus-pgbouncer-exporter.service")
1184 wait_for_open_port(9127)
1185 succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'")
1186 succeed(
1187 "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'"
1188 )
1189 '';
1190 };
1191
1192 php-fpm = {
1193 nodeName = "php_fpm";
1194 exporterConfig = {
1195 enable = true;
1196 environmentFile = pkgs.writeTextFile {
1197 name = "/tmp/prometheus-php-fpm-exporter.env";
1198 text = ''
1199 PHP_FPM_SCRAPE_URI="tcp://127.0.0.1:9000/status"
1200 '';
1201 };
1202 };
1203 metricProvider = {
1204 users.users."php-fpm-exporter" = {
1205 isSystemUser = true;
1206 group = "php-fpm-exporter";
1207 };
1208 users.groups."php-fpm-exporter" = { };
1209 services.phpfpm.pools."php-fpm-exporter" = {
1210 user = "php-fpm-exporter";
1211 group = "php-fpm-exporter";
1212 settings = {
1213 "pm" = "dynamic";
1214 "pm.max_children" = 32;
1215 "pm.max_requests" = 500;
1216 "pm.start_servers" = 2;
1217 "pm.min_spare_servers" = 2;
1218 "pm.max_spare_servers" = 5;
1219 "pm.status_path" = "/status";
1220 "listen" = "127.0.0.1:9000";
1221 "listen.allowed_clients" = "127.0.0.1";
1222 };
1223 phpEnv."PATH" = makeBinPath [ pkgs.php ];
1224 };
1225 };
1226 exporterTest = ''
1227 wait_for_unit("phpfpm-php-fpm-exporter.service")
1228 wait_for_unit("prometheus-php-fpm-exporter.service")
1229 succeed("curl -sSf http://localhost:9253/metrics | grep 'phpfpm_up{.*} 1'")
1230 '';
1231 };
1232
1233 ping = {
1234 exporterConfig = {
1235 enable = true;
1236
1237 settings = {
1238 targets = [
1239 {
1240 "localhost" = {
1241 alias = "local machine";
1242 env = "prod";
1243 type = "domain";
1244 };
1245 }
1246 {
1247 "127.0.0.1" = {
1248 alias = "local machine";
1249 type = "v4";
1250 };
1251 }
1252 {
1253 "::1" = {
1254 alias = "local machine";
1255 type = "v6";
1256 };
1257 }
1258 {
1259 "google.com" = { };
1260 }
1261 ];
1262 dns = { };
1263 ping = {
1264 interval = "2s";
1265 timeout = "3s";
1266 history-size = 42;
1267 payload-size = 56;
1268 };
1269 log = {
1270 level = "warn";
1271 };
1272 };
1273 };
1274
1275 exporterTest = ''
1276 wait_for_unit("prometheus-ping-exporter.service")
1277 wait_for_open_port(9427)
1278 succeed("curl -sSf http://localhost:9427/metrics | grep 'ping_up{.*} 1'")
1279 '';
1280 };
1281
1282 postfix = {
1283 exporterConfig = {
1284 enable = true;
1285 };
1286 metricProvider = {
1287 services.postfix.enable = true;
1288 };
1289 exporterTest = ''
1290 wait_for_unit("prometheus-postfix-exporter.service")
1291 wait_for_file("/var/lib/postfix/queue/public/showq")
1292 wait_for_open_port(9154)
1293 wait_until_succeeds(
1294 "curl -sSf http://localhost:9154/metrics | grep 'postfix_up{path=\"unix:///var/lib/postfix/queue/public/showq\"} 1'"
1295 )
1296 succeed(
1297 "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
1298 )
1299 succeed("curl -sSf http://localhost:9154/metrics | grep 'postfix_up{.*} 1'")
1300 '';
1301 };
1302
1303 postgres = {
1304 exporterConfig = {
1305 enable = true;
1306 runAsLocalSuperUser = true;
1307 };
1308 metricProvider = {
1309 services.postgresql.enable = true;
1310 };
1311 exporterTest = ''
1312 wait_for_unit("prometheus-postgres-exporter.service")
1313 wait_for_open_port(9187)
1314 wait_for_unit("postgresql.target")
1315 succeed(
1316 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1317 )
1318 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1319 systemctl("stop postgresql")
1320 succeed(
1321 "curl -sSf http://localhost:9187/metrics | grep -v 'pg_exporter_last_scrape_error 0'"
1322 )
1323 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 0'")
1324 systemctl("start postgresql")
1325 wait_for_unit("postgresql.target")
1326 succeed(
1327 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1328 )
1329 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1330 '';
1331 };
1332
1333 process = {
1334 exporterConfig = {
1335 enable = true;
1336 settings.process_names = [
1337 # Remove nix store path from process name
1338 {
1339 name = "{{.Matches.Wrapped}} {{ .Matches.Args }}";
1340 cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ];
1341 }
1342 ];
1343 };
1344 exporterTest = ''
1345 wait_for_unit("prometheus-process-exporter.service")
1346 wait_for_open_port(9256)
1347 wait_until_succeeds(
1348 "curl -sSf localhost:9256/metrics | grep -q '{}'".format(
1349 'namedprocess_namegroup_cpu_seconds_total{groupname="process-exporter '
1350 )
1351 )
1352 '';
1353 };
1354
1355 pve =
1356 let
1357 pveExporterEnvFile = pkgs.writeTextFile {
1358 name = "pve.env";
1359 text = ''
1360 PVE_USER="test_user@pam"
1361 PVE_PASSWORD="hunter3"
1362 PVE_VERIFY_SSL="false"
1363 '';
1364 };
1365 in
1366 {
1367 exporterConfig = {
1368 enable = true;
1369 environmentFile = pveExporterEnvFile;
1370 };
1371 exporterTest = ''
1372 wait_for_unit("prometheus-pve-exporter.service")
1373 wait_for_open_port(9221)
1374 wait_until_succeeds("curl localhost:9221")
1375 '';
1376 };
1377
1378 py-air-control = {
1379 nodeName = "py_air_control";
1380 exporterConfig = {
1381 enable = true;
1382 deviceHostname = "127.0.0.1";
1383 };
1384 exporterTest = ''
1385 wait_for_unit("prometheus-py-air-control-exporter.service")
1386 wait_for_open_port(9896)
1387 succeed(
1388 "curl -sSf http://localhost:9896/metrics | grep 'py_air_control_sampling_error_total'"
1389 )
1390 '';
1391 };
1392
1393 redis = {
1394 exporterConfig = {
1395 enable = true;
1396 };
1397 metricProvider.services.redis.servers."".enable = true;
1398 exporterTest = ''
1399 wait_for_unit("redis.service")
1400 wait_for_unit("prometheus-redis-exporter.service")
1401 wait_for_open_port(6379)
1402 wait_for_open_port(9121)
1403 wait_until_succeeds("curl -sSf localhost:9121/metrics | grep 'redis_up 1'")
1404 '';
1405 };
1406
1407 restic =
1408 let
1409 repository = "rest:http://127.0.0.1:8000";
1410 passwordFile = pkgs.writeText "restic-test-password" "test-password";
1411 in
1412 {
1413 exporterConfig = {
1414 enable = true;
1415 inherit repository passwordFile;
1416 };
1417 metricProvider = {
1418 services.restic.server = {
1419 enable = true;
1420 extraFlags = [ "--no-auth" ];
1421 };
1422 environment.systemPackages = [ pkgs.restic ];
1423 };
1424 exporterTest = ''
1425 # prometheus-restic-exporter.service fails without initialised repository
1426 systemctl("stop prometheus-restic-exporter.service")
1427
1428 # Initialise the repository
1429 wait_for_unit("restic-rest-server.service")
1430 wait_for_open_port(8000)
1431 succeed("restic init --repo ${repository} --password-file ${passwordFile}")
1432
1433 systemctl("start prometheus-restic-exporter.service")
1434 wait_for_unit("prometheus-restic-exporter.service")
1435 wait_for_open_port(9753)
1436 wait_until_succeeds("curl -sSf localhost:9753/metrics | grep 'restic_check_success 1.0'")
1437 '';
1438 };
1439
1440 rspamd = {
1441 exporterConfig = {
1442 enable = true;
1443 };
1444 metricProvider = {
1445 services.rspamd.enable = true;
1446 };
1447 exporterTest = ''
1448 wait_for_unit("rspamd.service")
1449 wait_for_unit("prometheus-rspamd-exporter.service")
1450 wait_for_open_port(11334)
1451 wait_for_open_port(7980)
1452 wait_until_succeeds(
1453 "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep 'rspamd_scanned{host=\"rspamd\"} 0'"
1454 )
1455 '';
1456 };
1457
1458 rtl_433 = {
1459 exporterConfig = {
1460 enable = true;
1461 };
1462 metricProvider = {
1463 # Mock rtl_433 binary to return a dummy metric stream.
1464 nixpkgs.overlays = [
1465 (self: super: {
1466 rtl_433 = self.runCommand "rtl_433" { } ''
1467 mkdir -p "$out/bin"
1468 cat <<EOF > "$out/bin/rtl_433"
1469 #!/bin/sh
1470 while true; do
1471 printf '{"time" : "2020-04-26 13:37:42", "model" : "zopieux", "id" : 55, "channel" : 3, "temperature_C" : 18.000}\n'
1472 sleep 4
1473 done
1474 EOF
1475 chmod +x "$out/bin/rtl_433"
1476 '';
1477 })
1478 ];
1479 };
1480 exporterTest = ''
1481 wait_for_unit("prometheus-rtl_433-exporter.service")
1482 wait_for_open_port(9550)
1483 wait_until_succeeds(
1484 "curl -sSf localhost:9550/metrics | grep '{}'".format(
1485 'rtl_433_temperature_celsius{channel="3",id="55",location="",model="zopieux"} 18'
1486 )
1487 )
1488 '';
1489 };
1490
1491 sabnzbd = {
1492 exporterConfig = {
1493 enable = true;
1494 servers = [
1495 {
1496 baseUrl = "http://localhost:8080";
1497 apiKeyFile = "/var/sabnzbd-apikey";
1498 }
1499 ];
1500 };
1501
1502 metricProvider = {
1503 services.sabnzbd.enable = true;
1504
1505 # unrar is required for sabnzbd
1506 nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
1507
1508 # extract the generated api key before starting
1509 systemd.services.sabnzbd-apikey = {
1510 requires = [ "sabnzbd.service" ];
1511 after = [ "sabnzbd.service" ];
1512 requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
1513 before = [ "prometheus-sabnzbd-exporter.service" ];
1514 script = ''
1515 grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
1516 '';
1517 };
1518 };
1519
1520 exporterTest = ''
1521 wait_for_unit("sabnzbd.service")
1522 wait_for_unit("prometheus-sabnzbd-exporter.service")
1523 wait_for_open_port(8080)
1524 wait_for_open_port(9387)
1525 wait_until_succeeds(
1526 "curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
1527 )
1528 '';
1529 };
1530
1531 scaphandre = {
1532 exporterConfig = {
1533 enable = true;
1534 };
1535 metricProvider = {
1536 boot.kernelModules = [ "intel_rapl_common" ];
1537 };
1538 exporterTest = ''
1539 wait_for_unit("prometheus-scaphandre-exporter.service")
1540 wait_for_open_port(8080)
1541 wait_until_succeeds(
1542 "curl -sSf 'localhost:8080/metrics'"
1543 )
1544 '';
1545 };
1546
1547 shelly = {
1548 exporterConfig = {
1549 enable = true;
1550 metrics-file = "${pkgs.writeText "test.json" ''{}''}";
1551 };
1552 exporterTest = ''
1553 wait_for_unit("prometheus-shelly-exporter.service")
1554 wait_for_open_port(9784)
1555 wait_until_succeeds(
1556 "curl -sSf 'localhost:9784/metrics'"
1557 )
1558 '';
1559 };
1560
1561 script = {
1562 exporterConfig = {
1563 enable = true;
1564 settings.scripts = [
1565 {
1566 name = "success";
1567 command = [ "sleep" ];
1568 args = [ "1" ];
1569 }
1570 ];
1571 };
1572 exporterTest = ''
1573 wait_for_unit("prometheus-script-exporter.service")
1574 wait_for_open_port(9172)
1575 wait_until_succeeds(
1576 "curl -sSf 'localhost:9172/probe?script=success' | grep -q '{}'".format(
1577 'script_success{script="success"} 1'
1578 )
1579 )
1580 '';
1581 };
1582
1583 smartctl = {
1584 exporterConfig = {
1585 enable = true;
1586 devices = [
1587 "/dev/vda"
1588 ];
1589 };
1590 exporterTest = ''
1591 wait_until_succeeds(
1592 'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"'
1593 )
1594 '';
1595 };
1596
1597 smokeping = {
1598 exporterConfig = {
1599 enable = true;
1600 hosts = [ "127.0.0.1" ];
1601 };
1602 exporterTest = ''
1603 wait_for_unit("prometheus-smokeping-exporter.service")
1604 wait_for_open_port(9374)
1605 wait_until_succeeds(
1606 "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
1607 'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source="",tos="0"} '
1608 )
1609 )
1610 wait_until_succeeds(
1611 "curl -sSf localhost:9374/metrics | grep '{}'".format(
1612 'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source="",tos="0"}'
1613 )
1614 )
1615 '';
1616 };
1617
1618 storagebox = {
1619 exporterConfig = {
1620 enable = true;
1621 tokenFile = "/tmp/faketoken";
1622 };
1623 exporterTest = ''
1624 succeed(
1625 'echo faketoken > /tmp/faketoken'
1626 )
1627 wait_for_unit("prometheus-storagebox-exporter.service")
1628 wait_for_open_port(9509)
1629 succeed("curl -sSf localhost:9509/metrics | grep 'process_open_fds'")
1630 '';
1631 };
1632
1633 snmp = {
1634 exporterConfig = {
1635 enable = true;
1636 configuration = {
1637 auths.public_v2 = {
1638 community = "public";
1639 version = 2;
1640 };
1641 };
1642 };
1643 exporterTest = ''
1644 wait_for_unit("prometheus-snmp-exporter.service")
1645 wait_for_open_port(9116)
1646 succeed("curl -sSf localhost:9116/metrics | grep 'snmp_request_errors_total 0'")
1647 '';
1648 };
1649
1650 sql = {
1651 exporterConfig = {
1652 configuration.jobs.points = {
1653 interval = "1m";
1654 connections = [
1655 "postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
1656 ];
1657 queries = {
1658 points = {
1659 labels = [ "name" ];
1660 help = "Amount of points accumulated per person";
1661 values = [ "amount" ];
1662 query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
1663 };
1664 };
1665 };
1666 enable = true;
1667 user = "prometheus-sql-exporter";
1668 };
1669 metricProvider = {
1670 services.postgresql = {
1671 enable = true;
1672 initialScript = builtins.toFile "init.sql" ''
1673 CREATE DATABASE data;
1674 \c data;
1675 CREATE TABLE points (amount INT, name TEXT);
1676 INSERT INTO points(amount, name) VALUES (1, 'jack');
1677 INSERT INTO points(amount, name) VALUES (2, 'jill');
1678 INSERT INTO points(amount, name) VALUES (3, 'jack');
1679
1680 CREATE USER "prometheus-sql-exporter";
1681 GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
1682 GRANT SELECT ON points TO "prometheus-sql-exporter";
1683 '';
1684 };
1685 systemd.services.prometheus-sql-exporter.after = [ "postgresql.target" ];
1686 };
1687 exporterTest = ''
1688 wait_for_unit("prometheus-sql-exporter.service")
1689 wait_for_open_port(9237)
1690 succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep 2")
1691 '';
1692 };
1693
1694 statsd = {
1695 exporterConfig = {
1696 enable = true;
1697 };
1698 exporterTest = ''
1699 wait_for_unit("prometheus-statsd-exporter.service")
1700 wait_for_open_port(9102)
1701 succeed("curl http://localhost:9102/metrics | grep 'statsd_exporter_build_info{'")
1702 wait_until_succeeds(
1703 "echo 'test.udp:1|c' > /dev/udp/localhost/9125 && \
1704 curl http://localhost:9102/metrics | grep 'test_udp 1'",
1705 timeout=10
1706 )
1707 wait_until_succeeds(
1708 "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125 && \
1709 curl http://localhost:9102/metrics | grep 'test_tcp 1'",
1710 timeout=10
1711 )
1712 '';
1713 };
1714
1715 surfboard = {
1716 exporterConfig = {
1717 enable = true;
1718 modemAddress = "localhost";
1719 };
1720 metricProvider = {
1721 systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
1722 services.nginx = {
1723 enable = true;
1724 virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
1725 return 204;
1726 '';
1727 };
1728 };
1729 exporterTest = ''
1730 wait_for_unit("nginx.service")
1731 wait_for_open_port(80)
1732 wait_for_unit("prometheus-surfboard-exporter.service")
1733 wait_for_open_port(9239)
1734 succeed("curl -sSf localhost:9239/metrics | grep 'surfboard_up 1'")
1735 '';
1736 };
1737
1738 systemd = {
1739 exporterConfig = {
1740 enable = true;
1741
1742 extraFlags = [
1743 "--systemd.collector.enable-restart-count"
1744 ];
1745 };
1746 metricProvider = { };
1747 exporterTest = ''
1748 wait_for_unit("prometheus-systemd-exporter.service")
1749 wait_for_open_port(9558)
1750 wait_until_succeeds(
1751 "curl -sSf localhost:9558/metrics | grep '{}'".format(
1752 'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
1753 )
1754 )
1755 succeed(
1756 "curl -sSf localhost:9558/metrics | grep '{}'".format(
1757 'systemd_service_restart_total{name="prometheus-systemd-exporter.service"} 0'
1758 )
1759 )
1760 '';
1761 };
1762
1763 unpoller = {
1764 nodeName = "unpoller";
1765 exporterConfig.enable = true;
1766 exporterConfig.controllers = [ { } ];
1767 exporterTest = ''
1768 wait_until_succeeds(
1769 'journalctl -eu prometheus-unpoller-exporter.service -o cat | grep "Connection Error"'
1770 )
1771 '';
1772 };
1773
1774 unbound = {
1775 exporterConfig = {
1776 enable = true;
1777 unbound.host = "unix:///run/unbound/unbound.ctl";
1778 };
1779 metricProvider = {
1780 services.unbound = {
1781 enable = true;
1782 localControlSocketPath = "/run/unbound/unbound.ctl";
1783 };
1784 systemd.services.prometheus-unbound-exporter.serviceConfig = {
1785 SupplementaryGroups = [ "unbound" ];
1786 };
1787 };
1788 exporterTest = ''
1789 wait_for_unit("unbound.service")
1790 wait_for_unit("prometheus-unbound-exporter.service")
1791 wait_for_open_port(9167)
1792 wait_until_succeeds("curl -sSf localhost:9167/metrics | grep 'unbound_up 1'")
1793 '';
1794 };
1795
1796 v2ray = {
1797 exporterConfig = {
1798 enable = true;
1799 };
1800
1801 metricProvider = {
1802 systemd.services.prometheus-nginx-exporter.after = [ "v2ray.service" ];
1803 services.v2ray = {
1804 enable = true;
1805 config = {
1806 stats = { };
1807 api = {
1808 tag = "api";
1809 services = [ "StatsService" ];
1810 };
1811 inbounds = [
1812 {
1813 port = 1080;
1814 listen = "127.0.0.1";
1815 protocol = "http";
1816 }
1817 {
1818 listen = "127.0.0.1";
1819 port = 54321;
1820 protocol = "dokodemo-door";
1821 settings = {
1822 address = "127.0.0.1";
1823 };
1824 tag = "api";
1825 }
1826 ];
1827 outbounds = [
1828 {
1829 protocol = "freedom";
1830 }
1831 {
1832 protocol = "freedom";
1833 settings = { };
1834 tag = "api";
1835 }
1836 ];
1837 routing = {
1838 strategy = "rules";
1839 settings = {
1840 rules = [
1841 {
1842 inboundTag = [ "api" ];
1843 outboundTag = "api";
1844 type = "field";
1845 }
1846 ];
1847 };
1848 };
1849 };
1850 };
1851 };
1852 exporterTest = ''
1853 wait_for_unit("prometheus-v2ray-exporter.service")
1854 wait_for_open_port(9299)
1855 succeed("curl -sSf localhost:9299/scrape | grep 'v2ray_up 1'")
1856 '';
1857 };
1858
1859 varnish = {
1860 exporterConfig = {
1861 enable = true;
1862 instance = "/run/varnish/varnish";
1863 group = "varnish";
1864 };
1865 metricProvider = {
1866 systemd.services.prometheus-varnish-exporter.after = [
1867 "varnish.service"
1868 ];
1869 services.varnish = {
1870 enable = true;
1871 stateDir = "/run/varnish/varnish";
1872 config = ''
1873 vcl 4.0;
1874 backend default {
1875 .host = "127.0.0.1";
1876 .port = "80";
1877 }
1878 '';
1879 };
1880 };
1881 exporterTest = ''
1882 wait_for_unit("prometheus-varnish-exporter.service")
1883 wait_for_open_port(6081)
1884 wait_for_open_port(9131)
1885 succeed("curl -sSf http://localhost:9131/metrics | grep 'varnish_up 1'")
1886 '';
1887 };
1888
1889 wireguard =
1890 let
1891 snakeoil = import ./wireguard/snakeoil-keys.nix;
1892 publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
1893 in
1894 {
1895 exporterConfig.enable = true;
1896 metricProvider = {
1897 networking.wireguard.interfaces.wg0 = {
1898 ips = [
1899 "10.23.42.1/32"
1900 "fc00::1/128"
1901 ];
1902 listenPort = 23542;
1903
1904 inherit (snakeoil.peer0) privateKey;
1905
1906 peers = singleton {
1907 allowedIPs = [
1908 "10.23.42.2/32"
1909 "fc00::2/128"
1910 ];
1911
1912 inherit (snakeoil.peer1) publicKey;
1913 };
1914 };
1915 systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
1916 };
1917 exporterTest = ''
1918 wait_for_unit("prometheus-wireguard-exporter.service")
1919 wait_for_open_port(9586)
1920 wait_until_succeeds(
1921 "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
1922 )
1923 '';
1924 };
1925
1926 zfs = {
1927 exporterConfig = {
1928 enable = true;
1929 };
1930 metricProvider = {
1931 boot.supportedFilesystems = [ "zfs" ];
1932 networking.hostId = "7327ded7";
1933 };
1934 exporterTest = ''
1935 wait_for_unit("prometheus-zfs-exporter.service")
1936 wait_for_unit("zfs.target")
1937 wait_for_open_port(9134)
1938 wait_until_succeeds("curl -f localhost:9134/metrics | grep 'zfs_scrape_collector_success{.*} 1'")
1939 '';
1940 };
1941 };
1942in
1943mapAttrs (
1944 exporter: testConfig:
1945 (makeTest (
1946 let
1947 nodeName = testConfig.nodeName or exporter;
1948
1949 in
1950 {
1951 name = "prometheus-${exporter}-exporter";
1952
1953 nodes.${nodeName} = mkMerge [
1954 {
1955 services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
1956 }
1957 testConfig.metricProvider or { }
1958 ];
1959
1960 testScript = ''
1961 ${nodeName}.start()
1962 ${concatStringsSep "\n" (
1963 map (
1964 line:
1965 if
1966 builtins.any (b: b) [
1967 (builtins.match "^[[:space:]]*$" line != null)
1968 (builtins.substring 0 1 line == "#")
1969 (builtins.substring 0 1 line == " ")
1970 (builtins.substring 0 1 line == ")")
1971 ]
1972 then
1973 line
1974 else
1975 "${nodeName}.${line}"
1976 ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest))
1977 )}
1978 ${nodeName}.shutdown()
1979 '';
1980
1981 meta.maintainers = [ ];
1982 }
1983 ))
1984) exporterTests