]> Untitled Git - awful.systems.git/blob - lemmy/staging/module.nix
87a90630ee8057db6811dd22312973302ad221e7
[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 = mkIf cfg.nginx.enable {
158       enable = mkDefault true;
159       virtualHosts."${cfg.settings.hostname}".locations = let
160         ui = "http://127.0.0.1:${toString cfg.ui.port}";
161         backend = "http://127.0.0.1:${toString cfg.settings.port}";
162       in {
163         "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
164           # backend requests
165           proxyPass = backend;
166           proxyWebsockets = true;
167           recommendedProxySettings = true;
168         };
169         "/" = {
170           # mixed frontend and backend requests, based on the request headers
171           proxyPass = "$proxpass";
172           recommendedProxySettings = true;
173           extraConfig = ''
174             set $proxpass "${ui}";
175             if ($http_accept = "application/activity+json") {
176               set $proxpass "${backend}";
177             }
178             if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
179               set $proxpass "${backend}";
180             }
181             if ($request_method = POST) {
182               set $proxpass "${backend}";
183             }
184
185             # Cuts off the trailing slash on URLs to make them valid
186             rewrite ^(.+)/+$ $1 permanent;
187           '';
188         };
189       };
190     };
191
192     assertions = [
193       {
194         assertion = cfg.database.createLocally -> cfg.settings.database.host
195           == "localhost" || cfg.settings.database.host == "/run/postgresql";
196         message =
197           "if you want to create the database locally, you need to use a local database";
198       }
199       {
200         assertion = (!(hasAttrByPath [ "federation" ] cfg.settings))
201           && (!(hasAttrByPath [ "federation" "enabled" ] cfg.settings));
202         message =
203           "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
204       }
205     ];
206
207     systemd.services.lemmy-staging = {
208       description = "Lemmy server (staging)";
209
210       environment = {
211         LEMMY_CONFIG_LOCATION =
212           "${settingsFormat.generate "config.hjson" cfg.settings}";
213         LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
214       };
215
216       documentation = [
217         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
218         "https://join-lemmy.org/docs/en/"
219       ];
220
221       wantedBy = [ "multi-user.target" ];
222
223       after = [ "pict-rs.service" ]
224         ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
225
226       requires =
227         lib.optionals cfg.database.createLocally [ "postgresql.service" ];
228
229       serviceConfig = {
230         DynamicUser = true;
231         RuntimeDirectory = "lemmy";
232         ExecStart = "${cfg.server.package}/bin/lemmy_server";
233       };
234     };
235
236     systemd.services.lemmy-ui-staging = {
237       description = "Lemmy UI (staging)";
238
239       environment = {
240         LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
241         LEMMY_UI_LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
242         LEMMY_UI_LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
243         LEMMY_UI_HTTPS = "false";
244       };
245
246       documentation = [
247         "https://join-lemmy.org/docs/en/admins/from_scratch.html"
248         "https://join-lemmy.org/docs/en/"
249       ];
250
251       wantedBy = [ "multi-user.target" ];
252
253       after = [ "lemmy-staging.service" ];
254
255       requires = [ "lemmy-staging.service" ];
256
257       serviceConfig = {
258         DynamicUser = true;
259         WorkingDirectory = "${cfg.ui.package}";
260         ExecStart =
261           "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";
262       };
263     };
264   };
265
266 }