git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #123046 from Flakebi/paperless

paperless-ng: init at 1.4.5

commitae61a14242cdc2ee37910ee20b750d1adef1bc66 authorAndreas Rammhold <andreas@rammhold.de> dateWed 18 Aug 2021, 14:39 (5 years ago) parentbefd4f66e14210
19 files changed+733−534
nixos/doc/manual/from_md/release-notes/rl-2111.section.xml+84 nixos/doc/manual/release-notes/rl-2111.section.md+47 nixos/modules/module-list.nix+1−1 nixos/modules/services/misc/paperless-ng.nix+304 nixos/modules/services/misc/paperless.nix−183 nixos/tests/all-tests.nix+1−1 nixos/tests/paperless-ng.nix+36 nixos/tests/paperless.nix−36 pkgs/applications/office/paperless-ng/default.nix+197 pkgs/applications/office/paperless/default.nix−168 pkgs/applications/office/paperless/python-modules/default.nix−11 pkgs/applications/office/paperless/python-modules/django-crispy-forms.nix−39 pkgs/applications/office/paperless/python-modules/django-filter.nix−26 pkgs/applications/office/paperless/withConfig.nix−68 pkgs/development/python-modules/inotifyrecursive/default.nix+28 pkgs/development/python-modules/tika/default.nix+29 pkgs/top-level/aliases.nix+1 pkgs/top-level/all-packages.nix+1−1 pkgs/top-level/python-packages.nix+4
nixos/doc/manual/from_md/release-notes/rl-2111.section.xml+84
@@ -186,6 +186,90 @@
186186 <section xml:id="sec-release-21.11-incompatibilities">
187187 <title>Backward Incompatibilities</title>
188188 <itemizedlist>
189+ <listitem>
190+ <para>
191+ The <literal>paperless</literal> module and package have been
192+ removed. All users should migrate to the successor
193+ <literal>paperless-ng</literal> instead. The Paperless project
194+ <link xlink:href="https://github.com/the-paperless-project/paperless/commit/9b0063c9731f7c5f65b1852cb8caff97f5e40ba4">has
195+ been archived</link> and advises all users to use
196+ <literal>paperless-ng</literal> instead.
197+ </para>
198+ <para>
199+ Users can use the <literal>services.paperless-ng</literal>
200+ module as a replacement while noting the following
201+ incompatibilities:
202+ </para>
203+ <itemizedlist spacing="compact">
204+ <listitem>
205+ <para>
206+ <literal>services.paperless.ocrLanguages</literal> has no
207+ replacement. Users should migrate to
208+ <link xlink:href="options.html#opt-services.paperless-ng.extraConfig"><literal>services.paperless-ng.extraConfig</literal></link>
209+ instead:
210+ </para>
211+ </listitem>
212+ </itemizedlist>
213+ <programlisting language="bash">
214+{
215+ services.paperless-ng.extraConfig = {
216+ # Provide languages as ISO 639-2 codes
217+ # separated by a plus (+) sign.
218+ # https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
219+ PAPERLESS_OCR_LANGUAGE = &quot;deu+eng+jpn&quot;; # German &amp; English &amp; Japanse
220+ };
221+}
222+</programlisting>
223+ <itemizedlist>
224+ <listitem>
225+ <para>
226+ If you previously specified
227+ <literal>PAPERLESS_CONSUME_MAIL_*</literal> settings in
228+ <literal>services.paperless.extraConfig</literal> you
229+ should remove those options now. You now
230+ <emphasis>must</emphasis> define those settings in the
231+ admin interface of paperless-ng.
232+ </para>
233+ </listitem>
234+ <listitem>
235+ <para>
236+ Option <literal>services.paperless.manage</literal> no
237+ longer exists. Use the script at
238+ <literal>${services.paperless-ng.dataDir}/paperless-ng-manage</literal>
239+ instead. Note that this script only exists after the
240+ <literal>paperless-ng</literal> service has been started
241+ at least once.
242+ </para>
243+ </listitem>
244+ <listitem>
245+ <para>
246+ After switching to the new system configuration you should
247+ run the Django management command to reindex your
248+ documents and optionally create a user, if you don’t have
249+ one already.
250+ </para>
251+ <para>
252+ To do so, enter the data directory (the value of
253+ <literal>services.paperless-ng.dataDir</literal>,
254+ <literal>/var/lib/paperless</literal> by default), switch
255+ to the paperless user and execute the management command
256+ like below:
257+ </para>
258+ <programlisting>
259+$ cd /var/lib/paperless
260+$ su paperless -s /bin/sh
261+$ ./paperless-ng-manage document_index reindex
262+# if not already done create a user account, paperless-ng requires a login
263+$ ./paperless-ng-manage createsuperuser
264+Username (leave blank to use 'paperless'): my-user-name
265+Email address: me@example.com
266+Password: **********
267+Password (again): **********
268+Superuser created successfully.
269+</programlisting>
270+ </listitem>
271+ </itemizedlist>
272+ </listitem>
189273 <listitem>
190274 <para>
191275 The <literal>staticjinja</literal> package has been upgraded
nixos/doc/manual/release-notes/rl-2111.section.md+47
@@ -58,6 +58,53 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable
5858
5959## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
6060
61+- The `paperless` module and package have been removed. All users should migrate to the
62+ successor `paperless-ng` instead. The Paperless project [has been
63+ archived](https://github.com/the-paperless-project/paperless/commit/9b0063c9731f7c5f65b1852cb8caff97f5e40ba4)
64+ and advises all users to use `paperless-ng` instead.
65+
66+ Users can use the `services.paperless-ng` module as a replacement while noting the following incompatibilities:
67+ - `services.paperless.ocrLanguages` has no replacement. Users should migrate to [`services.paperless-ng.extraConfig`](options.html#opt-services.paperless-ng.extraConfig) instead:
68+ ```nix
69+ {
70+ services.paperless-ng.extraConfig = {
71+ # Provide languages as ISO 639-2 codes
72+ # separated by a plus (+) sign.
73+ # https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
74+ PAPERLESS_OCR_LANGUAGE = "deu+eng+jpn"; # German & English & Japanse
75+ };
76+ }
77+ ```
78+
79+ - If you previously specified `PAPERLESS_CONSUME_MAIL_*` settings in
80+ `services.paperless.extraConfig` you should remove those options now. You
81+ now *must* define those settings in the admin interface of paperless-ng.
82+
83+ - Option `services.paperless.manage` no longer exists.
84+ Use the script at `${services.paperless-ng.dataDir}/paperless-ng-manage` instead.
85+ Note that this script only exists after the `paperless-ng` service has been
86+ started at least once.
87+
88+ - After switching to the new system configuration you should run the Django
89+ management command to reindex your documents and optionally create a user,
90+ if you don't have one already.
91+
92+ To do so, enter the data directory (the value of
93+ `services.paperless-ng.dataDir`, `/var/lib/paperless` by default), switch
94+ to the paperless user and execute the management command like below:
95+ ```
96+ $ cd /var/lib/paperless
97+ $ su paperless -s /bin/sh
98+ $ ./paperless-ng-manage document_index reindex
99+ # if not already done create a user account, paperless-ng requires a login
100+ $ ./paperless-ng-manage createsuperuser
101+ Username (leave blank to use 'paperless'): my-user-name
102+ Email address: me@example.com
103+ Password: **********
104+ Password (again): **********
105+ Superuser created successfully.
106+ ```
107+
61108- The `staticjinja` package has been upgraded from 1.0.4 to 3.0.1
62109
63110- The `erigon` ethereum node has moved to a new database format in `2021-05-04`, and requires a full resync
nixos/modules/module-list.nix+1−1
@@ -551,7 +551,7 @@
551551 ./services/misc/ombi.nix
552552 ./services/misc/osrm.nix
553553 ./services/misc/packagekit.nix
554− ./services/misc/paperless.nix
554+ ./services/misc/paperless-ng.nix
555555 ./services/misc/parsoid.nix
556556 ./services/misc/plex.nix
557557 ./services/misc/plikd.nix
nixos/modules/services/misc/paperless-ng.nixnew file+304
@@ -0,0 +1,304 @@
1+{ config, pkgs, lib, ... }:
2+
3+with lib;
4+let
5+ cfg = config.services.paperless-ng;
6+
7+ defaultUser = "paperless";
8+
9+ env = {
10+ PAPERLESS_DATA_DIR = cfg.dataDir;
11+ PAPERLESS_MEDIA_ROOT = cfg.mediaDir;
12+ PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
13+ GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
14+ } // lib.mapAttrs (_: toString) cfg.extraConfig;
15+
16+ manage = let
17+ setupEnv = lib.concatStringsSep "\n" (mapAttrsToList (name: val: "export ${name}=\"${val}\"") env);
18+ in pkgs.writeShellScript "manage" ''
19+ ${setupEnv}
20+ exec ${cfg.package}/bin/paperless-ng "$@"
21+ '';
22+
23+ # Secure the services
24+ defaultServiceConfig = {
25+ TemporaryFileSystem = "/:ro";
26+ BindReadOnlyPaths = [
27+ "/nix/store"
28+ "-/etc/resolv.conf"
29+ "-/etc/nsswitch.conf"
30+ "-/etc/hosts"
31+ "-/etc/localtime"
32+ ];
33+ BindPaths = [
34+ cfg.consumptionDir
35+ cfg.dataDir
36+ cfg.mediaDir
37+ ];
38+ CapabilityBoundingSet = "";
39+ # ProtectClock adds DeviceAllow=char-rtc r
40+ DeviceAllow = "";
41+ LockPersonality = true;
42+ MemoryDenyWriteExecute = true;
43+ NoNewPrivileges = true;
44+ PrivateDevices = true;
45+ PrivateMounts = true;
46+ # Needs to connect to redis
47+ # PrivateNetwork = true;
48+ PrivateTmp = true;
49+ PrivateUsers = true;
50+ ProcSubset = "pid";
51+ ProtectClock = true;
52+ # Breaks if the home dir of the user is in /home
53+ # Also does not add much value in combination with the TemporaryFileSystem.
54+ # ProtectHome = true;
55+ ProtectHostname = true;
56+ # Would re-mount paths ignored by temporary root
57+ #ProtectSystem = "strict";
58+ ProtectControlGroups = true;
59+ ProtectKernelLogs = true;
60+ ProtectKernelModules = true;
61+ ProtectKernelTunables = true;
62+ ProtectProc = "invisible";
63+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
64+ RestrictNamespaces = true;
65+ RestrictRealtime = true;
66+ RestrictSUIDSGID = true;
67+ SystemCallArchitectures = "native";
68+ SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
69+ # Does not work well with the temporary root
70+ #UMask = "0066";
71+ };
72+in
73+{
74+ meta.maintainers = with maintainers; [ earvstedt Flakebi ];
75+
76+ imports = [
77+ (mkRemovedOptionModule [ "services" "paperless"] ''
78+ The paperless module has been removed as the upstream project died.
79+ Users should migrate to the paperless-ng module (services.paperless-ng).
80+ More information can be found in the NixOS 21.11 release notes.
81+ '')
82+ ];
83+
84+ options.services.paperless-ng = {
85+ enable = mkOption {
86+ type = lib.types.bool;
87+ default = false;
88+ description = ''
89+ Enable Paperless-ng.
90+
91+ When started, the Paperless database is automatically created if it doesn't
92+ exist and updated if the Paperless package has changed.
93+ Both tasks are achieved by running a Django migration.
94+
95+ A script to manage the Paperless instance (by wrapping Django's manage.py) is linked to
96+ <literal>''${dataDir}/paperless-ng-manage</literal>.
97+ '';
98+ };
99+
100+ dataDir = mkOption {
101+ type = types.str;
102+ default = "/var/lib/paperless";
103+ description = "Directory to store the Paperless data.";
104+ };
105+
106+ mediaDir = mkOption {
107+ type = types.str;
108+ default = "${cfg.dataDir}/media";
109+ defaultText = "\${dataDir}/consume";
110+ description = "Directory to store the Paperless documents.";
111+ };
112+
113+ consumptionDir = mkOption {
114+ type = types.str;
115+ default = "${cfg.dataDir}/consume";
116+ defaultText = "\${dataDir}/consume";
117+ description = "Directory from which new documents are imported.";
118+ };
119+
120+ consumptionDirIsPublic = mkOption {
121+ type = types.bool;
122+ default = false;
123+ description = "Whether all users can write to the consumption dir.";
124+ };
125+
126+ passwordFile = mkOption {
127+ type = types.nullOr types.path;
128+ default = null;
129+ example = "/run/keys/paperless-ng-password";
130+ description = ''
131+ A file containing the superuser password.
132+
133+ A superuser is required to access the web interface.
134+ If unset, you can create a superuser manually by running
135+ <literal>''${dataDir}/paperless-ng-manage createsuperuser</literal>.
136+
137+ The default superuser name is <literal>admin</literal>. To change it, set
138+ option <option>extraConfig.PAPERLESS_ADMIN_USER</option>.
139+ WARNING: When changing the superuser name after the initial setup, the old superuser
140+ will continue to exist.
141+
142+ To disable login for the web interface, set the following:
143+ <literal>extraConfig.PAPERLESS_AUTO_LOGIN_USERNAME = "admin";</literal>.
144+ WARNING: Only use this on a trusted system without internet access to Paperless.
145+ '';
146+ };
147+
148+ address = mkOption {
149+ type = types.str;
150+ default = "localhost";
151+ description = "Web interface address.";
152+ };
153+
154+ port = mkOption {
155+ type = types.port;
156+ default = 28981;
157+ description = "Web interface port.";
158+ };
159+
160+ extraConfig = mkOption {
161+ type = types.attrs;
162+ default = {};
163+ description = ''
164+ Extra paperless-ng config options.
165+
166+ See <link xlink:href="https://paperless-ng.readthedocs.io/en/latest/configuration.html">the documentation</link>
167+ for available options.
168+ '';
169+ example = literalExample ''
170+ {
171+ PAPERLESS_OCR_LANGUAGE = "deu+eng";
172+ }
173+ '';
174+ };
175+
176+ user = mkOption {
177+ type = types.str;
178+ default = defaultUser;
179+ description = "User under which Paperless runs.";
180+ };
181+
182+ package = mkOption {
183+ type = types.package;
184+ default = pkgs.paperless-ng;
185+ defaultText = "pkgs.paperless-ng";
186+ description = "The Paperless package to use.";
187+ };
188+ };
189+
190+ config = mkIf cfg.enable {
191+ # Enable redis if no special url is set
192+ services.redis.enable = mkIf (!hasAttr "PAPERLESS_REDIS" env) true;
193+
194+ systemd.tmpfiles.rules = [
195+ "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
196+ "d '${cfg.mediaDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
197+ (if cfg.consumptionDirIsPublic then
198+ "d '${cfg.consumptionDir}' 777 - - - -"
199+ else
200+ "d '${cfg.consumptionDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
201+ )
202+ ];
203+
204+ systemd.services.paperless-ng-server = {
205+ description = "Paperless document server";
206+ serviceConfig = defaultServiceConfig // {
207+ User = cfg.user;
208+ ExecStart = "${cfg.package}/bin/paperless-ng qcluster";
209+ Restart = "on-failure";
210+ };
211+ environment = env;
212+ wantedBy = [ "multi-user.target" ];
213+ wants = [ "paperless-ng-consumer.service" "paperless-ng-web.service" ];
214+
215+ preStart = ''
216+ ln -sf ${manage} ${cfg.dataDir}/paperless-ng-manage
217+
218+ # Auto-migrate on first run or if the package has changed
219+ versionFile="${cfg.dataDir}/src-version"
220+ if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
221+ ${cfg.package}/bin/paperless-ng migrate
222+ echo ${cfg.package} > "$versionFile"
223+ fi
224+ ''
225+ + optionalString (cfg.passwordFile != null) ''
226+ export PAPERLESS_ADMIN_USER="''${PAPERLESS_ADMIN_USER:-admin}"
227+ export PAPERLESS_ADMIN_PASSWORD=$(cat "${cfg.dataDir}/superuser-password")
228+ superuserState="$PAPERLESS_ADMIN_USER:$PAPERLESS_ADMIN_PASSWORD"
229+ superuserStateFile="${cfg.dataDir}/superuser-state"
230+
231+ if [[ $(cat "$superuserStateFile" 2>/dev/null) != $superuserState ]]; then
232+ ${cfg.package}/bin/paperless-ng manage_superuser
233+ echo "$superuserState" > "$superuserStateFile"
234+ fi
235+ '';
236+ };
237+
238+ # Password copying can't be implemented as a privileged preStart script
239+ # in 'paperless-ng-server' because 'defaultServiceConfig' limits the filesystem
240+ # paths accessible by the service.
241+ systemd.services.paperless-ng-copy-password = mkIf (cfg.passwordFile != null) {
242+ requiredBy = [ "paperless-ng-server.service" ];
243+ before = [ "paperless-ng-server.service" ];
244+ serviceConfig = {
245+ ExecStart = ''
246+ ${pkgs.coreutils}/bin/install --mode 600 --owner '${cfg.user}' --compare \
247+ '${cfg.passwordFile}' '${cfg.dataDir}/superuser-password'
248+ '';
249+ Type = "oneshot";
250+ };
251+ };
252+
253+ systemd.services.paperless-ng-consumer = {
254+ description = "Paperless document consumer";
255+ serviceConfig = defaultServiceConfig // {
256+ User = cfg.user;
257+ ExecStart = "${cfg.package}/bin/paperless-ng document_consumer";
258+ Restart = "on-failure";
259+ };
260+ environment = env;
261+ # Bind to `paperless-ng-server` so that the consumer never runs
262+ # during migrations
263+ bindsTo = [ "paperless-ng-server.service" ];
264+ after = [ "paperless-ng-server.service" ];
265+ };
266+
267+ systemd.services.paperless-ng-web = {
268+ description = "Paperless web server";
269+ serviceConfig = defaultServiceConfig // {
270+ User = cfg.user;
271+ ExecStart = ''
272+ ${pkgs.python3Packages.gunicorn}/bin/gunicorn \
273+ -c ${cfg.package}/lib/paperless-ng/gunicorn.conf.py paperless.asgi:application
274+ '';
275+ Restart = "on-failure";
276+
277+ AmbientCapabilities = "CAP_NET_BIND_SERVICE";
278+ CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
279+ # gunicorn needs setuid
280+ SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "@setuid" ];
281+ };
282+ environment = env // {
283+ PATH = mkForce cfg.package.path;
284+ PYTHONPATH = "${cfg.package.pythonPath}:${cfg.package}/lib/paperless-ng/src";
285+ };
286+ # Bind to `paperless-ng-server` so that the web server never runs
287+ # during migrations
288+ bindsTo = [ "paperless-ng-server.service" ];
289+ after = [ "paperless-ng-server.service" ];
290+ };
291+
292+ users = optionalAttrs (cfg.user == defaultUser) {
293+ users.${defaultUser} = {
294+ group = defaultUser;
295+ uid = config.ids.uids.paperless;
296+ home = cfg.dataDir;
297+ };
298+
299+ groups.${defaultUser} = {
300+ gid = config.ids.gids.paperless;
301+ };
302+ };
303+ };
304+}
nixos/modules/services/misc/paperless.nixdeleted−183
@@ -1,183 +0,0 @@
1−{ config, pkgs, lib, ... }:
2−
3−with lib;
4−let
5− cfg = config.services.paperless;
6−
7− defaultUser = "paperless";
8−
9− manage = cfg.package.withConfig {
10− config = {
11− PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
12− PAPERLESS_INLINE_DOC = "true";
13− PAPERLESS_DISABLE_LOGIN = "true";
14− } // cfg.extraConfig;
15− inherit (cfg) dataDir ocrLanguages;
16− paperlessPkg = cfg.package;
17− };
18−in
19−{
20− options.services.paperless = {
21− enable = mkOption {
22− type = lib.types.bool;
23− default = false;
24− description = ''
25− Enable Paperless.
26−
27− When started, the Paperless database is automatically created if it doesn't
28− exist and updated if the Paperless package has changed.
29− Both tasks are achieved by running a Django migration.
30− '';
31− };
32−
33− dataDir = mkOption {
34− type = types.str;
35− default = "/var/lib/paperless";
36− description = "Directory to store the Paperless data.";
37− };
38−
39− consumptionDir = mkOption {
40− type = types.str;
41− default = "${cfg.dataDir}/consume";
42− defaultText = "\${dataDir}/consume";
43− description = "Directory from which new documents are imported.";
44− };
45−
46− consumptionDirIsPublic = mkOption {
47− type = types.bool;
48− default = false;
49− description = "Whether all users can write to the consumption dir.";
50− };
51−
52− ocrLanguages = mkOption {
53− type = with types; nullOr (listOf str);
54− default = null;
55− description = ''
56− Languages available for OCR via Tesseract, specified as
57− <literal>ISO 639-2/T</literal> language codes.
58− If unset, defaults to all available languages.
59− '';
60− example = [ "eng" "spa" "jpn" ];
61− };
62−
63− address = mkOption {
64− type = types.str;
65− default = "localhost";
66− description = "Server listening address.";
67− };
68−
69− port = mkOption {
70− type = types.port;
71− default = 28981;
72− description = "Server port to listen on.";
73− };
74−
75− extraConfig = mkOption {
76− type = types.attrs;
77− default = {};
78− description = ''
79− Extra paperless config options.
80−
81− The config values are evaluated as double-quoted Bash string literals.
82−
83− See <literal>paperless-src/paperless.conf.example</literal> for available options.
84−
85− To enable user authentication, set <literal>PAPERLESS_DISABLE_LOGIN = "false"</literal>
86− and run the shell command <literal>$dataDir/paperless-manage createsuperuser</literal>.
87−
88− To define secret options without storing them in /nix/store, use the following pattern:
89− <literal>PAPERLESS_PASSPHRASE = "$(&lt; /etc/my_passphrase_file)"</literal>
90− '';
91− example = literalExample ''
92− {
93− PAPERLESS_OCR_LANGUAGE = "deu";
94− }
95− '';
96− };
97−
98− user = mkOption {
99− type = types.str;
100− default = defaultUser;
101− description = "User under which Paperless runs.";
102− };
103−
104− package = mkOption {
105− type = types.package;
106− default = pkgs.paperless;
107− defaultText = "pkgs.paperless";
108− description = "The Paperless package to use.";
109− };
110−
111− manage = mkOption {
112− type = types.package;
113− readOnly = true;
114− default = manage;
115− description = ''
116− A script to manage the Paperless instance.
117− It wraps Django's manage.py and is also available at
118− <literal>$dataDir/manage-paperless</literal>
119− '';
120− };
121− };
122−
123− config = mkIf cfg.enable {
124−
125− systemd.tmpfiles.rules = [
126− "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
127− ] ++ (optional cfg.consumptionDirIsPublic
128− "d '${cfg.consumptionDir}' 777 - - - -"
129− # If the consumption dir is not created here, it's automatically created by
130− # 'manage' with the default permissions.
131− );
132−
133− systemd.services.paperless-consumer = {
134− description = "Paperless document consumer";
135− serviceConfig = {
136− User = cfg.user;
137− ExecStart = "${manage} document_consumer";
138− Restart = "always";
139− };
140− after = [ "systemd-tmpfiles-setup.service" ];
141− wantedBy = [ "multi-user.target" ];
142− preStart = ''
143− if [[ $(readlink ${cfg.dataDir}/paperless-manage) != ${manage} ]]; then
144− ln -sf ${manage} ${cfg.dataDir}/paperless-manage
145− fi
146−
147− ${manage.setupEnv}
148− # Auto-migrate on first run or if the package has changed
149− versionFile="$PAPERLESS_DBDIR/src-version"
150− if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
151− python $paperlessSrc/manage.py migrate
152− echo ${cfg.package} > "$versionFile"
153− fi
154− '';
155− };
156−
157− systemd.services.paperless-server = {
158− description = "Paperless document server";
159− serviceConfig = {
160− User = cfg.user;
161− ExecStart = "${manage} runserver --noreload ${cfg.address}:${toString cfg.port}";
162− Restart = "always";
163− };
164− # Bind to `paperless-consumer` so that the server never runs
165− # during migrations
166− bindsTo = [ "paperless-consumer.service" ];
167− after = [ "paperless-consumer.service" ];
168− wantedBy = [ "multi-user.target" ];
169− };
170−
171− users = optionalAttrs (cfg.user == defaultUser) {
172− users.${defaultUser} = {
173− group = defaultUser;
174− uid = config.ids.uids.paperless;
175− home = cfg.dataDir;
176− };
177−
178− groups.${defaultUser} = {
179− gid = config.ids.gids.paperless;
180− };
181− };
182− };
183−}
nixos/tests/all-tests.nix+1−1
@@ -336,7 +336,7 @@ in
336336 pam-oath-login = handleTest ./pam-oath-login.nix {};
337337 pam-u2f = handleTest ./pam-u2f.nix {};
338338 pantheon = handleTest ./pantheon.nix {};
339− paperless = handleTest ./paperless.nix {};
339+ paperless-ng = handleTest ./paperless-ng.nix {};
340340 pdns-recursor = handleTest ./pdns-recursor.nix {};
341341 peerflix = handleTest ./peerflix.nix {};
342342 pgjwt = handleTest ./pgjwt.nix {};
nixos/tests/paperless-ng.nixnew file+36
@@ -0,0 +1,36 @@
1+import ./make-test-python.nix ({ lib, ... }: {
2+ name = "paperless-ng";
3+ meta.maintainers = with lib.maintainers; [ earvstedt Flakebi ];
4+
5+ nodes.machine = { pkgs, ... }: {
6+ environment.systemPackages = with pkgs; [ imagemagick jq ];
7+ services.paperless-ng = {
8+ enable = true;
9+ passwordFile = builtins.toFile "password" "admin";
10+ };
11+ virtualisation.memorySize = 1024;
12+ };
13+
14+ testScript = ''
15+ machine.wait_for_unit("paperless-ng-consumer.service")
16+
17+ with subtest("Create test doc"):
18+ machine.succeed(
19+ "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
20+ "-annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
21+ )
22+
23+ with subtest("Web interface gets ready"):
24+ machine.wait_for_unit("paperless-ng-web.service")
25+ # Wait until server accepts connections
26+ machine.wait_until_succeeds("curl -fs localhost:28981")
27+
28+ with subtest("Document is consumed"):
29+ machine.wait_until_succeeds(
30+ "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 1))"
31+ )
32+ assert "2005-10-16" in machine.succeed(
33+ "curl -u admin:admin -fs localhost:28981/api/documents/ | jq '.results | .[0] | .created'"
34+ )
35+ '';
36+})
nixos/tests/paperless.nixdeleted−36
@@ -1,36 +0,0 @@
1−import ./make-test-python.nix ({ lib, ... } : {
2− name = "paperless";
3− meta = with lib.maintainers; {
4− maintainers = [ earvstedt ];
5− };
6−
7− machine = { pkgs, ... }: {
8− environment.systemPackages = with pkgs; [ imagemagick jq ];
9− services.paperless = {
10− enable = true;
11− ocrLanguages = [ "eng" ];
12− };
13− };
14−
15− testScript = ''
16− machine.wait_for_unit("paperless-consumer.service")
17−
18− # Create test doc
19− machine.succeed(
20− "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black -annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
21− )
22−
23− with subtest("Service gets ready"):
24− machine.wait_for_unit("paperless-server.service")
25− # Wait until server accepts connections
26− machine.wait_until_succeeds("curl -fs localhost:28981")
27−
28− with subtest("Test document is consumed"):
29− machine.wait_until_succeeds(
30− "(($(curl -fs localhost:28981/api/documents/ | jq .count) == 1))"
31− )
32− assert "2005-10-16" in machine.succeed(
33− "curl -fs localhost:28981/api/documents/ | jq '.results | .[0] | .created'"
34− )
35− '';
36−})
pkgs/applications/office/paperless-ng/default.nixnew file+197
@@ -0,0 +1,197 @@
1+{ lib
2+, fetchurl
3+, nixosTests
4+, python3
5+, ghostscript
6+, imagemagick
7+, jbig2enc
8+, ocrmypdf
9+, optipng
10+, pngquant
11+, qpdf
12+, tesseract4
13+, unpaper
14+, liberation_ttf
15+}:
16+
17+let
18+ py = python3.override {
19+ packageOverrides = self: super: {
20+ django = super.django_3;
21+ django-picklefield = super.django-picklefield.overrideAttrs (oldAttrs: {
22+ # Checks do not pass with django 3
23+ doInstallCheck = false;
24+ });
25+ # Avoid warning in django-q versions > 1.3.4
26+ # https://github.com/jonaswinkler/paperless-ng/issues/857
27+ # https://github.com/Koed00/django-q/issues/526
28+ django-q = super.django-q.overridePythonAttrs (oldAttrs: rec {
29+ version = "1.3.4";
30+ src = super.fetchPypi {
31+ inherit (oldAttrs) pname;
32+ inherit version;
33+ sha256 = "Uj1U3PG2YVLBtlj5FPAO07UYo0MqnezUiYc4yo274Q8=";
34+ };
35+ });
36+ };
37+ };
38+
39+ path = lib.makeBinPath [ ghostscript imagemagick jbig2enc optipng pngquant qpdf tesseract4 unpaper ];
40+in
41+py.pkgs.pythonPackages.buildPythonApplication rec {
42+ pname = "paperless-ng";
43+ version = "1.4.5";
44+
45+ src = fetchurl {
46+ url = "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-${version}/${pname}-${version}.tar.xz";
47+ sha256 = "2PJb8j3oimlfiJ3gqjK6uTemzFdtAP2Mlm5RH09bx/E=";
48+ };
49+
50+ format = "other";
51+
52+ # Make bind address configurable
53+ # Fix tests with Pillow 8.3.1: https://github.com/jonaswinkler/paperless-ng/pull/1183
54+ prePatch = ''
55+ substituteInPlace gunicorn.conf.py --replace "bind = '0.0.0.0:8000'" ""
56+ substituteInPlace src/paperless_tesseract/parsers.py --replace "return x" "return round(x)"
57+ '';
58+
59+ propagatedBuildInputs = with py.pkgs.pythonPackages; [
60+ aioredis
61+ arrow
62+ asgiref
63+ async-timeout
64+ attrs
65+ autobahn
66+ automat
67+ blessed
68+ certifi
69+ cffi
70+ channels-redis
71+ channels
72+ chardet
73+ click
74+ coloredlogs
75+ concurrent-log-handler
76+ constantly
77+ cryptography
78+ daphne
79+ dateparser
80+ django-cors-headers
81+ django_extensions
82+ django-filter
83+ django-picklefield
84+ django-q
85+ django
86+ djangorestframework
87+ filelock
88+ fuzzywuzzy
89+ gunicorn
90+ h11
91+ hiredis
92+ httptools
93+ humanfriendly
94+ hyperlink
95+ idna
96+ imap-tools
97+ img2pdf
98+ incremental
99+ inotify-simple
100+ inotifyrecursive
101+ joblib
102+ langdetect
103+ lxml
104+ msgpack
105+ numpy
106+ ocrmypdf
107+ pathvalidate
108+ pdfminer
109+ pikepdf
110+ pillow
111+ pluggy
112+ portalocker
113+ psycopg2
114+ pyasn1-modules
115+ pyasn1
116+ pycparser
117+ pyopenssl
118+ python-dateutil
119+ python-dotenv
120+ python-gnupg
121+ python-Levenshtein
122+ python_magic
123+ pytz
124+ pyyaml
125+ redis
126+ regex
127+ reportlab
128+ requests
129+ scikit-learn
130+ scipy
131+ service-identity
132+ six
133+ sortedcontainers
134+ sqlparse
135+ threadpoolctl
136+ tika
137+ tqdm
138+ twisted.extras.tls
139+ txaio
140+ tzlocal
141+ urllib3
142+ uvicorn
143+ uvloop
144+ watchdog
145+ watchgod
146+ wcwidth
147+ websockets
148+ whitenoise
149+ whoosh
150+ zope_interface
151+ ];
152+
153+ doCheck = true;
154+ checkInputs = with py.pkgs.pythonPackages; [
155+ pytest
156+ pytest-cov
157+ pytest-django
158+ pytest-env
159+ pytest-sugar
160+ pytest-xdist
161+ factory_boy
162+ ];
163+
164+ # The tests require:
165+ # - PATH with runtime binaries
166+ # - A temporary HOME directory for gnupg
167+ # - XDG_DATA_DIRS with test-specific fonts
168+ checkPhase = ''
169+ pushd src
170+ PATH="${path}:$PATH" HOME=$(mktemp -d) XDG_DATA_DIRS="${liberation_ttf}/share:$XDG_DATA_DIRS" pytest
171+ popd
172+ '';
173+
174+ installPhase = ''
175+ mkdir -p $out/lib
176+ cp -r . $out/lib/paperless-ng
177+ chmod +x $out/lib/paperless-ng/src/manage.py
178+ makeWrapper $out/lib/paperless-ng/src/manage.py $out/bin/paperless-ng \
179+ --prefix PYTHONPATH : "$PYTHONPATH" \
180+ --prefix PATH : "${path}"
181+ '';
182+
183+ passthru = {
184+ # PYTHONPATH of all dependencies used by the package
185+ pythonPath = python3.pkgs.makePythonPath propagatedBuildInputs;
186+ inherit path;
187+
188+ tests = { inherit (nixosTests) paperless-ng; };
189+ };
190+
191+ meta = with lib; {
192+ description = "A supercharged version of paperless: scan, index, and archive all of your physical documents";
193+ homepage = "https://paperless-ng.readthedocs.io/en/latest/";
194+ license = licenses.gpl3Only;
195+ maintainers = with maintainers; [ earvstedt Flakebi ];
196+ };
197+}
pkgs/applications/office/paperless/default.nixdeleted−168
@@ -1,168 +0,0 @@
1−{ stdenv
2−, lib
3−, fetchFromGitHub
4−, makeWrapper
5−, callPackage
6−
7−, python3
8−, imagemagick
9−, ghostscript
10−, optipng
11−, tesseract
12−, unpaper
13−}:
14−
15−## Usage
16−
17−# ${paperless}/bin/paperless wraps manage.py
18−
19−# ${paperless}/share/paperless/setup-env.sh can be sourced from a
20−# shell script to setup a Paperless environment
21−
22−# paperless.withConfig is a convenience function to setup a
23−# configured Paperless instance. (See ./withConfig.nix)
24−
25−# For WSGI with gunicorn, use a shell script like this:
26−# let
27−# pythonEnv = paperless.python.withPackages (ps: paperless.runtimePackages ++ [ ps.gunicorn ]);
28−# in
29−# writers.writeBash "run-gunicorn" ''
30−# source ${paperless}/share/paperless/setup-env.sh
31−# PYTHONPATH=$paperlessSrc ${pythonEnv}/bin/gunicorn paperless.wsgi
32−# ''
33−
34−let
35− paperless = stdenv.mkDerivation rec {
36− pname = "paperless";
37− version = "2.7.0";
38−
39− src = fetchFromGitHub {
40− owner = "the-paperless-project";
41− repo = "paperless";
42− rev = version;
43− sha256 = "0pkmyky1crjnsg7r0gfk0fadisfsgzlsq6afpz16wx4hp6yvkkf7";
44− };
45−
46− nativeBuildInputs = [ makeWrapper ];
47−
48− doCheck = true;
49− dontInstall = true;
50−
51− pythonEnv = python.withPackages (_: runtimePackages);
52− pythonCheckEnv = python.withPackages (_: (runtimePackages ++ checkPackages));
53−
54− unpackPhase = ''
55− srcDir=$out/share/paperless
56− mkdir -p $srcDir
57− cp -r --no-preserve=mode $src/src/* $src/LICENSE $srcDir
58− '';
59−
60− postPatch = ''
61− # django-cors-headers 3.x requires a scheme for allowed hosts
62− substituteInPlace $out/share/paperless/paperless/settings.py \
63− --replace "localhost:8080" "http://localhost:8080"
64− '';
65−
66− buildPhase = let
67− # Paperless has explicit runtime checks that expect these binaries to be in PATH
68− extraBin = lib.makeBinPath [ imagemagick ghostscript optipng tesseract unpaper ];
69− in ''
70− ${python.interpreter} -m compileall $srcDir
71−
72− makeWrapper $pythonEnv/bin/python $out/bin/paperless \
73− --set PATH ${extraBin} --add-flags $out/share/paperless/manage.py
74−
75− # A shell snippet that can be sourced to setup a paperless env
76− cat > $out/share/paperless/setup-env.sh <<EOF
77− export PATH="$pythonEnv/bin:${extraBin}''${PATH:+:}$PATH"
78− export paperlessSrc=$out/share/paperless
79− EOF
80− '';
81−
82− checkPhase = ''
83− source $out/share/paperless/setup-env.sh
84− tmpDir=$(realpath testsTmp)
85− mkdir $tmpDir
86− export HOME=$tmpDir
87− export PAPERLESS_MEDIADIR=$tmpDir
88− cd $paperlessSrc
89− # Prevent tests from writing to the derivation output
90− chmod -R -w $out
91− # Disable cache to silence a pytest warning ("could not create cache")
92− $pythonCheckEnv/bin/pytest -p no:cacheprovider
93− '';
94−
95− passthru = {
96− withConfig = callPackage ./withConfig.nix {};
97− inherit python runtimePackages checkPackages tesseract;
98− };
99−
100− meta = with lib; {
101− description = "Scan, index, and archive all of your paper documents";
102− homepage = "https://github.com/the-paperless-project/paperless";
103− license = licenses.gpl3;
104− maintainers = [ maintainers.earvstedt ];
105− };
106− };
107−
108− python = python3.override {
109− packageOverrides = self: super: let
110− customPkgs = import ./python-modules super fetchFromGitHub; in
111− {
112− pyocr = pyocrWithUserTesseract super;
113−
114− # Paperless is incompatible with factory_boy >= 3
115− factory_boy = customPkgs.factory_boy_2_12_0;
116−
117− # These are pre-release versions, hence they are private to this pkg
118− django-filter = self.callPackage ./python-modules/django-filter.nix {};
119− django-crispy-forms = self.callPackage ./python-modules/django-crispy-forms.nix {};
120− };
121− };
122−
123− runtimePackages = with python.pkgs; [
124− dateparser
125− python-dateutil
126− django
127− django-cors-headers
128− django-crispy-forms
129− django-filter
130− django_extensions
131− djangoql
132− djangorestframework
133− factory_boy
134− filemagic
135− fuzzywuzzy
136− langdetect
137− pdftotext
138− pillow
139− psycopg2
140− pyocr
141− python-dotenv
142− python-gnupg
143− pytz
144− termcolor
145− ] ++ (lib.optional stdenv.isLinux inotify-simple);
146−
147− checkPackages = with python.pkgs; [
148− pytest
149− pytest-django
150− pytest-env
151− pytest-xdist
152− ];
153−
154− pyocrWithUserTesseract = pyPkgs:
155− let
156− pyocr = pyPkgs.pyocr.override { inherit tesseract; };
157− in
158− if pyocr.outPath == pyPkgs.pyocr.outPath then
159− pyocr
160− else
161− # The user has provided a custom tesseract derivation that might be
162− # missing some languages that are required for PyOCR's tests. Disable them to
163− # avoid build errors.
164− pyocr.overridePythonAttrs (attrs: {
165− doCheck = false;
166− });
167−in
168− paperless
pkgs/applications/office/paperless/python-modules/default.nixdeleted−11
@@ -1,11 +0,0 @@
1−pyPkgs: fetchFromGitHub:
2−{
3− factory_boy_2_12_0 = pyPkgs.factory_boy.overridePythonAttrs (old: rec {
4− version = "2.12.0";
5− src = pyPkgs.fetchPypi {
6− inherit (old) pname;
7− inherit version;
8− sha256 = "0w53hjgag6ad5i2vmrys8ysk54agsqvgbjy9lg8g0d8pi9h8vx7s";
9− };
10− });
11−}
pkgs/applications/office/paperless/python-modules/django-crispy-forms.nixdeleted−39
@@ -1,39 +0,0 @@
1−{ lib
2−, buildPythonPackage
3−, fetchFromGitHub
4−, pytestCheckHook
5−, pytest-django
6−, django
7−}:
8−
9−buildPythonPackage rec {
10− pname = "django-crispy-forms";
11− version = "1.10.0";
12−
13− src = fetchFromGitHub {
14− owner = "django-crispy-forms";
15− repo = "django-crispy-forms";
16− rev = version;
17− sha256 = "0y6kskfxgckb9npcgwx4zrs5n9px159zh9zhinhxi3i7wlriqpf5";
18− };
19−
20− # For reasons unknown, the source dir must contain a dash
21− # for the tests to run successfully
22− postUnpack = ''
23− mv $sourceRoot source-
24− export sourceRoot=source-
25− '';
26−
27− checkInputs = [ django pytest-django pytestCheckHook ];
28−
29− preCheck = ''
30− export DJANGO_SETTINGS_MODULE=crispy_forms.tests.test_settings
31− '';
32−
33− meta = with lib; {
34− description = "The best way to have DRY Django forms";
35− homepage = "https://github.com/maraujop/django-crispy-forms";
36− license = licenses.mit;
37− maintainers = with maintainers; [ earvstedt ];
38− };
39−}
pkgs/applications/office/paperless/python-modules/django-filter.nixdeleted−26
@@ -1,26 +0,0 @@
1−{ lib, buildPythonPackage, python, pythonOlder, fetchFromGitHub
2−, django, django-crispy-forms, djangorestframework, mock, pytz }:
3−
4−buildPythonPackage rec {
5− pname = "django-filter";
6− version = "2.1.0-pre";
7− disabled = pythonOlder "3.4";
8−
9− src = fetchFromGitHub {
10− owner = "carltongibson";
11− repo = pname;
12− rev = "24adad8c48bc9e7c7539b6510ffde4ce4effdc29";
13− sha256 = "0hv4w95jnlzp9vdximl6bb27fyi75001jhvsbs0ikkd8amq8iaj7";
14− };
15−
16− checkInputs = [ django django-crispy-forms djangorestframework mock pytz ];
17−
18− checkPhase = "${python.interpreter} runtests.py";
19−
20− meta = with lib; {
21− description = "A reusable Django application for allowing users to filter querysets dynamically.";
22− homepage = "https://github.com/carltongibson/django-filter";
23− license = licenses.bsd3;
24− maintainers = with maintainers; [ earvstedt ];
25− };
26−}
pkgs/applications/office/paperless/withConfig.nixdeleted−68
@@ -1,68 +0,0 @@
1−{ paperless, lib, writers }:
2−
3−## Usage
4−#
5−# nix-build --out-link ./paperless -E '
6−# (import <nixpkgs> {}).paperless.withConfig {
7−# dataDir = /tmp/paperless-data;
8−# config = {
9−# PAPERLESS_DISABLE_LOGIN = "true";
10−# };
11−# }'
12−#
13−# Setup DB
14−# ./paperless migrate
15−#
16−# Consume documents in ${dataDir}/consume
17−# ./paperless document_consumer --oneshot
18−#
19−# Start web interface
20−# ./paperless runserver --noreload localhost:8000
21−
22−{ config ? {}, dataDir ? null, ocrLanguages ? null
23−, paperlessPkg ? paperless, extraCmds ? "" }:
24−with lib;
25−let
26− paperless = if ocrLanguages == null then
27− paperlessPkg
28− else
29− (paperlessPkg.override {
30− tesseract = paperlessPkg.tesseract.override {
31− enableLanguages = ocrLanguages;
32− };
33− }).overrideDerivation (_: {
34− # `ocrLanguages` might be missing some languages required by the tests.
35− doCheck = false;
36− });
37−
38− envVars = (optionalAttrs (dataDir != null) {
39− PAPERLESS_CONSUMPTION_DIR = "${dataDir}/consume";
40− PAPERLESS_MEDIADIR = "${dataDir}/media";
41− PAPERLESS_STATICDIR = "${dataDir}/static";
42− PAPERLESS_DBDIR = dataDir;
43− }) // config;
44−
45− envVarDefs = mapAttrsToList (n: v: ''export ${n}="${toString v}"'') envVars;
46− setupEnvVars = builtins.concatStringsSep "\n" envVarDefs;
47−
48− setupEnv = ''
49− source ${paperless}/share/paperless/setup-env.sh
50− ${setupEnvVars}
51− ${optionalString (dataDir != null) ''
52− mkdir -p "$PAPERLESS_CONSUMPTION_DIR" \
53− "$PAPERLESS_MEDIADIR" \
54− "$PAPERLESS_STATICDIR" \
55− "$PAPERLESS_DBDIR"
56− ''}
57− '';
58−
59− runPaperless = writers.writeBash "paperless" ''
60− set -e
61− ${setupEnv}
62− ${extraCmds}
63− exec python $paperlessSrc/manage.py "$@"
64− '';
65−in
66− runPaperless // {
67− inherit paperless setupEnv;
68− }
pkgs/development/python-modules/inotifyrecursive/default.nixnew file+28
@@ -0,0 +1,28 @@
1+{ lib
2+, buildPythonPackage
3+, fetchPypi
4+, inotify-simple
5+}:
6+
7+buildPythonPackage rec {
8+ pname = "inotifyrecursive";
9+ version = "0.3.5";
10+
11+ src = fetchPypi {
12+ inherit pname version;
13+ sha256 = "osRQsxdpPkU4QW+Q6x14WFBtr+a4uIUDe9LdmuLa+h4=";
14+ };
15+
16+ propagatedBuildInputs = [ inotify-simple ];
17+
18+ # No tests included
19+ doCheck = false;
20+ pythonImportsCheck = [ pname ];
21+
22+ meta = with lib; {
23+ description = "Simple recursive inotify watches for Python";
24+ homepage = "https://github.com/letorbi/inotifyrecursive";
25+ license = licenses.lgpl3Plus;
26+ maintainers = with maintainers; [ Flakebi ];
27+ };
28+}
pkgs/development/python-modules/tika/default.nixnew file+29
@@ -0,0 +1,29 @@
1+{ lib
2+, buildPythonPackage
3+, fetchPypi
4+, pyyaml
5+, requests
6+}:
7+
8+buildPythonPackage rec {
9+ pname = "tika";
10+ version = "1.24";
11+
12+ src = fetchPypi {
13+ inherit pname version;
14+ sha256 = "wsUPQFYi90UxhBEE+ehcF1Ea7eEd6OU4XqsaKaMfGRs=";
15+ };
16+
17+ propagatedBuildInputs = [ pyyaml requests ];
18+
19+ # Requires network
20+ doCheck = false;
21+ pythonImportsCheck = [ pname ];
22+
23+ meta = with lib; {
24+ description = "A Python binding to the Apache Tika™ REST services";
25+ homepage = "https://github.com/chrismattmann/tika-python";
26+ license = licenses.asl20;
27+ maintainers = with maintainers; [ Flakebi ];
28+ };
29+}
pkgs/top-level/aliases.nix+1
@@ -567,6 +567,7 @@ mapAliases ({
567567 owncloudclient = owncloud-client; # added 2016-08
568568 ocz-ssd-guru = throw "ocz-ssd-guru has been removed due to there being no source available"; # added 2021-07-12
569569 p11_kit = p11-kit; # added 2018-02-25
570+ paperless = paperless-ng; # added 2021-06-06
570571 parity = openethereum; # added 2020-08-01
571572 parquet-cpp = arrow-cpp; # added 2018-09-08
572573 pass-otp = pass.withExtensions (ext: [ext.pass-otp]); # added 2018-05-04
pkgs/top-level/all-packages.nix+1−1
@@ -7893,7 +7893,7 @@ with pkgs;
78937893
78947894 pamtester = callPackage ../tools/security/pamtester { };
78957895
7896− paperless = callPackage ../applications/office/paperless { };
7896+ paperless-ng = callPackage ../applications/office/paperless-ng { };
78977897
78987898 paperwork = callPackage ../applications/office/paperwork/paperwork-gtk.nix { };
78997899
pkgs/top-level/python-packages.nix+4
@@ -3585,6 +3585,8 @@ in {
35853585
35863586 inotify-simple = callPackage ../development/python-modules/inotify-simple { };
35873587
3588+ inotifyrecursive = callPackage ../development/python-modules/inotifyrecursive { };
3589+
35883590 inquirer = callPackage ../development/python-modules/inquirer { };
35893591
35903592 intake = callPackage ../development/python-modules/intake { };
@@ -8746,6 +8748,8 @@ in {
87468748
87478749 tifffile = callPackage ../development/python-modules/tifffile { };
87488750
8751+ tika = callPackage ../development/python-modules/tika { };
8752+
87498753 tiledb = callPackage ../development/python-modules/tiledb {
87508754 inherit (pkgs) tiledb;
87518755 };