diff options
-rw-r--r-- | nixos/helpers.nix | 11 | ||||
-rw-r--r-- | nixos/hosts/tente/default.nix | 7 | ||||
-rw-r--r-- | nixos/hosts/tente/matrix.nix | 39 | ||||
-rw-r--r-- | nixos/hosts/tente/monitoring.nix | 287 | ||||
-rw-r--r-- | nixos/npins/sources.json | 34 | ||||
-rw-r--r-- | nixos/secrets/grafana-matrix-forwarder-env.age | bin | 0 -> 449 bytes | |||
-rw-r--r-- | nixos/secrets/secrets.nix | 2 | ||||
-rw-r--r-- | nixos/shared/alloy-nix-config/alloy_nix_config.go | 129 | ||||
-rw-r--r-- | nixos/shared/alloy-nix-config/default.nix | 8 | ||||
-rw-r--r-- | nixos/shared/alloy-nix-config/go.mod | 3 | ||||
-rw-r--r-- | nixos/shared/grafana-matrix-forwarder/default.nix | 10 | ||||
-rw-r--r-- | nixos/shared/grafana-matrix-forwarder/service.nix | 31 | ||||
-rw-r--r-- | nixos/shared/prometheus-sql-exporter/default.nix | 20 | ||||
-rw-r--r-- | nixos/shared/prometheus-sql-exporter/service.nix | 13 |
14 files changed, 401 insertions, 193 deletions
diff --git a/nixos/helpers.nix b/nixos/helpers.nix index cadf230..f8f1384 100644 --- a/nixos/helpers.nix +++ b/nixos/helpers.nix @@ -60,6 +60,17 @@ '}'; ''; + writeAlloyConfig = cfg: + let + alloy-nix-config = "${pkgs.callPackage <top/shared/alloy-nix-config> {}}/bin/alloy-nix-config"; + in + pkgs.runCommand "generated-config" {} '' + ${alloy-nix-config} ${pkgs.writeText "input.json" (builtins.toJSON cfg)} $out + # FUTURE: run alloy validate + ''; + + alloyConfigRef = s: { "$ref" = s; }; + joinWgNamespace = ns: cfg: lib.attrsets.recursiveUpdate cfg { bindsTo = ["netns@${ns}.service"]; diff --git a/nixos/hosts/tente/default.nix b/nixos/hosts/tente/default.nix index ac8f438..f03156b 100644 --- a/nixos/hosts/tente/default.nix +++ b/nixos/hosts/tente/default.nix @@ -9,8 +9,10 @@ let acmeEmail = "martin@push-f.com"; sources = import <top/npins>; helpers = import <top/helpers.nix> { inherit config lib pkgs; }; + pkgs-unstable = import sources.nixpkgs-unstable {}; in -{ +rec { + _module.args = { inherit pkgs-unstable; }; imports = [ ./hardware-configuration.nix <top/profiles/server> @@ -116,6 +118,7 @@ in monitoring.grafanaUiPort = 3000; monitoring.alloyUiPort = 3001; + monitoring.grafanaMatrixForwarderPort = 3002; monitoring.lokiPort = 3030; gotify.port = 4000; monitoring.prometheusNodeExporterPort = 9002; @@ -123,6 +126,8 @@ in headscale.port = 8080; matrix.port = 8008; + monitoring.matrixServerUrl = "http://localhost:${toString matrix.port}"; + # Use the GRUB 2 boot loader. boot.loader.grub.enable = true; # boot.loader.grub.efiSupport = true; diff --git a/nixos/hosts/tente/matrix.nix b/nixos/hosts/tente/matrix.nix index 2eb8673..89782fc 100644 --- a/nixos/hosts/tente/matrix.nix +++ b/nixos/hosts/tente/matrix.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, pkgs-unstable, ... }: let cfg = config.matrix; @@ -19,32 +19,19 @@ in config = { services = { - matrix-synapse = { + matrix-conduit = { enable = true; + package = pkgs-unstable.matrix-conduit; settings = { - server_name = cfg.serverName; - listeners = [{ - # This listener matches the default of NixOS 24.11 (replicated here to make the port configurable). - bind_addresses = ["127.0.0.1"]; + global = { + server_name = cfg.serverName; port = cfg.port; - resources = [ - { - compress = true; - names = ["client"]; - } - { - compress = false; - names = ["federation"]; - } - ]; - tls = false; - type = "http"; - x_forwarded = true; - }]; + address = "127.0.0.1"; # this is the default of conduit but the nixos service defaults to ::1 + database_backend = "rocksdb"; + enable_lightning_bolt = false; + allow_registration = false; + }; }; - - # The default is INFO which can easily spam the systemd journal with 500k messages a day. - log.root.level = "WARNING"; }; nginx.virtualHosts.${cfg.apiDomain} = { @@ -54,14 +41,12 @@ in # TODO: add locations."/" with some message - # Forward all Matrix API calls to the synapse Matrix homeserver. A trailing slash + # Forward all Matrix API calls to the Conduit Matrix homeserver. A trailing slash # *must not* be used here. locations."/_matrix".proxyPass = "http://127.0.0.1:${toString cfg.port}"; - # Forward requests for e.g. SSO and password-resets. - locations."/_synapse/client".proxyPass = "http://127.0.0.1:${toString cfg.port}"; }; - # I don't really care about these nginx access logs. Synapse has its own + # I don't really care about these nginx access logs. Conduit has its own # log anyway and with the default log rotation (weekly and delaycompress=true) # the access logs from last week took up ~800MB. logrotate.settings.matrix-nginx-access-log = diff --git a/nixos/hosts/tente/monitoring.nix b/nixos/hosts/tente/monitoring.nix index 545ae24..f6ed7cf 100644 --- a/nixos/hosts/tente/monitoring.nix +++ b/nixos/hosts/tente/monitoring.nix @@ -2,12 +2,16 @@ let cfg = config.monitoring; + helpers = import <top/helpers.nix> { inherit config lib pkgs; }; in { options.monitoring = { grafanaUiPort = lib.mkOption { type = lib.types.int; }; + grafanaMatrixForwarderPort = lib.mkOption { + type = lib.types.int; + }; lokiPort = lib.mkOption { type = lib.types.int; }; @@ -20,13 +24,19 @@ in prometheusSqlExporterPort = lib.mkOption { type = lib.types.int; }; + matrixServerUrl = lib.mkOption { + type = lib.types.str; + }; }; imports = [ <top/shared/prometheus-sql-exporter/service.nix> + <top/shared/grafana-matrix-forwarder/service.nix> ]; config = { + age.secrets.grafana-matrix-forwarder-env.file = <top/secrets/grafana-matrix-forwarder-env.age>; + services.grafana = { enable = true; settings = { @@ -53,6 +63,12 @@ in ]; }; }; + services.grafana-matrix-forwarder = { + enable = true; + port = cfg.grafanaMatrixForwarderPort; + homeserver = cfg.matrixServerUrl; + environmentFile = config.age.secrets.grafana-matrix-forwarder-env.path; + }; services.prometheus = { enable = true; @@ -166,159 +182,134 @@ in services.alloy = { enable = true; extraFlags = ["--server.http.listen-addr=0.0.0.0:${toString cfg.alloyUiPort}"]; - # TODO: submit PR to nixpkgs so that the alloy config can be specified as a JSON expression - configPath = pkgs.writeText "config.alloy" '' - loki.source.journal "journal" { - max_age = "12h0m0s" - relabel_rules = discovery.relabel.journal.rules - forward_to = [loki.process.journal.receiver] - labels = { - host = "tente", - job = "systemd-journal", - } - } - - loki.process "journal" { - forward_to = [loki.write.default.receiver] - - stage.match { - // Select messages from systemd services that have LogExtraFields=LOG_FORMAT=logfmt. - selector = "{__journal_LOG_FORMAT=\"logfmt\"}" - stage.logfmt { - mapping = { time = "", level = "" } - } - stage.timestamp { - source = "time" - format = "RFC3339" - } - stage.template { - // The slog package of the Go standard library prints levels as uppercase. - source = "level" - template = "{{ ToLower .Value }}" - } - stage.structured_metadata { - values = { level = "" } - } - } - } - - discovery.relabel "journal" { - targets = [] - - rule { - source_labels = ["__journal__systemd_unit"] - target_label = "unit" - } - } - - loki.source.file "nginx_access" { - targets = local.file_match.nginx_access.targets - forward_to = [loki.process.nginx_access.receiver] - } - - local.file_match "nginx_access" { - path_targets = [{ - __path__ = "/var/log/nginx/*.access.log", - }] - } - - loki.process "nginx_access" { - forward_to = [loki.write.default.receiver] - - stage.static_labels { - values = { - job = "nginx", - } - } - - // Extracting the log file name as vhost because it's more convenient - // to query for than the full filename. We could also use server_name - // but there could be wildcard server_names and Loki labels should have - // a low cardinality for performance reasons. - stage.regex { - source = "filename" - expression = "(?P<vhost>[^/]+)\\.access\\.log$" - } - - stage.labels { - values = { - vhost = "", - } - } - - stage.json { - expressions = { "msec" = "", path = "" } - } - - stage.timestamp { - source = "msec" - format = "Unix" - } - - // Setting level=info to prevent Loki's log level detection from wrongly - // detecting messages with paths containing "error" as errors. - // Creating the filetype entry via stage.template because there's no - // static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703) - stage.template { - source = "level" - template = "info" - } - stage.structured_metadata { - values = { level = "" } - } - - stage.labels { - values = { - // Temporarily adding path as a label so that we can use it in the match selectors. - path = "", - } - } - - stage.match { - selector = "{path=~\"/\\\\.well-known/.*\"}" - // Creating the filetype entry via stage.template because there's no - // static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703) - stage.template { - source = "filetype" - template = "well-known" - } - } + configPath = + let + ref = helpers.alloyConfigRef; + in + helpers.writeAlloyConfig { + "loki.source.journal".journal = { + max_age = "12h0m0s"; + relabel_rules = ref "discovery.relabel.journal.rules"; + forward_to = [(ref "loki.process.journal.receiver")]; + labels = { + host = "tente"; + job = "systemd-journal"; + }; + }; + "loki.process".journal = { + forward_to = [(ref "loki.write.default.receiver")]; + blocks = [ + { + name = "stage.match"; + # Select messages from systemd services that have LogExtraFields=LOG_FORMAT=logfmt. + selector = ''{__journal_LOG_FORMAT="logfmt"}''; + blocks = [ + { name = "stage.logfmt"; mapping = { time = ""; level = ""; }; } + { name = "stage.timestamp"; source = "time"; format = "RFC3339"; } + { + # The slog package of the Go standard library prints levels as uppercase. + name = "stage.template"; + source = "level"; + template = "{{ ToLower .Value }}"; + } + { name = "stage.structured_metadata"; values = { level = ""; }; } + ]; + } + ]; + }; + "discovery.relabel".journal = { + targets = []; + blocks = [ + { + name = "rule"; + source_labels = ["__journal__systemd_unit"]; + target_label = "unit"; + } + ]; + }; - stage.match { - selector = "{path=\"/robots.txt\"}" - stage.template { - source = "filetype" - template = "robots.txt" - } - } + "loki.source.file".nginx_access = { + targets = ref "local.file_match.nginx_access.targets"; + forward_to = [(ref "loki.process.nginx_access.receiver")]; + }; + "local.file_match".nginx_access = { + path_targets = [{ + __path__ = "/var/log/nginx/*.access.log"; + }]; + }; + "loki.process".nginx_access = { + forward_to = [(ref "loki.write.default.receiver")]; + blocks = [ + { name = "stage.static_labels"; values = { job = "nginx"; }; } - stage.match { - selector = "{path=~\".*\\\\.atom$\"}" - stage.template { - source = "filetype" - template = "feed" - } - } + { + # Extracting the log file name as vhost because it's more convenient + # to query for than the full filename. We could also use server_name + # but there could be wildcard server_names and Loki labels should have + # a low cardinality for performance reasons. + name = "stage.regex"; + source = "filename"; + expression = "(?P<vhost>[^/]+)\\.access\\.log$"; + } - stage.structured_metadata { - values = { - filetype = "", - } - } + { name = "stage.labels"; values = { vhost = ""; }; } + { name = "stage.json"; expressions = { msec = ""; path = ""; }; } + { name = "stage.timestamp"; source = "msec"; format = "Unix"; } + { + # Setting level=info to prevent Loki's log level detection from wrongly + # detecting messages with paths containing "error" as errors. + # Creating the filetype entry via stage.template because there's no + # static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703) + name = "stage.template"; + source = "level"; + template = "info"; + } + { name = "stage.structured_metadata"; values = { level = ""; }; } - // Dropping path again because it has a too high cardinality for a label. - stage.label_drop { - values = [ "path" ] - } - } + # Temporarily adding path as a label so that we can use it in the match selectors. + { name = "stage.labels"; values = { path = ""; }; } + { + name = "stage.match"; + selector = "{path=~\"/\\\\.well-known/.*\"}"; + # Creating the filetype entry via stage.template because there's no + # static_structured_metadata stage yet. (https://github.com/grafana/loki/issues/16703) + blocks = [ + { name = "stage.template"; source = "filetype"; template = "well-known"; } + ]; + } + { + name = "stage.match"; + selector = "{path=\"/robots.txt\"}"; + blocks = [ + { name = "stage.template"; source = "filetype"; template = "robots.txt"; } + ]; + } + { + name = "stage.match"; + selector = "{path=~\".*\\\\.atom$\"}"; + blocks = [ + { name = "stage.template"; source = "filetype"; template = "feed"; } + ]; + } + { + name = "stage.structured_metadata"; + values = { filetype = ""; }; + } - loki.write "default" { - endpoint { - url = "http://127.0.0.1:${toString cfg.lokiPort}/loki/api/v1/push" - } - external_labels = {} - } - ''; + # Dropping path again because it has a too high cardinality for a label. + { name = "stage.label_drop"; values = ["path"]; } + ]; + }; + "loki.write".default = { + blocks = [ + { + name = "endpoint"; + url = "http://127.0.0.1:${toString cfg.lokiPort}/loki/api/v1/push"; + } + ]; + external_labels = {}; + }; + }; }; }; } diff --git a/nixos/npins/sources.json b/nixos/npins/sources.json index 826b2a9..6f86eaa 100644 --- a/nixos/npins/sources.json +++ b/nixos/npins/sources.json @@ -16,6 +16,21 @@ "url": "https://api.github.com/repos/ryantm/agenix/tarball/0.15.0", "hash": "01dhrghwa7zw93cybvx4gnrskqk97b004nfxgsys0736823956la" }, + "grafana-matrix-forwarder": { + "type": "GitRelease", + "repository": { + "type": "Git", + "url": "https://gitlab.com/hctrdev/grafana-matrix-forwarder.git" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "v0.8.3", + "revision": "a77a360503ea12c8244646753a3bf271428ddd02", + "url": null, + "hash": "1mdgv6jfbjs3xsqk9dxli6w345jws8j5qgzqf6wljmxlvibbic6y" + }, "my-geopos-link": { "type": "Git", "repository": { @@ -103,8 +118,23 @@ "nixpkgs-unstable": { "type": "Channel", "name": "nixpkgs-unstable", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre832020.6b4955211758/nixexprs.tar.xz", - "hash": "128piyf6sw0r07bxhix6i998h30zqqgaz1sknzrgy6yvh8jgawaz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre852399.4c202d26483c/nixexprs.tar.xz", + "hash": "0aaj59kn5q6n5cwq3i2a9sa89z0q71cy69kmpiy0kkn53hv3dxch" + }, + "prometheus-sql-exporter": { + "type": "GitRelease", + "repository": { + "type": "Git", + "url": "https://github.com/burningalchemist/sql_exporter" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "0.18.1", + "revision": "6e73ff71a8976939c28ce9a0ddbeefe0801d0a37", + "url": null, + "hash": "1a3j3rsjvw6wqyayml5x5c7x18bvn45ldr2p280yn9pqdzihk480" } }, "version": 5 diff --git a/nixos/secrets/grafana-matrix-forwarder-env.age b/nixos/secrets/grafana-matrix-forwarder-env.age Binary files differnew file mode 100644 index 0000000..84cab14 --- /dev/null +++ b/nixos/secrets/grafana-matrix-forwarder-env.age diff --git a/nixos/secrets/secrets.nix b/nixos/secrets/secrets.nix index 6021803..282ee6c 100644 --- a/nixos/secrets/secrets.nix +++ b/nixos/secrets/secrets.nix @@ -3,7 +3,9 @@ let hamac = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJmjbC0gk2s/qDQ+QR//GJH0ZPld99L0EtX7dPP5h2RN"; ev = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINCSypbTOnAYBO32vUUieOsb6ws32gCsDg8nB8JhuFuI"; + tente = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGTyZdGXxkAjzRulRFB23AX0T/lVLZOaSaFJi+thsjgx"; in { "vpn-se-privKey.age".publicKeys = [ martin hamac ev ]; + "grafana-matrix-forwarder-env.age".publicKeys = [ martin tente ]; } diff --git a/nixos/shared/alloy-nix-config/alloy_nix_config.go b/nixos/shared/alloy-nix-config/alloy_nix_config.go new file mode 100644 index 0000000..4b6eb63 --- /dev/null +++ b/nixos/shared/alloy-nix-config/alloy_nix_config.go @@ -0,0 +1,129 @@ +package main + +import ( + "encoding/json" + "fmt" + "maps" + "os" + "slices" + "strconv" + "strings" +) + +func main() { + if len(os.Args) != 3 { + fmt.Fprintf(os.Stderr, "usage: %s <json_path> <out_path>\n", os.Args[0]) + os.Exit(1) + } + + jsonPath := os.Args[1] + outPath := os.Args[2] + + jsonData, err := os.ReadFile(jsonPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading file %s: %v\n", jsonPath, err) + os.Exit(1) + } + + // It would be nice to preserve the order of blocks ... except that we can't + // because Nix already doesn't preserve the order of attribute sets. + var config map[string]any + if err := json.Unmarshal(jsonData, &config); err != nil { + fmt.Fprintf(os.Stderr, "error parsing JSON: %v\n", err) + os.Exit(1) + } + + result := formatConfig(config) + + if err := os.WriteFile(outPath, []byte(result), 0644); err != nil { + fmt.Fprintf(os.Stderr, "error writing file %s: %v\n", outPath, err) + os.Exit(1) + } +} + +func formatConfig(config map[string]any) string { + var s strings.Builder + + for _, blockName := range slices.Sorted(maps.Keys(config)) { + labels := config[blockName] + + if labelsMap, ok := labels.(map[string]any); ok { + for label, block := range labelsMap { + if blockMap, ok := block.(map[string]any); ok { + s.WriteString(formatBlock(blockName, label, blockMap, 0)) + } + } + } + } + + return s.String() +} + +func formatBlock(blockName string, label string, block map[string]any, indent int) string { + var s strings.Builder + + s.WriteString(strings.Repeat(" ", indent)) + s.WriteString(blockName) + if label != "" { + s.WriteString(fmt.Sprintf(` %s`, strconv.Quote(label))) + } + s.WriteString(" {\n") + + var blocks []any + if blocksValue, exists := block["blocks"]; exists { + if blocksList, ok := blocksValue.([]any); ok { + blocks = blocksList + } + delete(block, "blocks") + } + + for _, key := range slices.Sorted(maps.Keys(block)) { + s.WriteString(strings.Repeat(" ", indent+1)) + s.WriteString(fmt.Sprintf("%s = %s\n", key, formatValue(block[key]))) + } + + for _, blockItem := range blocks { + if blockMap, ok := blockItem.(map[string]any); ok { + var name string + if nameValue, exists := blockMap["name"]; exists { + if nameStr, ok := nameValue.(string); ok { + name = nameStr + } + delete(blockMap, "name") + } + + s.WriteString(formatBlock(name, "", blockMap, indent+1)) + } + } + + s.WriteString(strings.Repeat(" ", indent)) + s.WriteString("}\n") + + return s.String() +} + +func formatValue(value any) string { + switch v := value.(type) { + case string: + return strconv.Quote(v) + case map[string]any: + if ref, exists := v["$ref"]; exists { + if refStr, ok := ref.(string); ok { + return refStr + } + } + var parts []string + for _, name := range slices.Sorted(maps.Keys(v)) { + parts = append(parts, fmt.Sprintf("%s=%s,", name, formatValue(v[name]))) + } + return "{" + strings.Join(parts, " ") + "}" + case []any: + var parts []string + for _, item := range v { + parts = append(parts, formatValue(item)) + } + return "[" + strings.Join(parts, ", ") + "]" + default: + return fmt.Sprintf("%v", v) + } +} diff --git a/nixos/shared/alloy-nix-config/default.nix b/nixos/shared/alloy-nix-config/default.nix new file mode 100644 index 0000000..d4efe02 --- /dev/null +++ b/nixos/shared/alloy-nix-config/default.nix @@ -0,0 +1,8 @@ +{ pkgs ? import <nixpkgs> {} }: + +pkgs.buildGoModule { + pname = "alloy-nix-config"; + version = "git"; + src = ./.; + vendorHash = null; +} diff --git a/nixos/shared/alloy-nix-config/go.mod b/nixos/shared/alloy-nix-config/go.mod new file mode 100644 index 0000000..2916089 --- /dev/null +++ b/nixos/shared/alloy-nix-config/go.mod @@ -0,0 +1,3 @@ +module push-f.com/alloy-nix-config + +go 1.24.5 diff --git a/nixos/shared/grafana-matrix-forwarder/default.nix b/nixos/shared/grafana-matrix-forwarder/default.nix new file mode 100644 index 0000000..7a04dcb --- /dev/null +++ b/nixos/shared/grafana-matrix-forwarder/default.nix @@ -0,0 +1,10 @@ +{ buildGoModule }: +let + sources = import <top/npins>; +in +buildGoModule { + pname = "grafana-matrix-forwarder"; + version = sources.grafana-matrix-forwarder.version; + src = sources.grafana-matrix-forwarder; + vendorHash = "sha256-ifkeakyRkIF2Y/4otUWhTvUzsPwRb1Wxx6gqN0806c4="; +} diff --git a/nixos/shared/grafana-matrix-forwarder/service.nix b/nixos/shared/grafana-matrix-forwarder/service.nix new file mode 100644 index 0000000..5ad511c --- /dev/null +++ b/nixos/shared/grafana-matrix-forwarder/service.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +let + grafanaMatrixForwarder = pkgs.callPackage ./default.nix {}; + cfg = config.services.grafana-matrix-forwarder; +in +{ + options.services.grafana-matrix-forwarder = { + enable = lib.mkEnableOption "grafana-matrix-forwarder"; + port = lib.mkOption { + type = lib.types.int; + }; + homeserver = lib.mkOption { + type = lib.types.str; + }; + environmentFile = lib.mkOption { + type = lib.types.path; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.grafana-matrix-forwarder = { + serviceConfig = { + ExecStart = "${grafanaMatrixForwarder}/bin/grafana-matrix-forwarder --port=${toString cfg.port} --homeserver ${cfg.homeserver}"; + EnvironmentFile = cfg.environmentFile; + DynamicUser = "true"; + }; + wantedBy = ["multi-user.target"]; + }; + }; +} diff --git a/nixos/shared/prometheus-sql-exporter/default.nix b/nixos/shared/prometheus-sql-exporter/default.nix index 81f1660..5d80a62 100644 --- a/nixos/shared/prometheus-sql-exporter/default.nix +++ b/nixos/shared/prometheus-sql-exporter/default.nix @@ -1,21 +1,15 @@ { lib, buildGoModule, - fetchFromGitHub, }: - -buildGoModule rec { +let + sources = import <top/npins>; +in +buildGoModule { pname = "sql_exporter"; - version = "0.17.1"; - - src = fetchFromGitHub { - owner = "burningalchemist"; - repo = pname; - rev = version; - sha256 = "sha256-AEPFXPplHtny1P3gMvB1gbMj10bpu9PXc6ywliF+dCc="; - }; - - vendorHash = "sha256-KFWDqbdbXvgEtz1nlasWrvIckpzasUdzbb+AKfXmYf8="; + version = sources.prometheus-sql-exporter.version; + src = sources.prometheus-sql-exporter; + vendorHash = "sha256-eZxxmqoiXPdjZs/lwbzvWco9mDFy0zmpGDcqTIyWbK4="; meta = with lib; { description = "Database-agnostic SQL exporter for Prometheus"; diff --git a/nixos/shared/prometheus-sql-exporter/service.nix b/nixos/shared/prometheus-sql-exporter/service.nix index a887f91..a79528c 100644 --- a/nixos/shared/prometheus-sql-exporter/service.nix +++ b/nixos/shared/prometheus-sql-exporter/service.nix @@ -4,7 +4,16 @@ let sqlExporter = pkgs.callPackage ./default.nix {}; cfg = config.services.prometheus-sql-exporter; - configFile = builtins.toFile "config.yaml" (builtins.toJSON cfg.config); + configFile = builtins.toFile "config.yaml" (builtins.toJSON cfg.config); + validateConfig = file: + pkgs.runCommand "validate-config" + { + nativeBuildInputs = [sqlExporter]; + } + '' + sql_exporter -config.check -config.file "${file}" + ln -s "${file}" "$out" + ''; in { options.services.prometheus-sql-exporter = { @@ -20,7 +29,7 @@ in config = lib.mkIf cfg.enable { systemd.services.prometheus-sql-exporter = { serviceConfig = { - ExecStart = "${sqlExporter}/bin/sql_exporter -config.file ${configFile} -web.listen-address :${toString cfg.port}"; + ExecStart = "${sqlExporter}/bin/sql_exporter -config.file ${validateConfig configFile} -web.listen-address :${toString cfg.port}"; DynamicUser = "true"; User = "prometheus-sql-exporter"; }; |