1 { lib, pkgs, config, ... }:
4 cfg = config.services.lemmy-prod;
5 settingsFormat = pkgs.formats.json { };
9 (mkRemovedOptionModule [ "services" "lemmy-prod" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
12 options.services.lemmy-prod = {
14 enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
17 package = mkPackageOptionMD pkgs "lemmy-server" {};
21 package = mkPackageOptionMD pkgs "lemmy-ui" {};
26 description = lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
30 caddy.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
31 nginx.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the nginx reverse proxy");
34 createLocally = mkEnableOption (lib.mdDoc "creation of database on the instance");
37 type = with types; nullOr str;
39 description = lib.mdDoc "The connection URI to use. Takes priority over the configuration file if set.";
45 description = lib.mdDoc "Lemmy configuration";
47 type = types.submodule {
48 freeformType = settingsFormat.type;
50 options.hostname = mkOption {
53 description = lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
56 options.port = mkOption {
59 description = lib.mdDoc "Port where lemmy should listen for incoming requests.";
66 description = lib.mdDoc "Enable Captcha.";
68 difficulty = mkOption {
69 type = types.enum [ "easy" "medium" "hard" ];
71 description = lib.mdDoc "The difficultly of the captcha to solve.";
81 services.lemmy-prod.settings = (mapAttrs (name: mkDefault)
85 pictrs_url = with config.services.pict-rs; "http://${address}:${toString port}";
86 actor_name_max_length = 20;
88 rate_limit.message = 180;
89 rate_limit.message_per_second = 60;
91 rate_limit.post_per_second = 600;
92 rate_limit.register = 3;
93 rate_limit.register_per_second = 3600;
95 rate_limit.image_per_second = 3600;
97 database = mapAttrs (name: mkDefault) {
99 host = "/run/postgresql";
106 services.postgresql = mkIf cfg.database.createLocally {
108 ensureDatabases = [ cfg.settings.database.database ];
110 name = cfg.settings.database.user;
111 ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
115 services.pict-rs.enable = true;
117 services.caddy = mkIf cfg.caddy.enable {
118 enable = mkDefault true;
119 virtualHosts."${cfg.settings.hostname}" = {
121 handle_path /static/* {
122 root * ${cfg.ui.package}/dist
126 path /api/* /pictrs/* /feeds/* /nodeinfo/*
128 handle @for_backend {
129 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
135 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
138 header Accept "application/activity+json"
139 header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
142 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
145 reverse_proxy 127.0.0.1:${toString cfg.ui.port}
151 services.nginx = mkIf cfg.nginx.enable {
152 enable = mkDefault true;
153 virtualHosts."${cfg.settings.hostname}".locations = let
154 ui = "http://127.0.0.1:${toString cfg.ui.port}";
155 backend = "http://127.0.0.1:${toString cfg.settings.port}";
157 "~ ^/(api|pictrs|feeds|nodeinfo|.well-known)" = {
160 proxyWebsockets = true;
161 recommendedProxySettings = true;
164 # mixed frontend and backend requests, based on the request headers
165 proxyPass = "$proxpass";
166 recommendedProxySettings = true;
168 set $proxpass "${ui}";
169 if ($http_accept = "application/activity+json") {
170 set $proxpass "${backend}";
172 if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") {
173 set $proxpass "${backend}";
175 if ($request_method = POST) {
176 set $proxpass "${backend}";
179 # Cuts off the trailing slash on URLs to make them valid
180 rewrite ^(.+)/+$ $1 permanent;
188 assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql";
189 message = "if you want to create the database locally, you need to use a local database";
192 assertion = (!(hasAttrByPath ["federation"] cfg.settings)) && (!(hasAttrByPath ["federation" "enabled"] cfg.settings));
193 message = "`services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect";
197 systemd.services.lemmy-prod = {
198 description = "Lemmy server (production)";
201 LEMMY_CONFIG_LOCATION = "${settingsFormat.generate "config.hjson" cfg.settings}";
202 LEMMY_DATABASE_URL = mkIf (cfg.database.uri != null) cfg.database.uri;
206 "https://join-lemmy.org/docs/en/admins/from_scratch.html"
207 "https://join-lemmy.org/docs/en/"
210 wantedBy = [ "multi-user.target" ];
212 after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
214 requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
218 RuntimeDirectory = "lemmy";
219 ExecStart = "${cfg.server.package}/bin/lemmy_server";
223 systemd.services.lemmy-ui-prod = {
224 description = "Lemmy UI (production)";
227 LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
228 LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
229 LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
230 LEMMY_HTTPS = "false";
231 NODE_ENV = "production";
235 "https://join-lemmy.org/docs/en/admins/from_scratch.html"
236 "https://join-lemmy.org/docs/en/"
239 wantedBy = [ "multi-user.target" ];
241 after = [ "lemmy-prod.service" ];
243 requires = [ "lemmy-prod.service" ];
247 WorkingDirectory = "${cfg.ui.package}";
248 ExecStart = "${pkgs.nodejs}/bin/node ${cfg.ui.package}/dist/js/server.js";