diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index 8494b62e6ff0..c8f63fecf133 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -233,6 +233,13 @@
services.kanata.
+
+
+ karma,
+ an alert dashboard for Prometheus Alertmanager. Available as
+ services.karma
+
+
languagetool,
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index a5ba4841f549..5a2b92c7d39a 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -84,6 +84,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [kanata](https://github.com/jtroo/kanata), a tool to improve keyboard comfort and usability with advanced customization.
Available as [services.kanata](options.html#opt-services.kanata.enable).
+- [karma](https://github.com/prymitive/karma), an alert dashboard for Prometheus Alertmanager. Available as [services.karma](options.html#opt-services.karma.enable)
+
- [languagetool](https://languagetool.org/), a multilingual grammar, style, and spell checker.
Available as [services.languagetool](options.html#opt-services.languagetool.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index dec66e395aad..c08632061a9d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -682,6 +682,7 @@
./services/monitoring/heapster.nix
./services/monitoring/incron.nix
./services/monitoring/kapacitor.nix
+ ./services/monitoring/karma.nix
./services/monitoring/kthxbye.nix
./services/monitoring/loki.nix
./services/monitoring/longview.nix
diff --git a/nixos/modules/services/monitoring/karma.nix b/nixos/modules/services/monitoring/karma.nix
new file mode 100644
index 000000000000..85dbc81f443f
--- /dev/null
+++ b/nixos/modules/services/monitoring/karma.nix
@@ -0,0 +1,128 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+ cfg = config.services.karma;
+ yaml = pkgs.formats.yaml { };
+in
+{
+ options.services.karma = {
+ enable = mkEnableOption (mdDoc "the Karma dashboard service");
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.karma;
+ defaultText = literalExpression "pkgs.karma";
+ description = mdDoc ''
+ The Karma package that should be used.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.path;
+ default = yaml.generate "karma.yaml" cfg.settings;
+ defaultText = "A configuration file generated from the provided nix attributes settings option.";
+ description = mdDoc ''
+ A YAML config file which can be used to configure karma instead of the nix-generated file.
+ '';
+ example = "/etc/karma/karma.conf";
+ };
+
+ environment = mkOption {
+ type = with types; attrsOf str;
+ default = {};
+ description = mdDoc ''
+ Additional environment variables to provide to karma.
+ '';
+ example = {
+ ALERTMANAGER_URI = "https://alertmanager.example.com";
+ ALERTMANAGER_NAME= "single";
+ };
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = mdDoc ''
+ Whether to open ports in the firewall needed for karma to function.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = mdDoc ''
+ Extra command line options.
+ '';
+ example = [
+ "--alertmanager.timeout 10s"
+ ];
+ };
+
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = yaml.type;
+
+ options.listen = {
+ address = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = mdDoc ''
+ Hostname or IP to listen on.
+ '';
+ example = "[::]";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 8080;
+ description = mdDoc ''
+ HTTP port to listen on.
+ '';
+ example = 8182;
+ };
+ };
+ };
+ default = {
+ listen = {
+ address = "127.0.0.1";
+ };
+ };
+ description = mdDoc ''
+ Karma dashboard configuration as nix attributes.
+
+ Reference:
+ '';
+ example = {
+ listen = {
+ address = "192.168.1.4";
+ port = "8000";
+ prefix = "/dashboard";
+ };
+ alertmanager = {
+ interval = "15s";
+ servers = [
+ {
+ name = "prod";
+ uri = "http://alertmanager.example.com";
+ }
+ ];
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.karma = {
+ description = "Alert dashboard for Prometheus Alertmanager";
+ wantedBy = [ "multi-user.target" ];
+ environment = cfg.environment;
+ serviceConfig = {
+ Type = "simple";
+ DynamicUser = true;
+ Restart = "on-failure";
+ ExecStart = "${pkgs.karma}/bin/karma --config.file ${cfg.configFile} ${concatStringsSep " " cfg.extraOptions}";
+ };
+ };
+ networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.listen.port ];
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 36c51b573100..a872ec86db3b 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -299,6 +299,7 @@ in {
k3s = handleTest ./k3s {};
kafka = handleTest ./kafka.nix {};
kanidm = handleTest ./kanidm.nix {};
+ karma = handleTest ./karma.nix {};
kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
kea = handleTest ./kea.nix {};
diff --git a/nixos/tests/karma.nix b/nixos/tests/karma.nix
new file mode 100644
index 000000000000..5ac2983b8aa3
--- /dev/null
+++ b/nixos/tests/karma.nix
@@ -0,0 +1,84 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+ name = "karma";
+ nodes = {
+ server = { ... }: {
+ services.prometheus.alertmanager = {
+ enable = true;
+ logLevel = "debug";
+ port = 9093;
+ openFirewall = true;
+ configuration = {
+ global = {
+ resolve_timeout = "1m";
+ };
+ route = {
+ # Root route node
+ receiver = "test";
+ group_by = ["..."];
+ continue = false;
+ group_wait = "1s";
+ group_interval="15s";
+ repeat_interval = "24h";
+ };
+ receivers = [
+ {
+ name = "test";
+ webhook_configs = [
+ {
+ url = "http://localhost:1234";
+ send_resolved = true;
+ max_alerts = 0;
+ }
+ ];
+ }
+ ];
+ };
+ };
+ services.karma = {
+ enable = true;
+ openFirewall = true;
+ settings = {
+ listen = {
+ address = "0.0.0.0";
+ port = 8081;
+ };
+ alertmanager = {
+ servers = [
+ {
+ name = "alertmanager";
+ uri = "https://127.0.0.1:9093";
+ }
+ ];
+ };
+ karma.name = "test-dashboard";
+ log.config = true;
+ log.requests = true;
+ log.timestamp = true;
+ };
+ };
+ };
+ };
+
+ testScript = ''
+ start_all()
+
+ with subtest("Wait for server to come up"):
+
+ server.wait_for_unit("alertmanager.service")
+ server.wait_for_unit("karma.service")
+
+ server.sleep(5) # wait for both services to settle
+
+ server.wait_for_open_port(9093)
+ server.wait_for_open_port(8081)
+
+ with subtest("Test alertmanager readiness"):
+ server.succeed("curl -s http://127.0.0.1:9093/-/ready")
+
+ # Karma only starts serving the dashboard once it has established connectivity to all alertmanagers in its config
+ # Therefore, this will fail if karma isn't able to reach alertmanager
+ server.succeed("curl -s http://127.0.0.1:8081")
+
+ server.shutdown()
+ '';
+})