From: self <self@awful.systems>
Date: Sat, 22 Jul 2023 23:14:02 +0000 (-0700)
Subject: fix federation between lemmy and other activitypub services
X-Git-Url: http://these/git/%7B%60/feeds/inbox/%22%7B%7D/%22?a=commitdiff_plain;h=c34cc3ee554e4d821d103fd6b1ce30621b3b08d8;p=awful.systems.git

fix federation between lemmy and other activitypub services
---

diff --git a/lemmy/dev/module.nix b/lemmy/dev/module.nix
index 8ae458d..97887c0 100644
--- a/lemmy/dev/module.nix
+++ b/lemmy/dev/module.nix
@@ -154,12 +154,30 @@ in {
       };
     };
 
-    services.nginx = mkIf cfg.nginx.enable {
+    services.nginx = let
+      ui = "http://127.0.0.1:${toString cfg.ui.port}";
+      backend = "http://127.0.0.1:${toString cfg.settings.port}";
+    in mkIf cfg.nginx.enable {
       enable = mkDefault true;
-      virtualHosts."${cfg.settings.hostname}".locations = let
-        ui = "http://127.0.0.1:${toString cfg.ui.port}";
-        backend = "http://127.0.0.1:${toString cfg.settings.port}";
-      in {
+      appendHttpConfig = ''
+        map "$request_method:$http_accept" $proxpass {
+            # If no explicit matches exists below, send traffic to lemmy-ui
+            default "${ui}";
+
+            # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
+            #
+            # These requests are used by Mastodon and other fediverse instances to look up profile information,
+            # discover site information and so on.
+            "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "${backend}";
+
+            # All non-GET/HEAD requests should go to lemmy
+            #
+            # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
+            # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
+            "~^(?!(GET|HEAD)).*:" "${backend}";
+        }
+      '';
+      virtualHosts."${cfg.settings.hostname}".locations = {
         "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
           # backend requests
           proxyPass = backend;
@@ -171,17 +189,6 @@ in {
           proxyPass = "$proxpass";
           recommendedProxySettings = true;
           extraConfig = ''
-            set $proxpass "${ui}";
-            if ($http_accept = "application/activity+json") {
-              set $proxpass "${backend}";
-            }
-            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
-              set $proxpass "${backend}";
-            }
-            if ($request_method = POST) {
-              set $proxpass "${backend}";
-            }
-
             # Cuts off the trailing slash on URLs to make them valid
             rewrite ^(.+)/+$ $1 permanent;
           '';
@@ -238,7 +245,8 @@ in {
 
       environment = {
         LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
-        LEMMY_UI_LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
+        LEMMY_UI_LEMMY_INTERNAL_HOST =
+          "127.0.0.1:${toString cfg.settings.port}";
         LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
         LEMMY_UI_HTTPS = "false";
       };
diff --git a/lemmy/prod/module.nix b/lemmy/prod/module.nix
index 58cacb8..dc24bd7 100644
--- a/lemmy/prod/module.nix
+++ b/lemmy/prod/module.nix
@@ -3,40 +3,44 @@ with lib;
 let
   cfg = config.services.lemmy-prod;
   settingsFormat = pkgs.formats.json { };
-in
-{
+in {
   imports = [
-    (mkRemovedOptionModule [ "services" "lemmy-prod" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
+    (mkRemovedOptionModule [ "services" "lemmy-prod" "jwtSecretPath" ]
+      "As of v0.13.0, Lemmy auto-generates the JWT secret.")
   ];
 
   options.services.lemmy-prod = {
 
-    enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
+    enable = mkEnableOption
+      (lib.mdDoc "lemmy a federated alternative to reddit in rust");
 
-    server = {
-      package = mkPackageOptionMD pkgs "lemmy-server" {};
-    };
+    server = { package = mkPackageOptionMD pkgs "lemmy-server" { }; };
 
     ui = {
-      package = mkPackageOptionMD pkgs "lemmy-ui" {};
+      package = mkPackageOptionMD pkgs "lemmy-ui" { };
 
       port = mkOption {
         type = types.port;
         default = 1234;
-        description = lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
+        description =
+          lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
       };
     };
 
-    caddy.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
-    nginx.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the nginx reverse proxy");
+    caddy.enable =
+      mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
+    nginx.enable =
+      mkEnableOption (lib.mdDoc "exposing lemmy with the nginx reverse proxy");
 
     database = {
-      createLocally = mkEnableOption (lib.mdDoc "creation of database on the instance");
+      createLocally =
+        mkEnableOption (lib.mdDoc "creation of database on the instance");
 
       uri = mkOption {
         type = with types; nullOr str;
         default = null;
-        description = lib.mdDoc "The connection URI to use. Takes priority over the configuration file if set.";
+        description = lib.mdDoc
+          "The connection URI to use. Takes priority over the configuration file if set.";
       };
     };
 
@@ -50,13 +54,15 @@ in
         options.hostname = mkOption {
           type = types.str;
           default = null;
-          description = lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
+          description =
+            lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
         };
 
         options.port = mkOption {
           type = types.port;
           default = 8536;
-          description = lib.mdDoc "Port where lemmy should listen for incoming requests.";
+          description =
+            lib.mdDoc "Port where lemmy should listen for incoming requests.";
         };
 
         options.captcha = {
@@ -76,178 +82,193 @@ in
 
   };
 
-  config =
-    lib.mkIf cfg.enable {
-      services.lemmy-prod.settings = (mapAttrs (name: mkDefault)
-        {
-          bind = "127.0.0.1";
-          tls_enabled = true;
-          pictrs_url = with config.services.pict-rs; "http://${address}:${toString port}";
-          actor_name_max_length = 20;
-
-          rate_limit.message = 180;
-          rate_limit.message_per_second = 60;
-          rate_limit.post = 6;
-          rate_limit.post_per_second = 600;
-          rate_limit.register = 3;
-          rate_limit.register_per_second = 3600;
-          rate_limit.image = 6;
-          rate_limit.image_per_second = 3600;
-        } // {
-        database = mapAttrs (name: mkDefault) {
-          user = "lemmy";
-          host = "/run/postgresql";
-          port = 5432;
-          database = "lemmy";
-          pool_size = 5;
-        };
-      });
-
-      services.postgresql = mkIf cfg.database.createLocally {
-        enable = true;
-        ensureDatabases = [ cfg.settings.database.database ];
-        ensureUsers = [{
-          name = cfg.settings.database.user;
-          ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
-        }];
+  config = lib.mkIf cfg.enable {
+    services.lemmy-prod.settings = (mapAttrs (name: mkDefault) {
+      bind = "127.0.0.1";
+      tls_enabled = true;
+      pictrs_url = with config.services.pict-rs;
+        "http://${address}:${toString port}";
+      actor_name_max_length = 20;
+
+      rate_limit.message = 180;
+      rate_limit.message_per_second = 60;
+      rate_limit.post = 6;
+      rate_limit.post_per_second = 600;
+      rate_limit.register = 3;
+      rate_limit.register_per_second = 3600;
+      rate_limit.image = 6;
+      rate_limit.image_per_second = 3600;
+    } // {
+      database = mapAttrs (name: mkDefault) {
+        user = "lemmy";
+        host = "/run/postgresql";
+        port = 5432;
+        database = "lemmy";
+        pool_size = 5;
       };
+    });
+
+    services.postgresql = mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ cfg.settings.database.database ];
+      ensureUsers = [{
+        name = cfg.settings.database.user;
+        ensurePermissions."DATABASE ${cfg.settings.database.database}" =
+          "ALL PRIVILEGES";
+      }];
+    };
 
-      services.pict-rs.enable = true;
+    services.pict-rs.enable = true;
+
+    services.caddy = mkIf cfg.caddy.enable {
+      enable = mkDefault true;
+      virtualHosts."${cfg.settings.hostname}" = {
+        extraConfig = ''
+          handle_path /static/* {
+            root * ${cfg.ui.package}/dist
+            file_server
+          }
+          @for_backend {
+            path /api/* /pictrs/* /feeds/* /nodeinfo/*
+          }
+          handle @for_backend {
+            reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+          }
+          @post {
+            method POST
+          }
+          handle @post {
+            reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+          }
+          @jsonld {
+            header Accept "application/activity+json"
+            header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+          }
+          handle @jsonld {
+            reverse_proxy 127.0.0.1:${toString cfg.settings.port}
+          }
+          handle {
+            reverse_proxy 127.0.0.1:${toString cfg.ui.port}
+          }
+        '';
+      };
+    };
 
-      services.caddy = mkIf cfg.caddy.enable {
-        enable = mkDefault true;
-        virtualHosts."${cfg.settings.hostname}" = {
+    services.nginx = let
+      ui = "http://127.0.0.1:${toString cfg.ui.port}";
+      backend = "http://127.0.0.1:${toString cfg.settings.port}";
+    in mkIf cfg.nginx.enable {
+      enable = mkDefault true;
+      appendHttpConfig = ''
+        map "$request_method:$http_accept" $proxpass {
+            # If no explicit matches exists below, send traffic to lemmy-ui
+            default "${ui}";
+
+            # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
+            #
+            # These requests are used by Mastodon and other fediverse instances to look up profile information,
+            # discover site information and so on.
+            "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "${backend}";
+
+            # All non-GET/HEAD requests should go to lemmy
+            #
+            # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
+            # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
+            "~^(?!(GET|HEAD)).*:" "${backend}";
+        }
+      '';
+      virtualHosts."${cfg.settings.hostname}".locations = {
+        "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
+          # backend requests
+          proxyPass = backend;
+          proxyWebsockets = true;
+          recommendedProxySettings = true;
+        };
+        "/" = {
+          # mixed frontend and backend requests, based on the request headers
+          proxyPass = "$proxpass";
+          recommendedProxySettings = true;
           extraConfig = ''
-            handle_path /static/* {
-              root * ${cfg.ui.package}/dist
-              file_server
-            }
-            @for_backend {
-              path /api/* /pictrs/* /feeds/* /nodeinfo/*
-            }
-            handle @for_backend {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            @post {
-              method POST
-            }
-            handle @post {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            @jsonld {
-              header Accept "application/activity+json"
-              header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
-            }
-            handle @jsonld {
-              reverse_proxy 127.0.0.1:${toString cfg.settings.port}
-            }
-            handle {
-              reverse_proxy 127.0.0.1:${toString cfg.ui.port}
-            }
+            # Cuts off the trailing slash on URLs to make them valid
+            rewrite ^(.+)/+$ $1 permanent;
           '';
         };
       };
+    };
 
-      services.nginx = mkIf cfg.nginx.enable {
-        enable = mkDefault true;
-        virtualHosts."${cfg.settings.hostname}".locations = let
-          ui = "http://127.0.0.1:${toString cfg.ui.port}";
-          backend = "http://127.0.0.1:${toString cfg.settings.port}";
-        in {
-          "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
-            # backend requests
-            proxyPass = backend;
-            proxyWebsockets = true;
-            recommendedProxySettings = true;
-          };
-          "/" = {
-            # mixed frontend and backend requests, based on the request headers
-            proxyPass = "$proxpass";
-            recommendedProxySettings = true;
-            extraConfig = ''
-              set $proxpass "${ui}";
-              if ($http_accept = "application/activity+json") {
-                set $proxpass "${backend}";
-              }
-              if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
-                set $proxpass "${backend}";
-              }
-              if ($request_method = POST) {
-                set $proxpass "${backend}";
-              }
-
-              # Cuts off the trailing slash on URLs to make them valid
-              rewrite ^(.+)/+$ $1 permanent;
-            '';
-          };
-        };
+    assertions = [
+      {
+        assertion = cfg.database.createLocally -> cfg.settings.database.host
+          == "localhost" || cfg.settings.database.host == "/run/postgresql";
+        message =
+          "if you want to create the database locally, you need to use a local database";
+      }
+      {
+        assertion = (!(hasAttrByPath [ "federation" ] cfg.settings))
+          && (!(hasAttrByPath [ "federation" "enabled" ] cfg.settings));
+        message =
+          "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
+      }
+    ];
+
+    systemd.services.lemmy-prod = {
+      description = "Lemmy server (production)";
+
+      environment = {
+        LEMMY_CONFIG_LOCATION =
+          "${settingsFormat.generate "config.hjson" cfg.settings}";
+        LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
       };
 
-      assertions = [
-        {
-          assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql";
-          message = "if you want to create the database locally, you need to use a local database";
-        }
-        {
-          assertion = (!(hasAttrByPath ["federation"] cfg.settings)) && (!(hasAttrByPath ["federation" "enabled"] cfg.settings));
-          message = "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
-        }
+      documentation = [
+        "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+        "https://join-lemmy.org/docs/en/"
       ];
 
-      systemd.services.lemmy-prod = {
-        description = "Lemmy server (production)";
-
-        environment = {
-          LEMMY_CONFIG_LOCATION = "${settingsFormat.generate "config.hjson" cfg.settings}";
-          LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
-        };
-
-        documentation = [
-          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
-          "https://join-lemmy.org/docs/en/"
-        ];
-
-        wantedBy = [ "multi-user.target" ];
+      wantedBy = [ "multi-user.target" ];
 
-        after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
+      after = [ "pict-rs.service" ]
+        ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
 
-        requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
+      requires =
+        lib.optionals cfg.database.createLocally [ "postgresql.service" ];
 
-        serviceConfig = {
-          DynamicUser = true;
-          RuntimeDirectory = "lemmy";
-          ExecStart = "${cfg.server.package}/bin/lemmy_server";
-        };
+      serviceConfig = {
+        DynamicUser = true;
+        RuntimeDirectory = "lemmy";
+        ExecStart = "${cfg.server.package}/bin/lemmy_server";
       };
+    };
 
-      systemd.services.lemmy-ui-prod = {
-        description = "Lemmy UI (production)";
+    systemd.services.lemmy-ui-prod = {
+      description = "Lemmy UI (production)";
 
-        environment = {
-          LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
-          LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
-          LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
-          LEMMY_HTTPS = "false";
-          NODE_ENV = "production";
-        };
+      environment = {
+        LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
+        LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
+        LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
+        LEMMY_HTTPS = "false";
+        NODE_ENV = "production";
+      };
 
-        documentation = [
-          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
-          "https://join-lemmy.org/docs/en/"
-        ];
+      documentation = [
+        "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+        "https://join-lemmy.org/docs/en/"
+      ];
 
-        wantedBy = [ "multi-user.target" ];
+      wantedBy = [ "multi-user.target" ];
 
-        after = [ "lemmy-prod.service" ];
+      after = [ "lemmy-prod.service" ];
 
-        requires = [ "lemmy-prod.service" ];
+      requires = [ "lemmy-prod.service" ];
 
-        serviceConfig = {
-          DynamicUser = true;
-          WorkingDirectory = "${cfg.ui.package}";
-          ExecStart = "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
-        };
+      serviceConfig = {
+        DynamicUser = true;
+        WorkingDirectory = "${cfg.ui.package}";
+        ExecStart =
+          "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
       };
     };
+  };
 
 }
diff --git a/lemmy/staging/module.nix b/lemmy/staging/module.nix
index 87a9063..575ff63 100644
--- a/lemmy/staging/module.nix
+++ b/lemmy/staging/module.nix
@@ -154,12 +154,30 @@ in {
       };
     };
 
-    services.nginx = mkIf cfg.nginx.enable {
+    services.nginx = let
+      ui = "http://127.0.0.1:${toString cfg.ui.port}";
+      backend = "http://127.0.0.1:${toString cfg.settings.port}";
+    in mkIf cfg.nginx.enable {
       enable = mkDefault true;
-      virtualHosts."${cfg.settings.hostname}".locations = let
-        ui = "http://127.0.0.1:${toString cfg.ui.port}";
-        backend = "http://127.0.0.1:${toString cfg.settings.port}";
-      in {
+      appendHttpConfig = ''
+        map "$request_method:$http_accept" $proxpass {
+            # If no explicit matches exists below, send traffic to lemmy-ui
+            default "${ui}";
+
+            # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
+            #
+            # These requests are used by Mastodon and other fediverse instances to look up profile information,
+            # discover site information and so on.
+            "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "${backend}";
+
+            # All non-GET/HEAD requests should go to lemmy
+            #
+            # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
+            # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
+            "~^(?!(GET|HEAD)).*:" "${backend}";
+        }
+      '';
+      virtualHosts."${cfg.settings.hostname}".locations = {
         "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
           # backend requests
           proxyPass = backend;
@@ -171,17 +189,6 @@ in {
           proxyPass = "$proxpass";
           recommendedProxySettings = true;
           extraConfig = ''
-            set $proxpass "${ui}";
-            if ($http_accept = "application/activity+json") {
-              set $proxpass "${backend}";
-            }
-            if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
-              set $proxpass "${backend}";
-            }
-            if ($request_method = POST) {
-              set $proxpass "${backend}";
-            }
-
             # Cuts off the trailing slash on URLs to make them valid
             rewrite ^(.+)/+$ $1 permanent;
           '';
@@ -238,7 +245,8 @@ in {
 
       environment = {
         LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
-        LEMMY_UI_LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
+        LEMMY_UI_LEMMY_INTERNAL_HOST =
+          "127.0.0.1:${toString cfg.settings.port}";
         LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
         LEMMY_UI_HTTPS = "false";
       };