diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d92482dab..ef8717a74 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -413,6 +413,9 @@ Makefile @thiagokokada
/modules/services/random-background.nix @rycee
+/modules/services/recoll.nix @foo-dogsquared
+/tests/modules/recoll @foo-dogsquared
+
/modules/services/redshift-gammastep @rycee @petabyteboy @thiagokokada
/tests/modules/redshift-gammastep @thiagokokada
diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index 9f6bd72f1..fadecda83 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -624,6 +624,14 @@ in
A new module is available: 'xsession.windowManager.spectrwm'.
'';
}
+
+ {
+ time = "2022-07-27T12:22:37+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.recoll'.
+ '';
+ }
];
};
}
diff --git a/modules/modules.nix b/modules/modules.nix
index 3be6a1867..d9e90f7f2 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -242,6 +242,7 @@ let
./services/poweralertd.nix
./services/pulseeffects.nix
./services/random-background.nix
+ ./services/recoll.nix
./services/redshift-gammastep/gammastep.nix
./services/redshift-gammastep/redshift.nix
./services/rsibreak.nix
diff --git a/modules/services/recoll.nix b/modules/services/recoll.nix
new file mode 100644
index 000000000..644b01184
--- /dev/null
+++ b/modules/services/recoll.nix
@@ -0,0 +1,192 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+# TODO: Fix the formatting of the resulting config.
+let
+ cfg = config.services.recoll;
+
+ # The key-value generator for Recoll config format. For future references,
+ # see the example configuration from the package (i.e.,
+ # `$out/share/recoll/examples/recoll.conf`).
+ mkRecollConfKeyValue = generators.mkKeyValueDefault {
+ mkValueString = v:
+ if v == true then
+ "1"
+ else if v == false then
+ "0"
+ else if isList v then
+ concatStringsSep " " v
+ else
+ generators.mkValueStringDefault { } v;
+ } " = ";
+
+ # A modified version of 'lib.generators.toINI' that also accepts top-level
+ # attributes as non-attrsets.
+ toRecollConf = { listsAsDuplicateKeys ? false }:
+ attr:
+ let
+ toKeyValue = generators.toKeyValue {
+ inherit listsAsDuplicateKeys;
+ mkKeyValue = mkRecollConfKeyValue;
+ };
+ mkSectionName = name: strings.escape [ "[" "]" ] name;
+ convert = k: v:
+ if isAttrs v then
+ ''
+ [${mkSectionName k}]
+ '' + toKeyValue v
+ else
+ toKeyValue { "${k}" = v; };
+
+ # TODO: Improve this chunk of code, pls.
+ # There's a possibility of attributes with attrsets overriding other
+ # top-level attributes with non-attrsets so we're forcing the attrsets to
+ # come last.
+ _config = mapAttrsToList convert (filterAttrs (k: v: !isAttrs v) attr);
+ _config' = mapAttrsToList convert (filterAttrs (k: v: isAttrs v) attr);
+ config = _config ++ _config';
+ in concatStringsSep "\n" config;
+
+ # A specific type for Recoll config format. Taken from `pkgs.formats`
+ # implementation from nixpkgs. See the 'Nix-representable formats' from the
+ # NixOS manual for more information.
+ recollConfFormat = { }: {
+ type = with types;
+ let
+ valueType = nullOr (oneOf [
+ bool
+ float
+ int
+ path
+ str
+ (attrsOf valueType)
+ (listOf valueType)
+ ]) // {
+ description = "Recoll config value";
+ };
+ in attrsOf valueType;
+
+ generate = name: value: pkgs.writeText name (toRecollConf { } value);
+ };
+
+ # The actual object we're going to use for this module. This is for the sake
+ # of consistency (and dogfooding the settings format implementation).
+ settingsFormat = recollConfFormat { };
+in {
+ meta.maintainers = [ maintainers.foo-dogsquared ];
+
+ options.services.recoll = {
+ enable = mkEnableOption "Recoll file index service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.recoll;
+ defaultText = literalExpression "pkgs.recoll";
+ description = ''
+ Package providing the recoll binary.
+ '';
+ example = literalExpression "(pkgs.recoll.override { withGui = false; })";
+ };
+
+ startAt = mkOption {
+ type = types.str;
+ default = "hourly";
+ example = "00/2:00";
+ description = ''
+ When or how often the periodic update should run. Must be the format
+ described from
+
+ systemd.time
+ 7
+ .
+ '';
+ };
+
+ settings = mkOption {
+ type = settingsFormat.type;
+ default = { };
+ description = ''
+ The configuration to be written at
+ ''${config.services.recoll.configDir}/recoll.conf.
+
+ See
+
+ recoll
+ 5
+ for more details about the configuration.
+ '';
+ example = literalExpression ''
+ {
+ nocjk = true;
+ loglevel = 5;
+ topdirs = [ "~/Downloads" "~/Documents" "~/projects" ];
+
+ "~/Downloads" = {
+ "skippedNames+" = [ "*.iso" ];
+ };
+
+ "~/projects" = {
+ "skippedNames+" = [ "node_modules" "target" "result" ];
+ };
+ }
+ '';
+ };
+
+ configDir = mkOption {
+ type = types.str;
+ default = "${config.home.homeDirectory}/.recoll";
+ defaultText = literalExpression "\${config.home.homeDirectory}/.recoll";
+ example = literalExpression "\${config.xdg.configHome}/recoll";
+ description = ''
+ The directory to contain Recoll configuration files. This will be set
+ as RECOLL_CONFDIR.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ (lib.hm.assertions.assertPlatform "services.recoll" pkgs
+ lib.platforms.linux)
+ ];
+
+ home.packages = [ cfg.package ];
+
+ home.sessionVariables = { RECOLL_CONFDIR = cfg.configDir; };
+
+ home.file."${cfg.configDir}/recoll.conf".source =
+ settingsFormat.generate "recoll-conf-${config.home.username}"
+ cfg.settings;
+
+ systemd.user.services.recollindex = {
+ Unit = {
+ Description = "Recoll index update";
+ Documentation = [
+ "man:recoll"
+ "man:recollindex"
+ "https://www.lesbonscomptes.com/recoll/usermanual/"
+ ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/recollindex";
+ Environment = [ "RECOLL_CONFDIR=${escapeShellArg cfg.configDir}" ];
+ };
+ };
+
+ systemd.user.timers.recollindex = {
+ Unit = {
+ Description = "Recoll index update";
+ PartOf = [ "default.target" ];
+ };
+
+ Timer = {
+ Persistent = true;
+ OnCalendar = cfg.startAt;
+ };
+
+ Install.WantedBy = [ "timers.target" ];
+ };
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 017e31176..4ab7ef9e3 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -171,6 +171,7 @@ import nmt {
./modules/services/picom
./modules/services/playerctld
./modules/services/polybar
+ ./modules/services/recoll
./modules/services/redshift-gammastep
./modules/services/screen-locker
./modules/services/swayidle
diff --git a/tests/modules/services/recoll/basic-configuration.conf b/tests/modules/services/recoll/basic-configuration.conf
new file mode 100644
index 000000000..185768a8e
--- /dev/null
+++ b/tests/modules/services/recoll/basic-configuration.conf
@@ -0,0 +1,16 @@
+nocjk = 0
+
+skippedNames+ = node_modules
+
+topdirs = ~/Downloads ~/Documents ~/library
+
+underscoresasletter = 1
+
+[~/library/projects]
+skippedNames+ = .editorconfig .gitignore result flake.lock go.sum
+
+[~/library/projects/software]
+skippedNames+ = target result
+
+[~/what-is-this-project]
+skippedNames+ = whoa-there
diff --git a/tests/modules/services/recoll/basic-configuration.nix b/tests/modules/services/recoll/basic-configuration.nix
new file mode 100644
index 000000000..9775c170c
--- /dev/null
+++ b/tests/modules/services/recoll/basic-configuration.nix
@@ -0,0 +1,35 @@
+{ config, ... }:
+
+{
+ services.recoll = {
+ enable = true;
+ package = config.lib.test.mkStubPackage { };
+ configDir = "${config.xdg.configHome}/recoll";
+ settings = {
+ topdirs = [ "~/Downloads" "~/Documents" "~/library" ];
+ "skippedNames+" = [ "node_modules" ];
+ underscoresasletter = true;
+ nocjk = false;
+
+ "~/library/projects" = {
+ "skippedNames+" =
+ [ ".editorconfig" ".gitignore" "result" "flake.lock" "go.sum" ];
+ };
+
+ "~/library/projects/software" = {
+ "skippedNames+" = [ "target" "result" ];
+ };
+
+ "~/what-is-this-project" = { "skippedNames+" = [ "whoa-there" ]; };
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/recollindex.service
+ assertFileExists home-files/.config/systemd/user/recollindex.timer
+
+ assertFileExists home-files/.config/recoll/recoll.conf
+ assertFileContent home-files/.config/recoll/recoll.conf \
+ ${./basic-configuration.conf}
+ '';
+}
diff --git a/tests/modules/services/recoll/config-format-order.conf b/tests/modules/services/recoll/config-format-order.conf
new file mode 100644
index 000000000..f1e251ff3
--- /dev/null
+++ b/tests/modules/services/recoll/config-format-order.conf
@@ -0,0 +1,25 @@
+b = 10
+
+d = 0
+
+e = This should be the second to the last non-attrset value in the config.
+
+g = This is coming from a list
+
+[a]
+foo = bar
+
+[c]
+a = This should appear as the second section.
+aa = 1
+b = 53
+
+[f]
+a = This should be second to the last for the attribute names with an attrset.
+b = 3193
+c = 0
+d = Hello there
+
+[foo]
+bar = This should be the last attribute with an attrset.
+baz = 42
diff --git a/tests/modules/services/recoll/config-format-order.nix b/tests/modules/services/recoll/config-format-order.nix
new file mode 100644
index 000000000..846627d96
--- /dev/null
+++ b/tests/modules/services/recoll/config-format-order.nix
@@ -0,0 +1,45 @@
+# This is a test primarily concerned with the order of the configuration. The
+# configuration is dynamically generated in alphabetical order of the top-level
+# attribute names. Because of this, it is possible to override top-level
+# attributes that are supposed to be configured in the top-level configuration.
+{ config, ... }:
+
+{
+ services.recoll = {
+ enable = true;
+ package = config.lib.test.mkStubPackage { };
+ settings = {
+ a = { foo = "bar"; };
+ b = 10;
+ c = {
+ a = "This should appear as the second section.";
+ b = 53;
+ aa = true;
+ };
+ d = false;
+ e =
+ "This should be the second to the last non-attrset value in the config.";
+ f = {
+ a =
+ "This should be second to the last for the attribute names with an attrset.";
+ b = 3193;
+ c = false;
+ d = [ "Hello" "there" ];
+ };
+ foo = {
+ bar = "This should be the last attribute with an attrset.";
+ baz = 42;
+ };
+ g = [ "This" "is" "coming" "from" "a" "list" ];
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/recollindex.service
+ assertFileExists home-files/.config/systemd/user/recollindex.timer
+
+ assertFileExists home-files/.recoll/recoll.conf
+ assertFileContent home-files/.recoll/recoll.conf \
+ ${./config-format-order.conf}
+ '';
+}
diff --git a/tests/modules/services/recoll/default.nix b/tests/modules/services/recoll/default.nix
new file mode 100644
index 000000000..3b0e560f1
--- /dev/null
+++ b/tests/modules/services/recoll/default.nix
@@ -0,0 +1,4 @@
+{
+ recoll-basic-configuration = ./basic-configuration.nix;
+ recoll-config-format-order = ./config-format-order.nix;
+}