{ options, config, lib, pkgs, ... }: with lib; let cfg = config.services.grafana-magic; configFile = settingsFormatIni.generate "config.ini" cfg.settings; 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: mkOption { type = provisioningSettingsFormat.type; default = {}; }; in { datasources = provisioningOption "datasources"; plugins = provisioningOption "plugins"; dashboards = provisioningOption "dashboards"; }; }; default = {}; apply = x: let ln = name: '' mkdir ${name} ln -s ${provisioningSettingsFormat.generate "config.yaml" x.${name}} ${name}/config.yaml ''; in pkgs.runCommand "grafana-provisioning" {} '' mkdir -p $out/{datasources,dashboards,plugins} ${ln "datasources"} ${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"; }; }; 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 ${configFile}"; 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 = {}; }; }