dev.fron.io nixrc / d0e9a16
containers(mastodon): use upstream Tony Olagbaiye a month ago
4 changed file(s) with 5 addition(s) and 614 deletion(s). Raw diff Collapse all Expand all
00 { config, pkgs, lib, usr, ... }:
11
22 let
3 cfg = config.services.mastodon;
3 cfg = config.containers.mastodon.config.services.mastodon;
44 securityLimits = config.environment.etc.limits;
55 hostAddress = "10.6.0.1";
66 localAddress = "10.6.0.2";
7
8 twitterCfg = with usr.secrets.mastodon.twitter; {
9 inherit key crt;
10 keyFile = pkgs.writeText "selfsigned.key" key;
11 crtFile = pkgs.writeText "selfsigned.crt" crt;
12 };
137 in {
148 services.postgresql.enable = true;
159 services.postgresql.ensureUsers = [
3327 { config, stdenv, ... }:
3428
3529 {
36 imports = [
37 ../modules/services/mastodon
38 ];
39
4030 nixpkgs.pkgs = pkgs;
4131 nixpkgs.config.allowUnfree = true;
4232
7161 services.mastodon.smtp = {
7262 createLocally = true;
7363 fromAddress = "mastodon@${usr.secrets.domains.srvc}";
64 user = "mastodon";
7465 };
7566 services.mastodon.configureNginx = true;
7667 services.mastodon.package = pkgs.mastodon;
8071 services.nginx = {
8172 enable = true;
8273 enableReload = true;
83 virtualHosts."${cfg.localDomain}" = {
74 virtualHosts."${usr.secrets.domains.srvc}" = {
8475 #enableACME = lib.mkForce false;
8576 serverAliases = [
8677 "u.${usr.secrets.domains.srvc}"
9889
9990 networking.firewall.enable = false;
10091 networking.nameservers = [ "62.210.16.6" "62.210.16.7" ];
101 networking.extraHosts = ''${localAddress} twitter.com'';
92 networking.extraHosts = ''${localAddress}'';
10293
10394 security.acme.acceptTerms = true;
10495 security.acme.email = "ssl@${usr.secrets.domains.home}";
105 security.pki.certificates = [ twitterCfg.crt ];
106
107 boot.enableContainers = true;
108 boot.kernel.sysctl = {
109 "net.ipv4.ip_forward" = "1";
110 };
111 containers.twitterpub =
112 {
113 autoStart = true;
114
115 config =
116 { config, stdenv, ... }:
117
118 {
119 nixpkgs.pkgs = pkgs;
120 nixpkgs.config.allowUnfree = true;
121
122 environment.systemPackages = with pkgs; [
123 twitterpub vim wget
124 ];
125
126 networking.firewall.enable = false;
127
128 systemd.services.twitterpub = {
129 serviceConfig = let
130 configToml = pkgs.writeText "twitterpub.toml" ''
131 Domain = "tw.${usr.secrets.domains.srvc}"
132 Listen = ":443"
133 TLS = true
134 CertFile = "${twitterCfg.crtFile}"
135 KeyFile = "${twitterCfg.keyFile}"
136 '';
137 in rec {
138 WorkingDirectory = "/var/lib/twitterpub";
139 ExecStartPre = pkgs.writeShellScript "setup-twitterpub" ''
140 mkdir -p ${WorkingDirectory}
141 ln -sf ${configToml} ${WorkingDirectory}/twitterpub.toml
142 ln -sf ${pkgs.twitterpub.src}/main.html ${WorkingDirectory}/main.html
143 '';
144 ExecStart = "${pkgs.twitterpub}/bin/twitterpub";
145 Restart = "always";
146 };
147 wantedBy = [ "default.target" ];
148 };
149 };
150 bindMounts = {
151 "/var/lib/twitterpub" = {
152 hostPath = "/var/lib/mastodon/twitterpub";
153 isReadOnly = false;
154 };
155 };
156 };
15796 };
15897 bindMounts = {
15998 "/var/lib/mastodon" = {
44 ./services/gitfs.nix
55 ./services/hydroxide/default.nix
66 ./services/ipfs-cluster/default.nix
7 ./services/mastodon/default.nix
87 ./security/mitigations.nix
98 ./system/machines.nix
109 ./networking/nftables.nix
+0
-548
modules/services/mastodon/default.nix less more
0 { config, lib, pkgs, domains, ... }:
1
2 let
3 cfg = config.services.mastodon;
4 # We only want to create a database if we're actually going to connect to it.
5 databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
6
7 env = {
8 RAILS_ENV = "production";
9 NODE_ENV = "production";
10
11 RAILS_LOG_LEVEL = "debug";
12
13 DB_USER = cfg.database.user;
14 SMTP_LOGIN = cfg.smtp.user;
15
16 REDIS_HOST = cfg.redis.host;
17 REDIS_PORT = toString(cfg.redis.port);
18 DB_HOST = cfg.database.host;
19 DB_PORT = toString(cfg.database.port);
20 DB_NAME = cfg.database.name;
21 LOCAL_DOMAIN = cfg.localDomain;
22 SMTP_SERVER = cfg.smtp.host;
23 SMTP_PORT = toString(cfg.smtp.port);
24 SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
25 PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
26 PAPERCLIP_ROOT_URL = "/system";
27 ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
28 ES_HOST = cfg.elasticsearch.host;
29 ES_PORT = toString(cfg.elasticsearch.port);
30 } // cfg.extraConfig;
31
32 envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
33 (lib.concatLists (lib.mapAttrsToList (name: value:
34 if value != null then [
35 "${name}=\"${toString value}\""
36 ] else []
37 ) env))));
38
39 mastodonEnv = pkgs.runCommand "mastodon-env" { preferLocalBuild = true; } ''
40 mkdir -p $out/bin
41 cat > $out/bin/mastodon-env << EOF
42 #!${pkgs.bash}/bin/bash
43 set -a
44 source "${envFile}"
45 source /var/lib/mastodon/.secrets_env
46 eval -- "\$@"
47 EOF
48 chmod +x $out/bin/mastodon-env
49 '';
50
51 in {
52
53 disabledModules = [
54 "services/web-apps/mastodon.nix"
55 ];
56
57 options = {
58 services.mastodon = {
59 enable = lib.mkEnableOption "Mastodon, a federated social network server";
60
61 configureNginx = lib.mkOption {
62 description = ''
63 Configure nginx as a reverse proxy for mastodon.
64 Note that this makes some assumptions on your setup, and sets settings that will
65 affect other virtualHosts running on your nginx instance, if any.
66 Alternatively you can configure a reverse-proxy of your choice to serve these paths:
67
68 <code>/ -> $(nix-instantiate --eval '&lt;nixpkgs&gt;' -A mastodon.outPath)/public</code>
69
70 <code>/ -> 127.0.0.1:{{ webPort }} </code>(If there was no file in the directory above.)
71
72 <code>/system/ -> /var/lib/mastodon/public-system/</code>
73
74 <code>/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}</code>
75
76 Make sure that websockets are forwarded properly. You might want to set up caching
77 of some requests. Take a look at mastodon's provided nginx configuration at
78 <code>https://github.com/tootsuite/mastodon/blob/master/dist/nginx.conf</code>.
79 '';
80 type = lib.types.bool;
81 default = false;
82 };
83
84 user = lib.mkOption {
85 description = ''
86 User under which mastodon runs. If it is set to "mastodon",
87 that user will be created, otherwise it should be set to the
88 name of a user created elsewhere. In both cases,
89 <package>mastodon</package> and a package containing only
90 the shell script <code>mastodon-env</code> will be added to
91 the user's package set. To run a command from
92 <package>mastodon</package> such as <code>tootctl</code>
93 with the environment configured by this module use
94 <code>mastodon-env</code>, as in:
95
96 <code>mastodon-env tootctl accounts create newuser --email newuser@example.com</code>
97 '';
98 type = lib.types.str;
99 default = "mastodon";
100 };
101
102 group = lib.mkOption {
103 description = ''
104 Group under which mastodon runs.
105 If it is set to "mastodon", a group will be created.
106 '';
107 type = lib.types.str;
108 default = "mastodon";
109 };
110
111 streamingPort = lib.mkOption {
112 description = "TCP port used by the mastodon-streaming service.";
113 type = lib.types.port;
114 default = 55000;
115 };
116
117 webPort = lib.mkOption {
118 description = "TCP port used by the mastodon-web service.";
119 type = lib.types.port;
120 default = 55001;
121 };
122
123 sidekiqPort = lib.mkOption {
124 description = "TCP port used by the mastodon-sidekiq service";
125 type = lib.types.port;
126 default = 55002;
127 };
128
129 vapidPublicKeyFile = lib.mkOption {
130 description = ''
131 Path to file containing the public key used for Web Push
132 Voluntary Application Server Identification. A new keypair can
133 be generated by running:
134
135 <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
136
137 If <option>mastodon.vapidPrivateKeyFile</option>does not
138 exist, it and this file will be created with a new keypair.
139 '';
140 default = "/var/lib/mastodon/secrets/vapid-public-key";
141 type = lib.types.str;
142 };
143
144 localDomain = lib.mkOption {
145 description = "The domain serving your Mastodon instance.";
146 default = domains.srvc;
147 type = lib.types.str;
148 };
149
150 secretKeyBaseFile = lib.mkOption {
151 description = ''
152 Path to file containing the secret key base.
153 A new secret key base can be generated by running:
154
155 <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
156
157 If this file does not exist, it will be created with a new secret key base.
158 '';
159 default = "/var/lib/mastodon/secrets/secret-key-base";
160 type = lib.types.str;
161 };
162
163 otpSecretFile = lib.mkOption {
164 description = ''
165 Path to file containing the OTP secret.
166 A new OTP secret can be generated by running:
167
168 <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake secret</code>
169
170 If this file does not exist, it will be created with a new OTP secret.
171 '';
172 default = "/var/lib/mastodon/secrets/otp-secret";
173 type = lib.types.str;
174 };
175
176 vapidPrivateKeyFile = lib.mkOption {
177 description = ''
178 Path to file containing the private key used for Web Push
179 Voluntary Application Server Identification. A new keypair can
180 be generated by running:
181
182 <code>nix build -f '&lt;nixpkgs&gt;' mastodon; cd result; bin/rake webpush:generate_keys</code>
183
184 If this file does not exist, it will be created with a new
185 private key.
186 '';
187 default = "/var/lib/mastodon/secrets/vapid-private-key";
188 type = lib.types.str;
189 };
190
191 redis = {
192 createLocally = lib.mkOption {
193 description = "Configure local Redis server for Mastodon.";
194 type = lib.types.bool;
195 default = true;
196 };
197
198 host = lib.mkOption {
199 description = "Redis host.";
200 type = lib.types.str;
201 default = "127.0.0.1";
202 };
203
204 port = lib.mkOption {
205 description = "Redis port.";
206 type = lib.types.port;
207 default = 6379;
208 };
209 };
210
211 database = {
212 createLocally = lib.mkOption {
213 description = "Configure local PostgreSQL database server for Mastodon.";
214 type = lib.types.bool;
215 default = true;
216 };
217
218 host = lib.mkOption {
219 type = lib.types.str;
220 default = "/run/postgresql";
221 example = "192.168.23.42";
222 description = "Database host address or unix socket.";
223 };
224
225 port = lib.mkOption {
226 type = lib.types.int;
227 default = 5432;
228 description = "Database host port.";
229 };
230
231 name = lib.mkOption {
232 type = lib.types.str;
233 default = "mastodon";
234 description = "Database name.";
235 };
236
237 user = lib.mkOption {
238 type = lib.types.str;
239 default = "mastodon";
240 description = "Database user.";
241 };
242
243 passwordFile = lib.mkOption {
244 type = lib.types.nullOr lib.types.path;
245 default = "/var/lib/mastodon/secrets/db-password";
246 example = "/run/keys/mastodon-db-password";
247 description = ''
248 A file containing the password corresponding to
249 <option>database.user</option>.
250 '';
251 };
252 };
253
254 smtp = {
255 createLocally = lib.mkOption {
256 description = "Configure local Postfix SMTP server for Mastodon.";
257 type = lib.types.bool;
258 default = true;
259 };
260
261 host = lib.mkOption {
262 description = "SMTP host used when sending emails to users.";
263 type = lib.types.str;
264 default = "127.0.0.1";
265 };
266
267 port = lib.mkOption {
268 description = "SMTP port used when sending emails to users.";
269 type = lib.types.port;
270 default = 25;
271 };
272
273 fromAddress = lib.mkOption {
274 description = ''"From" address used when sending Emails to users.'';
275 type = lib.types.str;
276 default = "mastodon@${domains.srvc}";
277 };
278
279 user = lib.mkOption {
280 description = "SMTP login name.";
281 type = lib.types.str;
282 default = "mastodon";
283 };
284
285 passwordFile = lib.mkOption {
286 description = ''
287 Path to file containing the SMTP password.
288 '';
289 default = "/var/lib/mastodon/secrets/smtp-password";
290 example = "/run/keys/mastodon-smtp-password";
291 type = lib.types.str;
292 };
293 };
294
295 elasticsearch = {
296 host = lib.mkOption {
297 description = ''
298 Elasticsearch host.
299 If it is not null, Elasticsearch full text search will be enabled.
300 '';
301 type = lib.types.nullOr lib.types.str;
302 default = "127.0.0.1";
303 };
304
305 port = lib.mkOption {
306 description = "Elasticsearch port.";
307 type = lib.types.port;
308 default = 9200;
309 };
310 };
311
312 extraConfig = lib.mkOption {
313 type = lib.types.attrs;
314 default = {};
315 description = ''
316 Extra environment variables to pass to all mastodon services.
317 '';
318 };
319
320 automaticMigrations = lib.mkOption {
321 type = lib.types.bool;
322 default = true;
323 description = ''
324 Do automatic database migrations.
325 '';
326 };
327
328 package = lib.mkOption {
329 type = lib.types.package;
330 default = null;
331 example = "pkgs.mastodon";
332 description = ''
333 Package to use.
334 '';
335 };
336 };
337 };
338
339 config = lib.mkIf cfg.enable {
340 assertions = [
341 {
342 assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
343 message = ''For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user and services.mastodon.database.user must be identical.'';
344 }
345 ];
346
347 environment.systemPackages = [ mastodonEnv ];
348
349 systemd.services.mastodon-init-dirs = {
350 script = ''
351 umask 077
352
353 if ! test -f ${cfg.secretKeyBaseFile}; then
354 mkdir -p $(dirname ${cfg.secretKeyBaseFile})
355 bin/rake secret > ${cfg.secretKeyBaseFile}
356 fi
357 if ! test -f ${cfg.otpSecretFile}; then
358 mkdir -p $(dirname ${cfg.otpSecretFile})
359 bin/rake secret > ${cfg.otpSecretFile}
360 fi
361 if ! test -f ${cfg.vapidPrivateKeyFile}; then
362 mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile})
363 keypair=$(bin/rake webpush:generate_keys)
364 echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile}
365 echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile}
366 fi
367
368 cat > /var/lib/mastodon/.secrets_env <<EOF
369 SECRET_KEY_BASE=$(cat ${cfg.secretKeyBaseFile})
370 OTP_SECRET=$(cat ${cfg.otpSecretFile})
371 VAPID_PRIVATE_KEY=$(cat ${cfg.vapidPrivateKeyFile})
372 VAPID_PUBLIC_KEY=$(cat ${cfg.vapidPublicKeyFile})
373 DB_PASS="$(cat ${cfg.database.passwordFile})"
374 SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
375 EOF
376 '';
377 serviceConfig = {
378 Type = "oneshot";
379 User = cfg.user;
380 Group = cfg.group;
381 WorkingDirectory = cfg.package;
382 LogsDirectory = "mastodon";
383 StateDirectory = "mastodon";
384 };
385 after = [ "network.target" ];
386 wantedBy = [ "multi-user.target" ];
387 };
388
389 systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
390 script = ''
391 if [ `psql mastodon -c \
392 "select count(*) from pg_class c \
393 join pg_namespace s on s.oid = c.relnamespace \
394 where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
395 and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
396 SAFETY_ASSURED=1 rake db:schema:load
397 rake db:seed
398 else
399 rake db:migrate
400 fi
401 '';
402 path = [ cfg.package pkgs.postgresql ];
403 environment = env;
404 serviceConfig = {
405 Type = "oneshot";
406 User = cfg.user;
407 Group = cfg.group;
408 EnvironmentFile = "/var/lib/mastodon/.secrets_env";
409 PrivateTmp = true;
410 LogsDirectory = "mastodon";
411 StateDirectory = "mastodon";
412 WorkingDirectory = cfg.package;
413 };
414 after = [ "mastodon-init-dirs.service" "network.target" ] ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else []);
415 wantedBy = [ "multi-user.target" ];
416 };
417
418 systemd.services.mastodon-streaming = {
419 after = [ "network.target" ]
420 ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
421 ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
422 description = "Mastodon streaming";
423 wantedBy = [ "multi-user.target" ];
424 environment = env // {
425 PORT = toString(cfg.streamingPort);
426 };
427 serviceConfig = {
428 ExecStart = "${pkgs.nodejs-slim}/bin/node streaming";
429 Restart = "always";
430 RestartSec = 20;
431 User = cfg.user;
432 Group = cfg.group;
433 WorkingDirectory = cfg.package;
434 EnvironmentFile = "/var/lib/mastodon/.secrets_env";
435 PrivateTmp = true;
436 LogsDirectory = "mastodon";
437 StateDirectory = "mastodon";
438 LimitNOFILE = "1048576";
439 };
440 };
441
442 systemd.services.mastodon-web = {
443 after = [ "network.target" ]
444 ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
445 ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
446 description = "Mastodon web";
447 wantedBy = [ "multi-user.target" ];
448 environment = env // {
449 PORT = toString(cfg.webPort);
450 };
451 serviceConfig = {
452 ExecStart = "${cfg.package}/bin/puma -C config/puma.rb";
453 Restart = "always";
454 RestartSec = 20;
455 User = cfg.user;
456 Group = cfg.group;
457 WorkingDirectory = cfg.package;
458 EnvironmentFile = "/var/lib/mastodon/.secrets_env";
459 PrivateTmp = true;
460 LogsDirectory = "mastodon";
461 StateDirectory = "mastodon";
462 LimitNOFILE = "1048576";
463 };
464 path = with pkgs; [ file imagemagick ffmpeg ];
465 };
466
467 systemd.services.mastodon-sidekiq = {
468 after = [ "network.target" ]
469 ++ (if databaseActuallyCreateLocally then [ "postgresql.service" ] else [])
470 ++ (if cfg.automaticMigrations then [ "mastodon-init-db.service" ] else [ "mastodon-init-dirs.service" ]);
471 description = "Mastodon sidekiq";
472 wantedBy = [ "multi-user.target" ];
473 environment = env // {
474 PORT = toString(cfg.sidekiqPort);
475 };
476 serviceConfig = {
477 ExecStart = "${cfg.package}/bin/sidekiq -c 25 -r ${cfg.package}";
478 Restart = "always";
479 RestartSec = 20;
480 User = cfg.user;
481 Group = cfg.group;
482 WorkingDirectory = cfg.package;
483 EnvironmentFile = "/var/lib/mastodon/.secrets_env";
484 PrivateTmp = true;
485 LogsDirectory = "mastodon";
486 StateDirectory = "mastodon";
487 LimitNOFILE = "1048576";
488 };
489 };
490
491 services.nginx = lib.mkIf cfg.configureNginx {
492 enable = true;
493 recommendedProxySettings = true; # required for redirections to work
494 virtualHosts."${cfg.localDomain}" = {
495 root = "${cfg.package}/public/";
496 forceSSL = true; # mastodon only supports https
497 enableACME = true;
498
499 locations."/system/".alias = "/var/lib/mastodon/public-system/";
500
501 locations."/" = {
502 tryFiles = "$uri @proxy";
503 };
504
505 locations."@proxy" = {
506 proxyPass = "http://127.0.0.1:${toString(cfg.webPort)}";
507 proxyWebsockets = true;
508 };
509
510 locations."/api/v1/streaming/" = {
511 proxyPass = "http://127.0.0.1:${toString(cfg.streamingPort)}/";
512 proxyWebsockets = true;
513 };
514 };
515 };
516
517 services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
518 enable = true;
519 };
520 services.redis = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") {
521 enable = true;
522 };
523 services.postgresql = lib.mkIf databaseActuallyCreateLocally {
524 enable = true;
525 ensureUsers = [
526 {
527 name = cfg.database.user;
528 ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
529 }
530 ];
531 ensureDatabases = [ cfg.database.name ];
532 };
533
534 users.users = lib.mkMerge [
535 (lib.mkIf (cfg.user == "mastodon") {
536 mastodon = {
537 isSystemUser = true;
538 inherit (cfg) group;
539 };
540 })
541 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv ])
542 ];
543
544 users.groups.mastodon = lib.mkIf (cfg.group == "mastodon") { };
545 };
546
547 }
271271 ];
272272 };
273273 mastodon.loadBalancer = {
274 passHostHeader = true;
274275 servers = [
275276 { url = "https://10.6.0.2:8443"; }
276277 ];