]> Untitled Git - awful.systems.git/blob - lemmy/prod/module.nix
docs: add database information
[awful.systems.git] / lemmy / prod / module.nix
1 { lib, pkgs, config, ... }:
2 with lib;
3 let
4   cfg = config.services.lemmy-prod;
5   settingsFormat = pkgs.formats.json { };
6 in {
7   imports = [
8     (mkRemovedOptionModule [ "services" "lemmy-prod" "jwtSecretPath" ]
9       "As of v0.13.0, Lemmy auto-generates the JWT secret.")
10   ];
11
12   options.services.lemmy-prod = {
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 = 1234;
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 = 8536;
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-prod.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       eventsConfig = ''
163         worker_connections 20000;
164       '';
165       appendHttpConfig = ''
166         map "$request_method:$http_accept" $proxpass {
167             # If no explicit matches exists below, send traffic to lemmy-ui
168             default "${ui}";
169
170             # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
171             #
172             # These requests are used by Mastodon and other fediverse instances to look up profile information,
173             # discover site information and so on.
174             "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "${backend}";
175
176             # All non-GET/HEAD requests should go to lemmy
177             #
178             # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
179             # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
180             "~^(?!(GET|HEAD)).*:" "${backend}";
181         }
182       '';
183       virtualHosts."${cfg.settings.hostname}".locations = {
184         "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
185           # backend requests
186           proxyPass = backend;
187           proxyWebsockets = true;
188           recommendedProxySettings = true;
189         };
190         "/" = {
191           # mixed frontend and backend requests, based on the request headers
192           proxyPass = "$proxpass";
193           recommendedProxySettings = true;
194           extraConfig = ''
195             # Cuts off the trailing slash on URLs to make them valid
196             rewrite ^(.+)/+$ $1 permanent;
197           '';
198         };
199       };
200     };
201
202     assertions = [
203       {
204         assertion = cfg.database.createLocally -> cfg.settings.database.host
205           == "localhost" || cfg.settings.database.host == "/run/postgresql";
206         message =
207           "if you want to create the database locally, you need to use a local database";
208       }
209       {
210         assertion = (!(hasAttrByPath [ "federation" ] cfg.settings))
211           && (!(hasAttrByPath [ "federation" "enabled" ] cfg.settings));
212         message =
213           "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
214       }
215     ];
216
217     systemd.services.lemmy-prod = {
218       description = "Lemmy server (production)";
219
220       environment = {
221         LEMMY_CONFIG_LOCATION =
222           "${settingsFormat.generate "config.hjson" cfg.settings}";
223         LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
224       };
225
226       documentation = [
227         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
228         "https://join-lemmy.org/docs/en/"
229       ];
230
231       wantedBy = [ "multi-user.target" ];
232
233       after = [ "pict-rs.service" ]
234         ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
235
236       requires =
237         lib.optionals cfg.database.createLocally [ "postgresql.service" ];
238
239       serviceConfig = {
240         DynamicUser = true;
241         RuntimeDirectory = "lemmy";
242         ExecStart = "${cfg.server.package}/bin/lemmy_server";
243       };
244     };
245
246     systemd.services.lemmy-ui-prod = {
247       description = "Lemmy UI (production)";
248
249       environment = {
250         LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
251         LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
252         LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
253         LEMMY_HTTPS = "false";
254         NODE_ENV = "production";
255       };
256
257       documentation = [
258         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
259         "https://join-lemmy.org/docs/en/"
260       ];
261
262       wantedBy = [ "multi-user.target" ];
263
264       after = [ "lemmy-prod.service" ];
265
266       requires = [ "lemmy-prod.service" ];
267
268       serviceConfig = {
269         DynamicUser = true;
270         WorkingDirectory = "${cfg.ui.package}";
271         ExecStart =
272           "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
273       };
274     };
275   };
276
277 }