Commit d5d4ca2d101a

Vincent Demeester <vincent@sbr.pm>
2026-02-09 16:39:31
paperless: migrated from sakhalin to aion
Added paperless-ngx service configuration on aion with syncthing integration for cross-device document access. Configuration changes: - Added syncthing folders: paperless-media (receiveonly) and paperless-inbox (bidirectional) - Enabled paperless service on aion (port 8000, /neo/paperless paths) - Configured to run as vincent:users with lib.mkForce overrides - Integrated with restic backups (excluding trash and consume) - Updated traefik routing from sakhalin to aion - Added firewall port and tmpfiles rules for directory structure Machines affected: aion (service host), rhea (routing), aomi, kyushu (syncthing)
1 parent e113fd0
Changed files (3)
systems/aion/extra.nix
@@ -106,19 +106,24 @@ in
   };
 
   services = {
-    # Paperless storage plan (not enabled here yet).
-    # Target layout: /neo/paperless/{consume,data,media,trash} (or /neo/documents with migration).
-    # Filename format mirrors sakhalin: year/type + rich metadata.
-    # paperless = {
-    #   settings = {
-    #     PAPERLESS_CONSUMPTION_DIR = "/neo/paperless/consume";
-    #     PAPERLESS_DATA_DIR = "/neo/paperless/data";
-    #     PAPERLESS_MEDIA_ROOT = "/neo/paperless/media";
-    #     PAPERLESS_EMPTY_TRASH_DIR = "/neo/paperless/trash";
-    #     PAPERLESS_FILENAME_FORMAT = "{{ created_year }}/{{ document_type }}/{{ created }} - {{ correspondent }} - {{ title }} - {{ asn }} ({{ doc_pk }})";
-    #     PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = "true";
-    #   };
-    # };
+    # Paperless document management
+    paperless = {
+      enable = true;
+      address = "0.0.0.0";
+      port = 8000;
+
+      dataDir = "/neo/paperless/data";
+      mediaDir = "/neo/paperless/media";
+      consumptionDir = "/neo/paperless/consume";
+
+      settings = {
+        PAPERLESS_URL = "https://paperless.sbr.pm";
+        PAPERLESS_EMPTY_TRASH_DIR = "/neo/paperless/trash";
+        PAPERLESS_FILENAME_FORMAT = "{{ created_year }}/{{ document_type }}/{{ created }} - {{ correspondent }} - {{ title }} - {{ asn }} ({{ doc_pk }})";
+        PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = "true";
+      };
+    };
+
     # Binary cache server (aarch64-linux)
     harmonia-cache = {
       enable = true;
@@ -231,9 +236,11 @@ in
       paths = [
         "/home/vincent/desktop/org" # Org files (<1GB)
         "/neo/documents" # Personal docs rsynced from rhea (~113GB)
+        "/neo/paperless/data" # Paperless database (~164MB)
+        "/neo/paperless/media" # Paperless PDFs (~8MB → 3GB)
       ];
 
-      # Daily backup for frequently changing org files
+      # Daily backup for frequently changing org files and paperless
       timerConfig = {
         OnCalendar = "daily";
         Persistent = true;
@@ -249,6 +256,8 @@ in
       extraBackupArgs = [
         "--exclude-caches"
         "--exclude='.sync-conflict-*'"
+        "--exclude='/neo/paperless/trash'" # Exclude trash
+        "--exclude='/neo/paperless/consume'" # Exclude inbox
         "--verbose"
       ];
     };
@@ -442,6 +451,43 @@ in
     };
   };
 
