From 944ef0ea3b9c84926bbe046b817b07be59bf9912 Mon Sep 17 00:00:00 2001 From: Dustin Frisch Date: Wed, 28 May 2025 17:23:43 +0200 Subject: [PATCH] nixos/perses: init --- .../manual/release-notes/rl-2605.section.md | 2 + nixos/modules/module-list.nix | 1 + nixos/modules/services/monitoring/perses.nix | 131 ++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/perses.nix | 65 +++++++++ pkgs/by-name/pe/perses/package.nix | 3 + 6 files changed, 203 insertions(+) create mode 100644 nixos/modules/services/monitoring/perses.nix create mode 100644 nixos/tests/perses.nix diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index a708c285091b..adbeeafb6a6f 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -66,6 +66,8 @@ - [Shoko](https://shokoanime.com), an anime management system. Available as [services.shoko](#opt-services.shoko.enable). +- [perses](https://perses.dev/), the open dashboard tool for Prometheus and other data sources. Available as [services.perses](#opt-services.perses.enable). + - [Drasl](https://github.com/unmojang/drasl), an alternative authentication server for Minecraft. Available as [services.drasl](#opt-services.drasl.enable). ## Backward Incompatibilities {#sec-release-26.05-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2db1636f824e..615f4de6f778 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1038,6 +1038,7 @@ ./services/monitoring/opentelemetry-collector.nix ./services/monitoring/osquery.nix ./services/monitoring/parsedmarc.nix + ./services/monitoring/perses.nix ./services/monitoring/pgscv.nix ./services/monitoring/prometheus/alertmanager-gotify-bridge.nix ./services/monitoring/prometheus/alertmanager-irc-relay.nix diff --git a/nixos/modules/services/monitoring/perses.nix b/nixos/modules/services/monitoring/perses.nix new file mode 100644 index 000000000000..352cdd1eaee3 --- /dev/null +++ b/nixos/modules/services/monitoring/perses.nix @@ -0,0 +1,131 @@ +{ + pkgs, + lib, + config, + utils, + ... +}: + +let + inherit (lib) + getExe + mkOption + mkEnableOption + mkPackageOption + mkIf + types + ; + + cfg = config.services.perses; + + settingsFormat = pkgs.formats.yaml { }; + + configPath = "/run/perses/config.yaml"; + secretsReplacement = utils.genJqSecretsReplacement { + loadCredential = true; + } cfg.settings configPath; + +in +{ + options.services.perses = { + enable = mkEnableOption "perses"; + + package = mkPackageOption pkgs "perses" { }; + + port = mkOption { + type = types.port; + default = 8080; + description = '' + Perses Web interface port. + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = ""; + description = '' + Address to listen on. Empty string will listen on all interfaces. + ''; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + }; + description = '' + Perses settings. See for available options. + You can specify secret values in this configuration by setting `somevalue._secret = "/path/to/file"` instead of setting `somevalue` directly. + ''; + default = { }; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "-web.telemetry-path=/metrics" + ]; + description = "Additional options passed to perses daemon."; + }; + }; + + config = mkIf cfg.enable { + systemd.services.perses = { + description = "Perses Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + + preStart = secretsReplacement.script; + + serviceConfig = rec { + ExecStart = utils.escapeSystemdExecArgs ( + [ + (getExe cfg.package) + "-config=${configPath}" + "-web.listen-address=${cfg.listenAddress}:${toString cfg.port}" + ] + ++ cfg.extraOptions + ); + + User = "perses"; + DynamicUser = true; + Restart = "on-failure"; + RuntimeDirectory = "perses"; + RuntimeDirectoryMode = "0755"; + StateDirectory = "perses"; + WorkingDirectory = "%S/${StateDirectory}"; + + LoadCredential = secretsReplacement.credentials; + + # Hardening + AmbientCapabilities = mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = if (cfg.port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ]; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = "0027"; + }; + }; + + environment.systemPackages = [ cfg.package ]; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 343500f195e2..763441391abb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1249,6 +1249,7 @@ in peerflix = runTest ./peerflix.nix; peering-manager = runTest ./web-apps/peering-manager.nix; peertube = handleTestOn [ "x86_64-linux" ] ./web-apps/peertube.nix { }; + perses = runTest ./perses.nix; pgadmin4 = runTest ./pgadmin4.nix; pgbackrest = import ./pgbackrest { inherit runTest; }; pgbouncer = runTest ./pgbouncer.nix; diff --git a/nixos/tests/perses.nix b/nixos/tests/perses.nix new file mode 100644 index 000000000000..dc5d8ee39f36 --- /dev/null +++ b/nixos/tests/perses.nix @@ -0,0 +1,65 @@ +{ pkgs, lib, ... }: + +{ + name = "perses"; + + meta.maintainers = with lib.maintainers; [ fooker ]; + + nodes.prometheus = { + services.prometheus.enable = true; + networking.firewall.allowedTCPPorts = [ 9090 ]; + }; + + nodes.machine = { + services.perses = { + enable = true; + + settings.provisioning.folders = [ + (pkgs.writeTextDir "perses-test-provision-project.yaml" '' + kind: "Project" + metadata: + name: "my-project" + '') + (pkgs.writeTextDir "perses-test-provision-datasource.yaml" '' + kind: "Datasource" + metadata: + name: "my-prometheus" + project: "my-project" + spec: + default: true + plugin: + kind: "PrometheusDatasource" + spec: + proxy: + kind: "HTTPProxy" + spec: + url: "http://prometheus:9090/" + '') + ]; + }; + }; + + testScript = '' + start_all() + + prometheus.wait_for_unit("prometheus.service") + prometheus.wait_for_open_port(9090) + + with subtest("Perses starts"): + machine.wait_for_unit("perses.service") + machine.wait_for_open_port(8080) + machine.succeed("percli login http://127.0.0.1:8080") + machine.succeed("percli version") + machine.succeed("percli config") + machine.succeed("percli plugin list | grep 'Prometheus'") + + with subtest("Query resources"): + machine.succeed("percli get projects | grep my-project") + machine.succeed("percli project my-project") + machine.succeed("percli get datasources | grep my-prometheus") + machine.succeed("curl --fail -s 127.0.0.1:8080/api/v1/projects/my-project/datasources/my-prometheus") + + with subtest("Datasource check"): + machine.succeed("curl --fail -s http://127.0.0.1:8080/proxy/projects/my-project/datasources/my-prometheus/api/v1/status/config") + ''; +} diff --git a/pkgs/by-name/pe/perses/package.nix b/pkgs/by-name/pe/perses/package.nix index a7b17d8c9b35..cdc98c312aa2 100644 --- a/pkgs/by-name/pe/perses/package.nix +++ b/pkgs/by-name/pe/perses/package.nix @@ -10,6 +10,7 @@ turbo, linkFarm, installShellFiles, + nixosTests, }: let @@ -118,6 +119,8 @@ buildGoModule (finalAttrs: { passthru = { updateScript = ./update.sh; + tests.nixos = nixosTests.perses; + inherit pluginsArchive; };