{ options, config, lib, pkgs, ... }: let inherit (lib) mkEnableOption mkOption literalExpression types mkDefault mkIf recursiveUpdate ; cfg = config.services.grafana-magic; settingsFile = settingsFormatIni.generate "config.ini" (recursiveUpdate cfg.settings { paths.provisioning = "/etc/grafana.d/provisioning"; }); provisioningSettingsFormat = pkgs.formats.yaml {}; settingsFormatIni = pkgs.formats.ini {}; in { options.services.grafana-magic = { enable = mkEnableOption (lib.mdDoc "grafana"); package = mkOption { description = lib.mdDoc "Package to use."; default = pkgs.grafana; defaultText = literalExpression "pkgs.grafana"; type = types.package; }; dataDir = mkOption { description = lib.mdDoc "Data directory."; default = "/var/lib/grafana"; type = types.path; }; settings = mkOption { description = lib.mdDoc '' Grafana settings. See for available options. INI format is used. ''; type = types.submodule { freeformType = settingsFormatIni.type; options = { paths.provisioning = mkOption { type = types.submodule { options = let provisioningOption = name: cname: mkOption { type = types.submodule { options = { apiVersion = mkOption { type = types.int; default = 1; }; "delete${cname}" = mkOption { type = provisioningSettingsFormat.type; default = []; }; "${name}" = mkOption { type = provisioningSettingsFormat.type; default = []; }; }; }; default = {}; }; in { datasources = provisioningOption "datasources" "Datasources"; plugins = provisioningOption "plugins" "Plugins"; dashboards = provisioningOption "dashboards" "Dashboards"; notifiers = provisioningOption "notifiers" "Notifiers"; alerting = provisioningOption "alerting" "Alerting"; }; }; default = {}; apply = x: let ln = name: '' mkdir -p $out/${name} ln -s ${provisioningSettingsFormat.generate "config.yaml" x.${name}} $out/${name}/config.yaml ''; in pkgs.runCommand "grafana-provisioning" {} '' ${ln "datasources"} ${ln "notifiers"} ${ln "alerting"} ${ln "plugins"} ${ln "dashboards"} ''; }; }; }; default = {}; }; }; config = mkIf cfg.enable { environment.systemPackages = [cfg.package]; services.grafana-magic.settings = { server = { static_root_path = "${cfg.package}/share/grafana/public"; http_port = mkDefault 3000; protocol = mkDefault "http"; }; }; environment.etc."grafana.d/main.ini" = { source = settingsFile; }; environment.etc."grafana.d/provisioning" = { source = cfg.settings.paths.provisioning; }; systemd.services.grafana = { description = "Grafana Service Daemon"; wantedBy = ["multi-user.target"]; after = ["networking.target"]; serviceConfig = { ExecStart = "${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${settingsFile}"; WorkingDirectory = cfg.dataDir; User = "grafana"; RuntimeDirectory = "grafana"; RuntimeDirectoryMode = "0755"; # Hardening AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) ["CAP_NET_BIND_SERVICE"]; CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then ["CAP_NET_BIND_SERVICE"] else [""]; DeviceAllow = [""]; 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" "AF_UNIX"]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; # Upstream grafana is not setting SystemCallFilter for compatibility # reasons, see https://github.com/grafana/grafana/pull/40176 SystemCallFilter = [ "@system-service" "~@privileged" ] ++ lib.optional (cfg.settings.server.protocol == "socket") ["@chown"]; UMask = "0027"; }; preStart = '' ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir} ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir} ''; }; users.users.grafana = { uid = config.ids.uids.grafana; description = "Grafana user"; home = cfg.dataDir; createHome = true; group = "grafana"; }; users.groups.grafana = {}; }; }