Commit 81b189d66526

Vincent Demeester <vincent@sbr.pm>
2025-12-18 22:00:17
feat(webdav): migrate to lightweight standalone server on rhea
- Consolidate WebDAV on rhea where storage resides - Replace nginx+modules with 9.5MB standalone server - Resolve athena logrotate permission issues Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 4e9318d
Changed files (5)
secrets/rhea/webdav-password.age
Binary file
systems/athena/extra.nix
@@ -1,6 +1,4 @@
 {
-  pkgs,
-  lib,
   libx,
   globals,
   ...
@@ -17,14 +15,6 @@
   # TODO make it an option ? (otherwise I'll add it for all)
   users.users.vincent.linger = true;
 
-  # Ensure nginx can access vincent's home directory
-  systemd.services.nginx.serviceConfig = {
-    ReadWritePaths = [ "/home/vincent/sync/boox" ];
-    # Run nginx workers as vincent user for proper permissions
-    User = lib.mkForce "vincent";
-    Group = lib.mkForce "users";
-  };
-
   services = {
     wireguard = {
       enable = true;
@@ -32,54 +22,5 @@
       endpoint = "${globals.net.vpn.endpoint}";
       endpointPublicKey = "${globals.machines.kerkouane.net.vpn.pubkey}";
     };
-    nginx = {
-      enable = true;
-      statusPage = true;
-      package = pkgs.nginxMainline.override (_old: {
-        modules = with pkgs.nginxModules; [
-          fancyindex
-          dav
-        ];
-      });
-      recommendedGzipSettings = true;
-      recommendedTlsSettings = true;
-      recommendedOptimisation = true;
-      virtualHosts."dav.athena.sbr.pm" = {
-        locations."/" = {
-          root = "/home/vincent/sync/boox";
-          basicAuthFile = "/var/www/dav.auth";
-          extraConfig = ''
-            						autoindex on;
-                        set $x $uri$request_method;
-                        if ($x ~ [^/]MKCOL$) {
-                            rewrite ^(.*)$ $1/;
-                        }
-                        dav_methods PUT DELETE MKCOL COPY MOVE;
-                        dav_ext_methods PROPFIND OPTIONS;
-                        dav_access user:rw group:rw all:r;
-                        client_body_temp_path /var/cache/nginx;
-                        create_full_put_path on;
-                        # add_header 'Access-Control-Allow-Origin' '$ALLOWED_ORIGIN' always;
-                        # add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, OPTIONS' always;
-                        # add_header 'Access-Control-Allow-Headers' 'Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization,X-CSRF-Token,Depth' always;
-                        # add_header 'Access-Control-Allow-Credentials' 'true' always;
-          '';
-        };
-      };
-      virtualHosts."home.athena.sbr.pm" = {
-        listen = [
-          {
-            addr = "0.0.0.0";
-            port = 8080;
-          }
-        ];
-        locations."/" = {
-          root = "${pkgs.homepage}";
-          extraConfig = ''
-            index index.html;
-          '';
-        };
-      };
-    };
   };
 }
systems/rhea/extra.nix
@@ -58,7 +58,7 @@ in
     ../../modules/music-playlist-dl
   ];
 
-  # Age secrets: gandi.env + generated exportarr secrets
+  # Age secrets: gandi.env + webdav + jellyfin + generated exportarr secrets
   age.secrets = {
     "gandi.env" = {
       file = ../../secrets/rhea/gandi.env.age;
@@ -66,6 +66,10 @@ in
       owner = "traefik";
       group = "traefik";
     };
+    "webdav-password" = {
+      file = ../../secrets/rhea/webdav-password.age;
+      mode = "400";
+    };
     "jellyfin-auto-collections-api-key" = {
       file = ../../secrets/rhea/jellyfin-auto-collections-api-key.age;
       mode = "400";
@@ -190,10 +194,6 @@ in
               altHosts = [ "t.sbr.pm" ];
             };
             immich.port = 2283;
-            navidrome = {
-              port = 4533;
-              altHosts = [ "music.sbr.pm" ];
-            };
             audiobookshelf = {
               port = 13378;
               altHosts = [ "podcasts.sbr.pm" ];
@@ -202,6 +202,7 @@ in
               port = 8083;
               altHosts = [ "books.sbr.pm" ];
             };
+            dav.port = 6065;
             homepage.port = 3001;
           };
 
@@ -289,7 +290,10 @@ in
                 n8n = mkRouter "n8n" [ "n8n.sbr.pm" ];
                 paperless = mkRouter "paperless" [ "paperless.sbr.pm" ];
                 grafana = mkRouter "grafana" [ "grafana.sbr.pm" ];
-                dav = mkRouter "dav" [ "dav.sbr.pm" ];
+                navidrome = mkRouter "navidrome" [
+                  "navidrome.sbr.pm"
+                  "music.sbr.pm"
+                ];
                 linkwarden = mkRouter "linkwarden" [
                   "linkwarden.sbr.pm"
                   "links.sbr.pm"
@@ -313,7 +317,6 @@ in
                 grafana = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:3000";
                 linkwarden = mkService "http://${builtins.head globals.machines.sakhalin.net.ips}:3002";
                 navidrome = mkService "http://${builtins.head globals.machines.aion.net.ips}:4533";
-                dav = mkService "http://${builtins.head globals.machines.athena.net.ips}:80";
               };
             middlewares =
               syncthingMiddlewares
@@ -436,6 +439,31 @@ in
       port = 13378;
       host = "0.0.0.0";
     };
+    webdav = {
+      enable = true;
+      user = "vincent";
+      group = "users";
+      environmentFile = config.age.secrets."webdav-password".path;
+      settings = {
+        address = "127.0.0.1";
+        port = 6065;
+        scope = "/neo/documents/boox";
+        modify = true;
+        users = [
+          {
+            username = "vincent";
+            password = "{env}WEBDAV_PASSWORD_HASH";
+          }
+        ];
+        rules = [
+          {
+            regex = true;
+            allow = false;
+            path = "/(\\..*|.*\\.tmp)$"; # Block hidden files and .tmp files
+          }
+        ];
+      };
+    };
     audible-sync = {
       enable = true;
       user = "vincent";
globals.nix
@@ -536,7 +536,7 @@ _: {
     # Ebook library management on rhea
     calibre.host = "rhea";
     books.host = "rhea";
-    # WebDAV on athena (routed through rhea/traefik)
+    # WebDAV on rhea
     dav.host = "rhea";
     # MQTT on demeter (routed through rhea/traefik)
     mqtt.host = "rhea";
secrets.nix
@@ -101,6 +101,7 @@ in
   "secrets/rhea/exportarr-bazarr-apikey.age".publicKeys = users ++ [ rhea ];
   "secrets/rhea/jellyfin-auto-collections-api-key.age".publicKeys = users ++ [ rhea ];
   "secrets/rhea/jellyfin-auto-collections-jellyseerr-password.age".publicKeys = users ++ [ rhea ];
+  "secrets/rhea/webdav-password.age".publicKeys = users ++ [ rhea ];
   "secrets/sakhalin/grafana-admin-password.age".publicKeys = users ++ [ sakhalin ];
   "secrets/demeter/mosquitto-homeassistant-password.age".publicKeys = users ++ [ demeter ];
 }