+  # Override paperless services to run as vincent
+  systemd.services.paperless-scheduler.serviceConfig = {
+    User = lib.mkForce "vincent";
+    Group = lib.mkForce "users";
+    ReadWritePaths = [ "/neo/paperless/trash" ];
+  };
+
+  systemd.services.paperless-task-queue.serviceConfig = {
+    User = lib.mkForce "vincent";
+    Group = lib.mkForce "users";
+    ReadWritePaths = [
+      "/neo/paperless/trash"
+      "/neo/paperless"
+    ];
+  };
+
+  systemd.services.paperless-consumer.serviceConfig = {
+    User = lib.mkForce "vincent";
+    Group = lib.mkForce "users";
+    ReadWritePaths = [ "/neo/paperless/trash" ];
+  };
+
+  systemd.services.paperless-web.serviceConfig = {
+    User = lib.mkForce "vincent";
+    Group = lib.mkForce "users";
+    ReadWritePaths = [ "/neo/paperless/trash" ];
+  };
+
+  # Create paperless directory structure
+  systemd.tmpfiles.rules = [
+    "d /neo/paperless 0755 vincent users -"
+    "d /neo/paperless/data 0755 vincent users -"
+    "d /neo/paperless/media 0755 vincent users -"
+    "d /neo/paperless/consume 0755 vincent users -"
+    "d /neo/paperless/trash 0755 vincent users -"
+  ];
+
   # Override prometheus-restic-exporter service to disable DynamicUser
   # This is needed so the service runs as vincent and can access SSH keys
   # DISABLED: Service is currently disabled due to excessive load
@@ -463,6 +509,7 @@ in
       allowedTCPPorts = [
         3001 # Homepage dashboard
         4533 # Navidrome
+        8000 # Paperless
         8384 # Syncthing web UI
         13378 # Audiobookshelf
         8686 # Lidarr
systems/rhea/extra.nix
@@ -361,7 +361,7 @@ in
                 home = mkService "http://${builtins.head globals.machines.hass.net.ips}:8123";
                 kiwix = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:8080";
                 n8n = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:5678";
-                paperless = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:8000";
+                paperless = mkService "http://${builtins.head globals.machines.aion.net.ips}:8000";
                 grafana = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:3000";
                 navidrome = mkService "http://${builtins.head globals.machines.aion.net.ips}:4533";
                 transmission-music = mkService "http://${builtins.head globals.machines.aion.net.ips}:9091";
globals.nix
@@ -49,6 +49,22 @@ _: {
       id = "ai-sync"; # unified AI agent storage (sessions, plans, learnings, research)
       path = "/home/vincent/.local/share/ai-sync";
     };
+    paperless-media = {
+      id = "paperless-media";
+      path = "/neo/paperless/media";
+      versioning = {
+        type = "staggered";
+        params = {
+          cleanInterval = "3600"; # cleanup every hour
+          maxAge = "15768000"; # keep for ~6 months (182 days in seconds)
+        };
+      };
+    };
+    paperless-inbox = {
+      id = "paperless-inbox";
+      path = "/neo/paperless/consume";
+      # No versioning - temporary files get processed and deleted
+    };
   };
   net = {
     dns = {
@@ -191,6 +207,10 @@ _: {
           wallpapers = { };
           claude-sync = { };
           ai-sync = { };
+          paperless-media = {
+            type = "receiveonly";
+          };
+          paperless-inbox = { };
           # TODO: implement paused or filter theses
           # photos = {
           #   type = "receiveonly";
@@ -229,6 +249,10 @@ _: {
           wallpapers = { };
           claude-sync = { };
           ai-sync = { };
+          paperless-media = {
+            type = "receiveonly";
+          };
+          paperless-inbox = { };
           # photos = {
           #   type = "receiveonly";
           #   paused = true; # TODO: implement this, start as paused
@@ -371,6 +395,10 @@ _: {
           wallpapers = {
             path = "/neo/pictures/vincent/wallpapers";
           };
+          paperless-media = {
+            type = "receiveonly";
+          };
+          paperless-inbox = { };
         };
       };
     };
@@ -395,6 +423,8 @@ _: {
         folders = {
           org = { };
           sync = { };
+          paperless-media = { };
+          paperless-inbox = { };
         };
       };
     };