]> Untitled Git - awful.systems.git/blob - lemmy/staging/module.nix
fix federation between lemmy and other activitypub services
[awful.systems.git] / lemmy / staging / module.nix
1 { lib, pkgs, config, ... }:
2 with lib;
3 let
4   cfg = config.services.lemmy-staging;
5   settingsFormat = pkgs.formats.json { };
6 in {
7   imports = [
8     (mkRemovedOptionModule [ "services" "lemmy-staging" "jwtSecretPath" ]
9       "As of v0.13.0, Lemmy auto-generates the JWT secret.")
10   ];
11
12   options.services.lemmy-staging = {
13
14     enable = mkEnableOption
15       (lib.mdDoc "lemmy a federated alternative to reddit in rust");
16
17     server = { package = mkPackageOptionMD pkgs "lemmy-server" { }; };
18
19     ui = {
20       package = mkPackageOptionMD pkgs "lemmy-ui" { };
21
22       port = mkOption {
23         type = types.port;
24         default = 1235;
25         description =
26           lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
27       };
28     };
29
30     caddy.enable =
31       mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
32     nginx.enable =
33       mkEnableOption (lib.mdDoc "exposing lemmy with the nginx reverse proxy");
34
35     database = {
36       createLocally =
37         mkEnableOption (lib.mdDoc "creation of database on the instance");
38
39       uri = mkOption {
40         type = with types; nullOr str;
41         default = null;
42         description = lib.mdDoc
43           "The connection URI to use. Takes priority over the configuration file if set.";
44       };
45     };
46
47     settings = mkOption {
48       default = { };
49       description = lib.mdDoc "Lemmy configuration";
50
51       type = types.submodule {
52         freeformType = settingsFormat.type;
53
54         options.hostname = mkOption {
55           type = types.str;
56           default = null;
57           description =
58             lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
59         };
60
61         options.port = mkOption {
62           type = types.port;
63           default = 8537;
64           description =
65             lib.mdDoc "Port where lemmy should listen for incoming requests.";
66         };
67
68         options.captcha = {
69           enabled = mkOption {
70             type = types.bool;
71             default = true;
72             description = lib.mdDoc "Enable Captcha.";
73           };
74           difficulty = mkOption {
75             type = types.enum [ "easy" "medium" "hard" ];
76             default = "medium";
77             description = lib.mdDoc "The difficultly of the captcha to solve.";
78           };
79         };
80       };
81     };
82
83   };
84
85   config = lib.mkIf cfg.enable {
86     services.lemmy-staging.settings = (mapAttrs (name: mkDefault) {
87       bind = "127.0.0.1";
88       tls_enabled = true;
89       pictrs_url = with config.services.pict-rs;
90         "http://${address}:${toString port}";
91       actor_name_max_length = 20;
92
93       rate_limit.message = 180;
94       rate_limit.message_per_second = 60;
95       rate_limit.post = 6;
96       rate_limit.post_per_second = 600;
97       rate_limit.register = 3;
98       rate_limit.register_per_second = 3600;
99       rate_limit.image = 6;
100       rate_limit.image_per_second = 3600;
101     } // {
102       database = mapAttrs (name: mkDefault) {
103         user = "lemmy";
104         host = "/run/postgresql";
105         port = 5432;
106         database = "lemmy";
107         pool_size = 5;
108       };
109     });
110
111     services.postgresql = mkIf cfg.database.createLocally {
112       enable = true;
113       ensureDatabases = [ cfg.settings.database.database ];
114       ensureUsers = [{
115         name = cfg.settings.database.user;
116         ensurePermissions."DATABASE ${cfg.settings.database.database}" =
117           "ALL PRIVILEGES";
118       }];
119     };
120
121     services.pict-rs.enable = true;
122
123     services.caddy = mkIf cfg.caddy.enable {
124       enable = mkDefault true;
125       virtualHosts."${cfg.settings.hostname}" = {
126         extraConfig = ''
127           handle_path /static/* {
128             root * ${cfg.ui.package}/dist
129             file_server
130           }
131           @for_backend {
132             path /api/* /pictrs/* /feeds/* /nodeinfo/*
133           }
134           handle @for_backend {
135             reverse_proxy 127.0.0.1:${toString cfg.settings.port}
136           }
137           @post {
138             method POST
139           }
140           handle @post {
141             reverse_proxy 127.0.0.1:${toString cfg.settings.port}
142           }
143           @jsonld {
144             header Accept "application/activity+json"
145             header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
146           }
147           handle @jsonld {
148             reverse_proxy 127.0.0.1:${toString cfg.settings.port}
149           }
150           handle {
151             reverse_proxy 127.0.0.1:${toString cfg.ui.port}
152           }
153         '';
154       };
155     };
156
157     services.nginx = let
158       ui = "http://127.0.0.1:${toString cfg.ui.port}";
159       backend = "http://127.0.0.1:${toString cfg.settings.port}";
160     in mkIf cfg.nginx.enable {
161       enable = mkDefault true;
162       appendHttpConfig = ''
163         map "$request_method:$http_accept" $proxpass {
164             # If no explicit matches exists below, send traffic to lemmy-ui
165             default "${ui}";
166
167             # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
168             #
169             # These requests are used by Mastodon and other fediverse instances to look up profile information,
170             # discover site information and so on.
171             "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "${backend}";
172
173             # All non-GET/HEAD requests should go to lemmy
174             #
175             # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
176             # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
177             "~^(?!(GET|HEAD)).*:" "${backend}";
178         }
179       '';
180       virtualHosts."${cfg.settings.hostname}".locations = {
181         "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
182           # backend requests
183           proxyPass = backend;
184           proxyWebsockets = true;
185           recommendedProxySettings = true;
186         };
187         "/" = {
188           # mixed frontend and backend requests, based on the request headers
189           proxyPass = "$proxpass";
190           recommendedProxySettings = true;
191           extraConfig = ''
192             # Cuts off the trailing slash on URLs to make them valid
193             rewrite ^(.+)/+$ $1 permanent;
194           '';
195         };
196       };
197     };
198
199     assertions = [
200       {
201         assertion = cfg.database.createLocally -> cfg.settings.database.host
202           == "localhost" || cfg.settings.database.host == "/run/postgresql";
203         message =
204           "if you want to create the database locally, you need to use a local database";
205       }
206       {
207         assertion = (!(hasAttrByPath [ "federation" ] cfg.settings))
208           && (!(hasAttrByPath [ "federation" "enabled" ] cfg.settings));
209         message =
210           "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
211       }
212     ];
213
214     systemd.services.lemmy-staging = {
215       description = "Lemmy server (staging)";
216
217       environment = {
218         LEMMY_CONFIG_LOCATION =
219           "${settingsFormat.generate "config.hjson" cfg.settings}";
220         LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
221       };
222
223       documentation = [
224         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
225         "https://join-lemmy.org/docs/en/"
226       ];
227
228       wantedBy = [ "multi-user.target" ];
229
230       after = [ "pict-rs.service" ]
231         ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
232
233       requires =
234         lib.optionals cfg.database.createLocally [ "postgresql.service" ];
235
236       serviceConfig = {
237         DynamicUser = true;
238         RuntimeDirectory = "lemmy";
239         ExecStart = "${cfg.server.package}/bin/lemmy_server";
240       };
241     };
242
243     systemd.services.lemmy-ui-staging = {
244       description = "Lemmy UI (staging)";
245
246       environment = {
247         LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
248         LEMMY_UI_LEMMY_INTERNAL_HOST =
249           "127.0.0.1:${toString cfg.settings.port}";
250         LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
251         LEMMY_UI_HTTPS = "false";
252       };
253
254       documentation = [
255         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
256         "https://join-lemmy.org/docs/en/"
257       ];
258
259       wantedBy = [ "multi-user.target" ];
260
261       after = [ "lemmy-staging.service" ];
262
263       requires = [ "lemmy-staging.service" ];
264
265       serviceConfig = {
266         DynamicUser = true;
267         WorkingDirectory = "${cfg.ui.package}";
268         ExecStart =
269           "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
270       };
271     };
272   };
273
274 }