about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorh7x4 <h7x4@nani.wtf>2024-02-01 20:05:11 +0100
committerGitHub <noreply@github.com>2024-02-01 20:05:11 +0100
commit932344b54a276a1a74cad3bf1a3f4715ad85ef64 (patch)
tree7757081c5cd4245e9ac9266060600fe83ca725a3 /nixos
parentd070e3c00b929974dae80bc65936d690f5f14fe5 (diff)
parentda91e4d75b1c35e3a712887b20d4c9984649ff88 (diff)
downloadnixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar.gz
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar.bz2
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar.lz
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar.xz
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.tar.zst
nixlib-932344b54a276a1a74cad3bf1a3f4715ad85ef64.zip
Merge pull request #270475 from MinerSebas/prometheus-restic-exporter
prometheus-restic-exporter: init at 1.4.0
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/restic.nix131
-rw-r--r--nixos/tests/prometheus-exporters.nix40
3 files changed, 171 insertions, 1 deletions
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 0583b12fe7a3..6be6ba7edf72 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -70,6 +70,7 @@ let
     "pve"
     "py-air-control"
     "redis"
+    "restic"
     "rspamd"
     "rtl_433"
     "sabnzbd"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/restic.nix b/nixos/modules/services/monitoring/prometheus/exporters/restic.nix
new file mode 100644
index 000000000000..5b32c93a666d
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/restic.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.restic;
+in
+{
+  port = 9753;
+  extraOpts = {
+    repository = mkOption {
+      type = types.str;
+      description = lib.mdDoc ''
+        URI pointing to the repository to monitor.
+      '';
+      example = "sftp:backup@192.168.1.100:/backups/example";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        File containing the password to the repository.
+      '';
+      example = "/etc/nixos/restic-password";
+    };
+
+    environmentFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = lib.mdDoc ''
+        File containing the credentials to access the repository, in the
+        format of an EnvironmentFile as described by systemd.exec(5)
+      '';
+    };
+
+    refreshInterval = mkOption {
+      type = types.ints.unsigned;
+      default = 60;
+      description = lib.mdDoc ''
+        Refresh interval for the metrics in seconds.
+        Computing the metrics is an expensive task, keep this value as high as possible.
+      '';
+    };
+
+    rcloneOptions = mkOption {
+      type = with types; attrsOf (oneOf [ str bool ]);
+      default = { };
+      description = lib.mdDoc ''
+        Options to pass to rclone to control its behavior.
+        See <https://rclone.org/docs/#options> for
+        available options. When specifying option names, strip the
+        leading `--`. To set a flag such as
+        `--drive-use-trash`, which does not take a value,
+        set the value to the Boolean `true`.
+      '';
+    };
+
+    rcloneConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str bool ]);
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for the rclone remote being used for backup.
+        See the remote's specific options under rclone's docs at
+        <https://rclone.org/docs/>. When specifying
+        option names, use the "config" name specified in the docs.
+        For example, to set `--b2-hard-delete` for a B2
+        remote, use `hard_delete = true` in the
+        attribute set.
+
+        ::: {.warning}
+        Secrets set in here will be world-readable in the Nix
+        store! Consider using the {option}`rcloneConfigFile`
+        option instead to specify secret values separately. Note that
+        options set here will override those set in the config file.
+        :::
+      '';
+    };
+
+    rcloneConfigFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to the file containing rclone configuration. This file
+        must contain configuration for the remote specified in this backup
+        set and also must be readable by root.
+
+        ::: {.caution}
+        Options set in `rcloneConfig` will override those set in this
+        file.
+        :::
+      '';
+    };
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-restic-exporter}/bin/restic-exporter.py \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+      EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
+    };
+    environment =
+      let
+        rcloneRemoteName = builtins.elemAt (splitString ":" cfg.repository) 1;
+        rcloneAttrToOpt = v: "RCLONE_" + toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
+        rcloneAttrToConf = v: "RCLONE_CONFIG_" + toUpper (rcloneRemoteName + "_" + v);
+        toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
+      in
+      {
+        RESTIC_REPO_URL = cfg.repository;
+        RESTIC_REPO_PASSWORD_FILE = cfg.passwordFile;
+        LISTEN_ADDRESS = cfg.listenAddress;
+        LISTEN_PORT = toString cfg.port;
+        REFRESH_INTERVAL = toString cfg.refreshInterval;
+      }
+      // (mapAttrs'
+        (name: value:
+          nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
+        )
+        cfg.rcloneOptions)
+      // optionalAttrs (cfg.rcloneConfigFile != null) {
+        RCLONE_CONFIG = cfg.rcloneConfigFile;
+      }
+      // (mapAttrs'
+        (name: value:
+          nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
+        )
+        cfg.rcloneConfig);
+  };
+}
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 0f8e355b2f4c..7e74f27174ec 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -1177,6 +1177,39 @@ let
       '';
     };
 
+    restic =
+      let
+        repository = "rest:http://127.0.0.1:8000";
+        passwordFile = pkgs.writeText "restic-test-password" "test-password";
+      in
+      {
+        exporterConfig = {
+          enable = true;
+          inherit repository passwordFile;
+        };
+        metricProvider = {
+          services.restic.server = {
+            enable = true;
+            extraFlags = [ "--no-auth" ];
+          };
+          environment.systemPackages = [ pkgs.restic ];
+        };
+        exporterTest = ''
+          # prometheus-restic-exporter.service fails without initialised repository
+          systemctl("stop prometheus-restic-exporter.service")
+
+          # Initialise the repository
+          wait_for_unit("restic-rest-server.service")
+          wait_for_open_port(8000)
+          succeed("restic init --repo ${repository} --password-file ${passwordFile}")
+
+          systemctl("start prometheus-restic-exporter.service")
+          wait_for_unit("prometheus-restic-exporter.service")
+          wait_for_open_port(9753)
+          wait_until_succeeds("curl -sSf localhost:9753/metrics | grep 'restic_check_success 1.0'")
+        '';
+      };
+
     rspamd = {
       exporterConfig = {
         enable = true;
@@ -1684,7 +1717,12 @@ mapAttrs
       testScript = ''
         ${nodeName}.start()
         ${concatStringsSep "\n" (map (line:
-          if (builtins.substring 0 1 line == " " || builtins.substring 0 1 line == ")")
+          if builtins.any (b: b) [
+            (builtins.match "^[[:space:]]*$" line != null)
+            (builtins.substring 0 1 line == "#")
+            (builtins.substring 0 1 line == " ")
+            (builtins.substring 0 1 line == ")")
+          ]
           then line
           else "${nodeName}.${line}"
         ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}