diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index eb15f1c4b8e9b..6766371ffa7f6 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -342,6 +342,9 @@ - `bind.cacheNetworks` now only controls access for recursive queries, where it previously controlled access for all queries. +- The paperless module now has an option for regular automatic export of + documents data using the integrated document exporter. + - Caddy can now be built with plugins by using `caddy.withPlugins`, a `passthru` function that accepts an attribute set as a parameter. The `plugins` argument represents a list of Caddy plugins, with each Caddy plugin being a versioned module. The `hash` argument represents the `vendorHash` of the resulting Caddy source code with the plugins added. Example: diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix index 7f34f8d704ea4..9ffb233bd71c6 100644 --- a/nixos/modules/services/misc/paperless.nix +++ b/nixos/modules/services/misc/paperless.nix @@ -1,4 +1,4 @@ -{ config, pkgs, lib, ... }: +{ config, options, pkgs, lib, ... }: let cfg = config.services.paperless; @@ -82,7 +82,7 @@ let }; in { - meta.maintainers = with lib.maintainers; [ leona SuperSandro2000 erikarvstedt ]; + meta.maintainers = with lib.maintainers; [ leona SuperSandro2000 erikarvstedt atemu theuni ]; imports = [ (lib.mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ]) @@ -252,9 +252,42 @@ in ''; }; }; + + exporter = { + enable = lib.mkEnableOption "regular automatic document exports"; + + directory = lib.mkOption { + type = lib.types.str; + default = cfg.dataDir + "/export"; + defaultText = "\${dataDir}/export"; + description = "Directory to store export."; + }; + + onCalendar = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "01:30:00"; + description = '' + When to run the exporter. See {manpage}`systemd.time(7)`. + + `null` disables the timer; allowing you to run the + `paperless-exporter` service through other means. + ''; + }; + + settings = lib.mkOption { + type = with lib.types; attrsOf anything; + default = { + "no-progress-bar" = true; + "no-color" = true; + "compare-checksums" = true; + "delete" = true; + }; + description = "Settings to pass to the document exporter as CLI arguments."; + }; + }; }; - config = lib.mkIf cfg.enable { + config = lib.mkIf cfg.enable (lib.mkMerge [ { services.redis.servers.paperless.enable = lib.mkIf enableRedis true; services.postgresql = lib.mkIf cfg.database.createLocally { @@ -439,5 +472,40 @@ in gid = config.ids.gids.paperless; }; }; - }; + } + + (lib.mkIf cfg.exporter.enable { + systemd.tmpfiles.rules = [ + "d '${cfg.exporter.directory}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -" + ]; + + services.paperless.exporter.settings = options.services.paperless.exporter.settings.default; + + systemd.services.paperless-exporter = { + startAt = lib.defaultTo [] cfg.exporter.onCalendar; + serviceConfig = { + User = cfg.user; + WorkingDirectory = cfg.dataDir; + }; + unitConfig = let + services = [ + "paperless-consumer.service" + "paperless-scheduler.service" + "paperless-task-queue.service" + "paperless-web.service" ]; + in { + # Shut down the paperless services while the exporter runs + Conflicts = services; + After = services; + # Bring them back up afterwards, regardless of pass/fail + OnFailure = services; + OnSuccess = services; + }; + enableStrictShellChecks = true; + script = '' + ./paperless-manage document_exporter ${cfg.exporter.directory} ${lib.cli.toGNUCommandLineShell {} cfg.exporter.settings} + ''; + }; + }) + ]); } diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix index 2a2a74eeb2414..fcb93938f0f79 100644 --- a/nixos/tests/paperless.nix +++ b/nixos/tests/paperless.nix @@ -8,6 +8,15 @@ import ./make-test-python.nix ({ lib, ... }: { services.paperless = { enable = true; passwordFile = builtins.toFile "password" "admin"; + + exporter = { + enable = true; + + settings = { + "no-color" = lib.mkForce false; # override a default option + "no-thumbnail" = true; # add a new option + }; + }; }; }; postgres = { config, pkgs, ... }: { @@ -73,6 +82,25 @@ import ./make-test-python.nix ({ lib, ... }: { metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/")) assert "original_checksum" in metadata + with subtest("Exporter"): + node.succeed("systemctl start --wait paperless-exporter") + node.wait_for_unit("paperless-web.service") + node.wait_for_unit("paperless-consumer.service") + node.wait_for_unit("paperless-scheduler.service") + node.wait_for_unit("paperless-task-queue.service") + + node.succeed("ls -lah /var/lib/paperless/export/manifest.json") + + timers = node.succeed("systemctl list-timers paperless-exporter") + print(timers) + assert "paperless-exporter.timer paperless-exporter.service" in timers, "missing timer" + assert "1 timers listed." in timers, "incorrect number of timers" + + # Double check that our attrset option override works as expected + cmdline = node.succeed("grep 'paperless-manage' $(systemctl cat paperless-exporter | grep ExecStart | cut -f 2 -d=)") + print(f"Exporter command line {cmdline!r}") + assert cmdline.strip() == "./paperless-manage document_exporter /var/lib/paperless/export --compare-checksums --delete --no-progress-bar --no-thumbnail", "Unexpected exporter command line" + test_paperless(simple) simple.send_monitor_command("quit") simple.wait_for_shutdown()