1{ lib, pkgs, config, ... }:
2with lib;
3let
4 cfg = config.services.lemmy;
5 settingsFormat = pkgs.formats.json { };
6in
7{
8 meta.maintainers = with maintainers; [ happysalada ];
9 # Don't edit the docbook xml directly, edit the md and generate it:
10 # `pandoc lemmy.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > lemmy.xml`
11 meta.doc = ./lemmy.xml;
12
13 imports = [
14 (mkRemovedOptionModule [ "services" "lemmy" "jwtSecretPath" ] "As of v0.13.0, Lemmy auto-generates the JWT secret.")
15 ];
16
17 options.services.lemmy = {
18
19 enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
20
21 ui = {
22 port = mkOption {
23 type = types.port;
24 default = 1234;
25 description = lib.mdDoc "Port where lemmy-ui should listen for incoming requests.";
26 };
27 };
28
29 caddy.enable = mkEnableOption (lib.mdDoc "exposing lemmy with the caddy reverse proxy");
30
31 database.createLocally = mkEnableOption (lib.mdDoc "creation of database on the instance");
32
33 settings = mkOption {
34 default = { };
35 description = lib.mdDoc "Lemmy configuration";
36
37 type = types.submodule {
38 freeformType = settingsFormat.type;
39
40 options.hostname = mkOption {
41 type = types.str;
42 default = null;
43 description = lib.mdDoc "The domain name of your instance (eg 'lemmy.ml').";
44 };
45
46 options.port = mkOption {
47 type = types.port;
48 default = 8536;
49 description = lib.mdDoc "Port where lemmy should listen for incoming requests.";
50 };
51
52 options.federation = {
53 enabled = mkEnableOption (lib.mdDoc "activitypub federation");
54 };
55
56 options.captcha = {
57 enabled = mkOption {
58 type = types.bool;
59 default = true;
60 description = lib.mdDoc "Enable Captcha.";
61 };
62 difficulty = mkOption {
63 type = types.enum [ "easy" "medium" "hard" ];
64 default = "medium";
65 description = lib.mdDoc "The difficultly of the captcha to solve.";
66 };
67 };
68 };
69 };
70
71 };
72
73 config =
74 lib.mkIf cfg.enable {
75 services.lemmy.settings = (mapAttrs (name: mkDefault)
76 {
77 bind = "127.0.0.1";
78 tls_enabled = true;
79 pictrs_url = with config.services.pict-rs; "http://${address}:${toString port}";
80 actor_name_max_length = 20;
81
82 rate_limit.message = 180;
83 rate_limit.message_per_second = 60;
84 rate_limit.post = 6;
85 rate_limit.post_per_second = 600;
86 rate_limit.register = 3;
87 rate_limit.register_per_second = 3600;
88 rate_limit.image = 6;
89 rate_limit.image_per_second = 3600;
90 } // {
91 database = mapAttrs (name: mkDefault) {
92 user = "lemmy";
93 host = "/run/postgresql";
94 port = 5432;
95 database = "lemmy";
96 pool_size = 5;
97 };
98 });
99
100 services.postgresql = mkIf cfg.database.createLocally {
101 enable = true;
102 ensureDatabases = [ cfg.settings.database.database ];
103 ensureUsers = [{
104 name = cfg.settings.database.user;
105 ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
106 }];
107 };
108
109 services.pict-rs.enable = true;
110
111 services.caddy = mkIf cfg.caddy.enable {
112 enable = mkDefault true;
113 virtualHosts."${cfg.settings.hostname}" = {
114 extraConfig = ''
115 handle_path /static/* {
116 root * ${pkgs.lemmy-ui}/dist
117 file_server
118 }
119 @for_backend {
120 path /api/* /pictrs/* /feeds/* /nodeinfo/*
121 }
122 handle @for_backend {
123 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
124 }
125 @post {
126 method POST
127 }
128 handle @post {
129 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
130 }
131 @jsonld {
132 header Accept "application/activity+json"
133 header Accept "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
134 }
135 handle @jsonld {
136 reverse_proxy 127.0.0.1:${toString cfg.settings.port}
137 }
138 handle {
139 reverse_proxy 127.0.0.1:${toString cfg.ui.port}
140 }
141 '';
142 };
143 };
144
145 assertions = [{
146 assertion = cfg.database.createLocally -> cfg.settings.database.host == "localhost" || cfg.settings.database.host == "/run/postgresql";
147 message = "if you want to create the database locally, you need to use a local database";
148 }];
149
150 systemd.services.lemmy = {
151 description = "Lemmy server";
152
153 environment = {
154 LEMMY_CONFIG_LOCATION = "/run/lemmy/config.hjson";
155
156 # Verify how this is used, and don't put the password in the nix store
157 LEMMY_DATABASE_URL = with cfg.settings.database;"postgres:///${database}?host=${host}";
158 };
159
160 documentation = [
161 "https://join-lemmy.org/docs/en/administration/from_scratch.html"
162 "https://join-lemmy.org/docs"
163 ];
164
165 wantedBy = [ "multi-user.target" ];
166
167 after = [ "pict-rs.service" ] ++ lib.optionals cfg.database.createLocally [ "postgresql.service" ];
168
169 requires = lib.optionals cfg.database.createLocally [ "postgresql.service" ];
170
171 serviceConfig = {
172 DynamicUser = true;
173 RuntimeDirectory = "lemmy";
174 ExecStartPre = "${pkgs.coreutils}/bin/install -m 600 ${settingsFormat.generate "config.hjson" cfg.settings} /run/lemmy/config.hjson";
175 ExecStart = "${pkgs.lemmy-server}/bin/lemmy_server";
176 };
177 };
178
179 systemd.services.lemmy-ui = {
180 description = "Lemmy ui";
181
182 environment = {
183 LEMMY_UI_HOST = "127.0.0.1:${toString cfg.ui.port}";
184 LEMMY_INTERNAL_HOST = "127.0.0.1:${toString cfg.settings.port}";
185 LEMMY_EXTERNAL_HOST = cfg.settings.hostname;
186 LEMMY_HTTPS = "false";
187 };
188
189 documentation = [
190 "https://join-lemmy.org/docs/en/administration/from_scratch.html"
191 "https://join-lemmy.org/docs"
192 ];
193
194 wantedBy = [ "multi-user.target" ];
195
196 after = [ "lemmy.service" ];
197
198 requires = [ "lemmy.service" ];
199
200 serviceConfig = {
201 DynamicUser = true;
202 WorkingDirectory = "${pkgs.lemmy-ui}";
203 ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.lemmy-ui}/dist/js/server.js";
204 };
205 };
206 };
207
208}