diff --git a/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixos/modules/services/monitoring/grafana-image-renderer.nix index 57c4f377f440..759006ec52b8 100644 --- a/nixos/modules/services/monitoring/grafana-image-renderer.nix +++ b/nixos/modules/services/monitoring/grafana-image-renderer.nix @@ -2,97 +2,178 @@ lib, pkgs, config, + utils, ... }: let cfg = config.services.grafana-image-renderer; - format = pkgs.formats.json { }; + format = { + type = + with lib.types; + attrsOf ( + attrsOf (oneOf [ + str + int + bool + (listOf (oneOf [ + str + int + ])) + ]) + ); - configFile = format.generate "grafana-image-renderer-config.json" cfg.settings; + generate = lib.flip lib.pipe [ + # Remove legacy option prefixes that only exist for backwards-compat + (lib.flip builtins.removeAttrs [ + "service" + "rendering" + "assertions" + "warnings" + ]) + + # Normalize CLI args from a nested attr-set to `section.option = value` + (lib.concatMapAttrs (block: lib.mapAttrs' (option: lib.nameValuePair "${block}.${option}"))) + + # Turn attr-set into a list of arguments, denormalize list options. + (lib.mapAttrsToList ( + option: value: + if lib.isBool value then + lib.optional value "--${option}" + else if lib.isList value then + map (v: [ + "--${option}" + v + ]) value + else + [ + "--${option}" + (toString value) + ] + )) + lib.flatten + + # Turn into a string + utils.escapeSystemdExecArgs + ]; + }; in { + imports = [ + (lib.mkChangedOptionModule + [ + "services" + "grafana-image-renderer" + "chromium" + ] + [ + "services" + "grafana-image-renderer" + "settings" + "browser" + "path" + ] + (config: lib.getExe config.services.grafana-image-renderer.chromium) + ) + (lib.mkRemovedOptionModule + [ + "services" + "grafana-image-renderer" + "verbose" + ] + '' + Use `services.grafana-image-renderer.settings.log.level = "debug"` instead. + '' + ) + ]; + options.services.grafana-image-renderer = { enable = lib.mkEnableOption "grafana-image-renderer"; - chromium = lib.mkOption { - type = lib.types.package; - description = '' - The chromium to use for image rendering. - ''; - }; - - verbose = lib.mkEnableOption "verbosity for the service"; - provisionGrafana = lib.mkEnableOption "Grafana configuration for grafana-image-renderer"; settings = lib.mkOption { type = lib.types.submodule { freeformType = format.type; + imports = [ + ../../misc/assertions.nix + (lib.mkRenamedOptionModule + [ + "rendering" + "width" + ] + [ + "browser" + "min-width" + ] + ) + (lib.mkRenamedOptionModule + [ + "rendering" + "height" + ] + [ + "browser" + "min-height" + ] + ) + (lib.mkRenamedOptionModule + [ + "rendering" + "args" + ] + [ + + "browser" + "flag" + ] + ) + (lib.mkChangedOptionModule + [ + "service" + "port" + ] + [ + "server" + "addr" + ] + (config: "0.0.0.0:${toString config.service.port}") + ) + (lib.mkRemovedOptionModule + [ + "rendering" + "mode" + ] + '' + This option is obsolete. + '' + ) + (lib.mkRenamedOptionModule + [ + "service" + "logging" + ] + [ + "log" + ] + ) + ]; options = { - service = { - port = lib.mkOption { - type = lib.types.port; - default = 8081; - description = '' - The TCP port to use for the rendering server. - ''; - }; - logging.level = lib.mkOption { - type = lib.types.enum [ - "error" - "warning" - "info" - "debug" - ]; - default = "info"; - description = '' - The log-level of the {file}`grafana-image-renderer.service`-unit. - ''; - }; + server.addr = lib.mkOption { + type = lib.types.str; + default = "localhost:8081"; + description = '' + Listen address of the service. + ''; }; - rendering = { - width = lib.mkOption { - default = 1000; - type = lib.types.ints.positive; - description = '' - Width of the PNG used to display the alerting graph. - ''; - }; - height = lib.mkOption { - default = 500; - type = lib.types.ints.positive; - description = '' - Height of the PNG used to display the alerting graph. - ''; - }; - mode = lib.mkOption { - default = "default"; - type = lib.types.enum [ - "default" - "reusable" - "clustered" - ]; - description = '' - Rendering mode of `grafana-image-renderer`: - - - `default:` Creates on browser-instance - per rendering request. - - `reusable:` One browser instance - will be started and reused for each rendering request. - - `clustered:` allows to precisely - configure how many browser-instances are supposed to be used. The values - for that mode can be declared in `rendering.clustering`. - ''; - }; - args = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ "--no-sandbox" ]; - description = '' - List of CLI flags passed to `chromium`. - ''; - }; + browser.path = lib.mkOption { + type = lib.types.path; + default = lib.getExe pkgs.chromium; + defaultText = lib.literalExpression "lib.getExe pkgs.chromium"; + description = '' + Path to the executable of the chromium to use. + ''; }; }; }; @@ -101,9 +182,6 @@ in description = '' Configuration attributes for `grafana-image-renderer`. - - See - for supported values. ''; }; }; @@ -120,39 +198,53 @@ in ]; services.grafana.settings.rendering = lib.mkIf cfg.provisionGrafana { - server_url = "http://localhost:${toString cfg.settings.service.port}/render"; + server_url = "http://${toString cfg.settings.server.addr}/render"; callback_url = "http://${config.services.grafana.settings.server.http_addr}:${toString config.services.grafana.settings.server.http_port}"; }; - services.grafana-image-renderer.chromium = lib.mkDefault pkgs.chromium; - services.grafana-image-renderer.settings = { - rendering = lib.mapAttrs (lib.const lib.mkDefault) { - chromeBin = "${cfg.chromium}/bin/chromium"; - verboseLogging = cfg.verbose; - timezone = config.time.timeZone; - }; - - service = { - logging.level = lib.mkIf cfg.verbose (lib.mkDefault "debug"); - metrics.enabled = lib.mkDefault false; - }; + browser.timezone = lib.mkIf (config.time.timeZone != null) (lib.mkDefault config.time.timeZone); }; systemd.services.grafana-image-renderer = { wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; description = "Grafana backend plugin that handles rendering of panels & dashboards to PNGs using headless browser (Chromium/Chrome)"; - environment = { - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = "true"; - }; - serviceConfig = { DynamicUser = true; PrivateTmp = true; - ExecStart = "${pkgs.grafana-image-renderer}/bin/grafana-image-renderer server --config=${configFile}"; + ExecStart = "${lib.getExe pkgs.grafana-image-renderer} server ${format.generate cfg.settings}"; Restart = "always"; + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + LockPersonality = true; + MountAPIVFS = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = "strict"; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = 27; }; }; }; diff --git a/pkgs/by-name/gr/grafana-image-renderer/package.json b/pkgs/by-name/gr/grafana-image-renderer/package.json deleted file mode 100644 index 857923067e87..000000000000 --- a/pkgs/by-name/gr/grafana-image-renderer/package.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "name": "renderer", - "version": "1.0.0", - "author": "Grafana Labs", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/grafana/grafana-image-renderer.git" - }, - "scripts": { - "eslint": "eslint .", - "typecheck": "tsc --noEmit", - "prettier:check": "prettier --list-different \"**/*.ts\"", - "prettier:write": "prettier --list-different \"**/*.ts\" --write", - "precommit": "npm run eslint & npm run typecheck", - "watch": "tsc-watch --onSuccess \"node build/app.js server --config=dev.json\"", - "watch:debug": "tsc-watch --onSuccess \"cross-env DEBUG=puppeteer-cluster:* node build/app.js server --config=dev.json\"", - "build": "tsc", - "start": "node build/app.js server --config=dev.json", - "create-gcom-plugin-json": "ts-node scripts/createGcomPluginJson.ts ./scripts/tmp", - "push-to-gcom": "sh ./scripts/push-to-gcom.sh", - "test-update": "cross-env UPDATE_GOLDEN=true jest", - "test": "sh ./scripts/run_tests.sh", - "test-ci": "jest", - "test-diff": "cross-env SAVE_DIFF=true jest" - }, - "dependencies": { - "@grpc/grpc-js": "^1.8.22", - "@grpc/proto-loader": "^0.7.2", - "@hapi/boom": "^10.0.0", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/auto-instrumentations-node": "^0.49.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.52.1", - "@opentelemetry/resources": "^1.25.1", - "@opentelemetry/sdk-node": "^0.52.1", - "@opentelemetry/semantic-conventions": "^1.25.1", - "@puppeteer/browsers": "^2.3.1", - "chokidar": "^3.5.2", - "dompurify": "^3.2.4", - "express": "^4.21.1", - "express-prom-bundle": "^6.5.0", - "ioredis": "^5.6.1", - "jimp": "^0.22.12", - "jsdom": "20.0.0", - "lodash": "^4.17.21", - "minimist": "^1.2.6", - "morgan": "^1.9.0", - "multer": "^2.0.2", - "on-finished": "^2.3.0", - "poolpeteer": "^0.24.0", - "prom-client": "^14.1.0", - "puppeteer": "^22.8.2", - "puppeteer-cluster": "^0.24.0", - "rate-limiter-flexible": "^7.0.0", - "tar-fs": "^3.0.9", - "unique-filename": "^2.0.1", - "winston": "^3.8.2" - }, - "devDependencies": { - "@eslint/js": "^9.31.0", - "@grafana/eslint-config": "^8.1.0", - "@grafana/sign-plugin": "^3.1.3", - "@stylistic/eslint-plugin-ts": "^4.4.1", - "@types/content-disposition": "^0.5.9", - "@types/express": "^4.17.14", - "@types/jest": "^29.5.12", - "@types/jsdom": "20.0.0", - "@types/lodash": "^4.17.20", - "@types/minimist": "^1.2.5", - "@types/morgan": "^1.9.10", - "@types/multer": "^1.4.7", - "@types/node": "^20.17.27", - "@types/pixelmatch": "^5.2.6", - "@types/supertest": "^2.0.15", - "@typescript-eslint/eslint-plugin": "^8.37.0", - "@typescript-eslint/parser": "^8.37.0", - "@yao-pkg/pkg": "^6.3.0", - "axios": "1.8.2", - "cross-env": "7.0.3", - "eslint": "^9.31.0", - "eslint-config-prettier": "^10.1.5", - "eslint-plugin-jsdoc": "^51.4.1", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^5.2.0", - "fast-png": "^6.2.0", - "jest": "^29.7.0", - "jsonwebtoken": "^9.0.2", - "lint-staged": "13.0.3", - "prettier": "2.7.1", - "supertest": "^7.0.0", - "ts-jest": "^29.1.1", - "ts-node": "10.9.1", - "tsc-watch": "5.0.3", - "typescript": "^5.8.3", - "typescript-eslint": "^8.37.0" - }, - "lint-staged": { - "*.ts": [ - "prettier --write" - ] - }, - "pkg": { - "assets": "proto/*" - }, - "bin": "build/app.js", - "engines": { - "node": ">= 22" - } -} diff --git a/pkgs/by-name/gr/grafana-image-renderer/package.nix b/pkgs/by-name/gr/grafana-image-renderer/package.nix index 89ee72cd2a5b..e7ca2beb0840 100644 --- a/pkgs/by-name/gr/grafana-image-renderer/package.nix +++ b/pkgs/by-name/gr/grafana-image-renderer/package.nix @@ -1,68 +1,23 @@ { lib, - mkYarnPackage, + buildGoModule, fetchFromGitHub, - fetchYarnDeps, - nodejs, - runtimeShell, }: -# Notes for the upgrade: -# * Download the tarball of the new version to use. -# * Replace new `package.json` here. -# * Update `version`+`hash` and rebuild. - -mkYarnPackage rec { +buildGoModule (finalAttrs: { pname = "grafana-image-renderer"; - version = "4.0.14"; + version = "5.0.10"; src = fetchFromGitHub { owner = "grafana"; repo = "grafana-image-renderer"; - rev = "v${version}"; - hash = "sha256-CoQTOzQ7h31B3U0yvJYsgC3uaSyjNNLpD+8uMN+naiQ="; + tag = "v${finalAttrs.version}"; + hash = "sha256-oWJlb1mV1sNgN7EQ8L4msfnKps5oV60JgwZYpAJQaq4="; }; - offlineCache = fetchYarnDeps { - yarnLock = src + "/yarn.lock"; - hash = "sha256-xZrIoQlPeyGTbFRUQ0M8Tc6YpzsC5IACW0bbZ+HnsOQ="; - }; + vendorHash = "sha256-wA1XeLO2bYwq7HZOQ5UNcdqqJdEWRUxFoAQucXAj48k="; - packageJSON = ./package.json; - - buildPhase = '' - runHook preBuild - - pushd deps/renderer - yarn run build - popd - - runHook postBuild - ''; - - dontInstall = true; - - distPhase = '' - runHook preDist - - shopt -s extglob - - pushd deps/renderer - install_path="$out/libexec/grafana-image-renderer" - mkdir -p $install_path - cp -R ../../node_modules $install_path - cp -R ./!(node_modules) $install_path - popd - - mkdir -p $out/bin - cat >$out/bin/grafana-image-renderer <