From 0b0ddfc2f971374469904dac51a4d8a3fdb77037 Mon Sep 17 00:00:00 2001 From: pazpi Date: Mon, 5 May 2025 22:42:35 +0200 Subject: [PATCH] New service: Paperless-ngx --- hosts/caddy/default.nix | 8 ++ hosts/default.nix | 11 +++ hosts/deployments.nix | 10 +++ hosts/paperless/default.nix | 96 +++++++++++++++++++++ hosts/parameters.nix | 1 + modules/services/default.nix | 1 + modules/services/paperless-ngx.nix | 134 +++++++++++++++++++++++++++++ secrets.nix | 2 + ssh-keys.nix | 1 + 9 files changed, 264 insertions(+) create mode 100644 hosts/paperless/default.nix create mode 100644 modules/services/paperless-ngx.nix diff --git a/hosts/caddy/default.nix b/hosts/caddy/default.nix index 2bb3f99..96bc119 100644 --- a/hosts/caddy/default.nix +++ b/hosts/caddy/default.nix @@ -77,6 +77,14 @@ in }; }; + paperless = { + proxy = { + enable = true; + domain = p.domains.public; + host = p.hosts.paperless; + }; + }; + searx = { enable = true; secretFile = config.age.secrets.searx-secret.path; diff --git a/hosts/default.nix b/hosts/default.nix index 0b1c84a..98bdfe6 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -222,4 +222,15 @@ in # specialArgs = { }; }; + + paperless = nixpkgs-unstable.lib.nixosSystem { + pkgs = pkgs-unstable "x86_64-linux"; + modules = [ + myModules + proxmoxModule + ./paperless + agenix.nixosModules.default + ]; + # specialArgs = { }; + }; } diff --git a/hosts/deployments.nix b/hosts/deployments.nix index 47db656..4ce3abf 100644 --- a/hosts/deployments.nix +++ b/hosts/deployments.nix @@ -34,6 +34,7 @@ in "portainer" "vaultwarden" "immich" + "paperless" ]; }; @@ -135,6 +136,15 @@ in ]; }; + paperless.deployment = { + targetHost = hosts.paperless; + tags = [ + "lxc" + "bacco" + "paperless" + ]; + }; + deadbeef.deployment = { allowLocalDeployment = true; targetHost = null; diff --git a/hosts/paperless/default.nix b/hosts/paperless/default.nix new file mode 100644 index 0000000..a49d2b7 --- /dev/null +++ b/hosts/paperless/default.nix @@ -0,0 +1,96 @@ +{ + config, + pkgs, + lib, + ... +}: +let + p = import ../parameters.nix; +in +{ + + age.secrets = { + paperless-admin = { + file = ../../secrets/paperless-admin.age; + mode = "770"; + owner = config.services.paperless.user; + group = config.services.paperless.user; + }; + paperless-oauth2-client-secret = { + file = ../../secrets/paperless-oauth2-client-secret.age; + mode = "770"; + owner = config.services.paperless.user; + group = config.services.paperless.user; + }; + }; + + my = { + utils = { + commons.enable = true; + lxc-standard.enable = true; + }; + + services.paperless = { + enable = true; + passwordFile = config.age.secrets.paperless-admin.path; + mediaDir = "/mnt/nas01/documents/paperless"; + settings = { + PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect"; + # PAPERLESS_SOCIALACCOUNT_PROVIDERS_FILE = config.age.secrets.paperless-oicd.path; + + PAPERLESS_SOCIALACCOUNT_PROVIDERS = builtins.toJSON ({ + openid_connect = { + OAUTH_PKCE_ENABLED = true; + APPS = [ + { + provider_id = "authentik"; + name = "authentik"; + client_id = "MxETd4SMhYqRX6rhFvr8I4k6hCc6HDKvojVD2PQh"; + settings = { + server_url = "https://auth.pasetto.me/application/o/paperless/.well-known/openid-configuration"; + oauth_pkce_enabled = true; + }; + } + ]; + }; + }); + + PAPERLESS_SOCIAL_AUTO_SIGNUP = true; + PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS = true; + PAPERLESS_ACCOUNT_ALLOW_SIGNUPS = false; + PAPERLESS_ACCOUNT_EMAIL_VERIFICATION = "none"; + PAPERLESS_DISABLE_REGULAR_LOGIN = false; + PAPERLESS_REDIRECT_LOGIN_TO_SSO = false; + PAPERLESS_ACCOUNT_SESSION_REMEMBER = true; + PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS = true; + + PAPERLESS_FILENAME_FORMAT = "{{ created_year }}/{{ correspondent }}/{{ title }}"; + }; + proxy = { + domain = p.domains.public; + }; + }; + + networking.nas-samba-share = { + enable = true; + allowUsers = [ config.services.paperless.user ]; + }; + + virtualisation.proxmox.enable = true; + }; + + # Add secret to PAPERLESS_SOCIALACCOUNT_PROVIDERS + systemd.services.paperless-web.script = lib.mkBefore '' + oidcSecret=$(< ${config.age.secrets.paperless-oauth2-client-secret.path}) + export PAPERLESS_SOCIALACCOUNT_PROVIDERS=$( + ${pkgs.jq}/bin/jq <<< "$PAPERLESS_SOCIALACCOUNT_PROVIDERS" \ + --compact-output \ + --arg oidcSecret "$oidcSecret" '.openid_connect.APPS.[0].secret = $oidcSecret' + ) + ''; + + # Extra packages + environment.systemPackages = with pkgs; [ ]; + + system.stateVersion = "24.11"; +} diff --git a/hosts/parameters.nix b/hosts/parameters.nix index 162b85e..c65f527 100644 --- a/hosts/parameters.nix +++ b/hosts/parameters.nix @@ -17,6 +17,7 @@ shadowsocks = "shadowsocks.internal"; librechat = "librechat.internal"; immich = "immich.internal"; + paperless = "paperless.internal"; }; domains = { public = "pasetto.me"; diff --git a/modules/services/default.nix b/modules/services/default.nix index d655e72..64567d5 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -5,6 +5,7 @@ ./immich.nix ./media-mgr.nix ./nextcloud.nix + ./paperless-ngx.nix ./plex.nix ./postgres.nix ./searx.nix diff --git a/modules/services/paperless-ngx.nix b/modules/services/paperless-ngx.nix new file mode 100644 index 0000000..73a1d21 --- /dev/null +++ b/modules/services/paperless-ngx.nix @@ -0,0 +1,134 @@ +{ + lib, + config, + pkgs, + ... +}: +let + cfg = config.my.services.paperless; + defaultSettings = { + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_TIKA_ENABLED = true; + PAPERLESS_TIKA_ENDPOINT = "http://localhost:${toString config.services.tika.port}"; + PAPERLESS_TIKA_GOTENBERG_ENDPOINT = "http://localhost:${toString config.services.gotenberg.port}"; + PAPERLESS_OCR_LANGUAGE = "eng+ita"; + PAPERLESS_OCR_USER_ARGS = { + optimize = 1; + pdfa_image_compression = "lossless"; + # Allow OCRmyPDF to modify signed PDFs, since original is also stored + # https://github.com/paperless-ngx/paperless-ngx/issues/7383 + invalidate_digital_signatures = true; + }; + }; +in +{ + + options.my.services.paperless = { + enable = lib.mkEnableOption "Enable Paperless NGX module"; + + passwordFile = lib.mkOption { + type = lib.types.path; + default = "/var/lib/paperless/password"; + description = "File with the Paperless NGX access password"; + }; + + mediaDir = lib.mkOption { + type = lib.types.path; + default = "/var/lib/paperless/media"; + description = "Directory with the Paperless NGX media files"; + }; + + settings = lib.mkOption { + default = { }; + description = '' + Paperless settings as described here: https://search.nixos.org/options?type=packages&query=services.paperless.settings + ''; + inherit (pkgs.formats.json { }) type; + }; + + proxy = { + enable = lib.mkEnableOption "Set the proxy entry for this service"; + + domain = lib.mkOption { + default = "example.com"; + type = lib.types.str; + description = '' + The domain where Caddy is reachable + ''; + }; + + subdomain = lib.mkOption { + default = "docs"; + type = lib.types.str; + description = '' + The subdomain where Paperless NGX is reachable + ''; + }; + + host = lib.mkOption { + default = "localhost"; + type = lib.types.str; + description = '' + host name where the service is running + ''; + }; + + }; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.enable { + + my.services.postgresql = { + enable = true; + ensures = [ + { + username = "paperless"; + database = "paperless"; + } + ]; + }; + + services.tika = { + enable = true; + enableOcr = true; + }; + + services.gotenberg = { + enable = true; + chromium.disableJavascript = true; + extraArgs = [ + "--chromium-allow-list=file:///tmp/.*" + ]; + }; + + services.paperless = { + enable = true; + address = "0.0.0.0"; + settings = + defaultSettings + // cfg.settings + // { + PAPERLESS_URL = "https://${cfg.proxy.subdomain}.${cfg.proxy.domain}"; + }; + passwordFile = cfg.passwordFile; + mediaDir = cfg.mediaDir; + openMPThreadingWorkaround = true; + }; + + networking.firewall.allowedTCPPorts = [ + config.services.paperless.port + ]; + + }) + + (lib.mkIf cfg.proxy.enable { + services.caddy = with cfg.proxy; { + virtualHosts."${subdomain}.${domain}".extraConfig = '' + reverse_proxy http://${host}:${toString config.services.paperless.port} + import cloudflare_${domain} + ''; + }; + }) + ]; +} diff --git a/secrets.nix b/secrets.nix index 32e3302..2c8e870 100644 --- a/secrets.nix +++ b/secrets.nix @@ -27,6 +27,8 @@ let dns02-admin-password = [ machines.dns02 ]; dns02-dhcp-failover = [ machines.dns02 ]; shadowsocks-password = [ machines.shadowsocks ]; + paperless-admin = [ machines.paperless ]; + paperless-oauth2-client-secret = [ machines.paperless ]; }; in builtins.listToAttrs ( diff --git a/ssh-keys.nix b/ssh-keys.nix index d6c3010..02f47a4 100644 --- a/ssh-keys.nix +++ b/ssh-keys.nix @@ -20,6 +20,7 @@ rec { dns01 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII7BdiP/dCE6FHoJylcBKQ5AXz06UpLHNyeuvfLVccSi"; dns02 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ+HIq6/ebjiv71xDozdOTn5AdnXgr1fGqIzXnH7Not+"; shadowsocks = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINQ4qYaS5ccciH7BNyrF5+J3d4JtHJNr1R256/ulEtxl"; + paperless = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILRNgDyk3TuMooG4ZCv7SOgXh0ql1/1hhhng7uSnsLeK"; }; # Machines able to provision other machines