nixos/paperless: add module options for automated exports

Paperless includes a document exporter that can be used for e.g.
backups.

This change extends the module to provide a way to enable and configure
a timer, export settings, pre- and post-processing
scripts (e.g. to ship the backup somewhere else, clean up, ...).

It works out of the box when just enabling it but can be customized.

Includes suitable tests.
This commit is contained in:
Christian Theune 2023-07-07 17:14:54 +02:00
parent 14f4013165
commit 865ab91155
3 changed files with 103 additions and 4 deletions

View File

@ -308,6 +308,9 @@
- `bind.cacheNetworks` now only controls access for recursive queries, where it previously controlled access for all queries. - `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. - 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: Example:

View File

@ -1,4 +1,4 @@
{ config, pkgs, lib, ... }: { config, options, pkgs, lib, ... }:
let let
cfg = config.services.paperless; cfg = config.services.paperless;
@ -82,7 +82,7 @@ let
}; };
in in
{ {
meta.maintainers = with lib.maintainers; [ leona SuperSandro2000 erikarvstedt ]; meta.maintainers = with lib.maintainers; [ leona SuperSandro2000 erikarvstedt atemu theuni ];
imports = [ imports = [
(lib.mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ]) (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.redis.servers.paperless.enable = lib.mkIf enableRedis true;
services.postgresql = lib.mkIf cfg.database.createLocally { services.postgresql = lib.mkIf cfg.database.createLocally {
@ -439,5 +472,40 @@ in
gid = config.ids.gids.paperless; 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}
'';
};
})
]);
} }

View File

@ -8,6 +8,15 @@ import ./make-test-python.nix ({ lib, ... }: {
services.paperless = { services.paperless = {
enable = true; enable = true;
passwordFile = builtins.toFile "password" "admin"; 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, ... }: { 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/")) metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/"))
assert "original_checksum" in 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) test_paperless(simple)
simple.send_monitor_command("quit") simple.send_monitor_command("quit")
simple.wait_for_shutdown() simple.wait_for_shutdown()