at 23.05-pre 25 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 cfg = config.services.mastodon; 5 # We only want to create a database if we're actually going to connect to it. 6 databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql"; 7 8 env = { 9 RAILS_ENV = "production"; 10 NODE_ENV = "production"; 11 12 LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so"; 13 14 # mastodon-web concurrency. 15 WEB_CONCURRENCY = toString cfg.webProcesses; 16 MAX_THREADS = toString cfg.webThreads; 17 18 # mastodon-streaming concurrency. 19 STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses; 20 21 DB_USER = cfg.database.user; 22 23 REDIS_HOST = cfg.redis.host; 24 REDIS_PORT = toString(cfg.redis.port); 25 DB_HOST = cfg.database.host; 26 DB_PORT = toString(cfg.database.port); 27 DB_NAME = cfg.database.name; 28 LOCAL_DOMAIN = cfg.localDomain; 29 SMTP_SERVER = cfg.smtp.host; 30 SMTP_PORT = toString(cfg.smtp.port); 31 SMTP_FROM_ADDRESS = cfg.smtp.fromAddress; 32 PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system"; 33 PAPERCLIP_ROOT_URL = "/system"; 34 ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false"; 35 ES_HOST = cfg.elasticsearch.host; 36 ES_PORT = toString(cfg.elasticsearch.port); 37 38 TRUSTED_PROXY_IP = cfg.trustedProxy; 39 } 40 // (if cfg.smtp.authenticate then { SMTP_LOGIN = cfg.smtp.user; } else {}) 41 // cfg.extraConfig; 42 43 systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ]; 44 45 cfgService = { 46 # User and group 47 User = cfg.user; 48 Group = cfg.group; 49 # State directory and mode 50 StateDirectory = "mastodon"; 51 StateDirectoryMode = "0750"; 52 # Logs directory and mode 53 LogsDirectory = "mastodon"; 54 LogsDirectoryMode = "0750"; 55 # Proc filesystem 56 ProcSubset = "pid"; 57 ProtectProc = "invisible"; 58 # Access write directories 59 UMask = "0027"; 60 # Capabilities 61 CapabilityBoundingSet = ""; 62 # Security 63 NoNewPrivileges = true; 64 # Sandboxing 65 ProtectSystem = "strict"; 66 ProtectHome = true; 67 PrivateTmp = true; 68 PrivateDevices = true; 69 PrivateUsers = true; 70 ProtectClock = true; 71 ProtectHostname = true; 72 ProtectKernelLogs = true; 73 ProtectKernelModules = true; 74 ProtectKernelTunables = true; 75 ProtectControlGroups = true; 76 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; 77 RestrictNamespaces = true; 78 LockPersonality = true; 79 MemoryDenyWriteExecute = false; 80 RestrictRealtime = true; 81 RestrictSUIDSGID = true; 82 RemoveIPC = true; 83 PrivateMounts = true; 84 # System Call Filtering 85 SystemCallArchitectures = "native"; 86 }; 87 88 envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") ( 89 (lib.concatLists (lib.mapAttrsToList (name: value: 90 if value != null then [ 91 "${name}=\"${toString value}\"" 92 ] else [] 93 ) env)))); 94 95 mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" '' 96 set -a 97 export RAILS_ROOT="${cfg.package}" 98 source "${envFile}" 99 source /var/lib/mastodon/.secrets_env 100 eval -- "\$@" 101 ''; 102 103in { 104 105 options = { 106 services.mastodon = { 107 enable = lib.mkEnableOption (lib.mdDoc "Mastodon, a federated social network server"); 108 109 configureNginx = lib.mkOption { 110 description = lib.mdDoc '' 111 Configure nginx as a reverse proxy for mastodon. 112 Note that this makes some assumptions on your setup, and sets settings that will 113 affect other virtualHosts running on your nginx instance, if any. 114 Alternatively you can configure a reverse-proxy of your choice to serve these paths: 115 116 `/ -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public` 117 118 `/ -> 127.0.0.1:{{ webPort }} `(If there was no file in the directory above.) 119 120 `/system/ -> /var/lib/mastodon/public-system/` 121 122 `/api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}` 123 124 Make sure that websockets are forwarded properly. You might want to set up caching 125 of some requests. Take a look at mastodon's provided nginx configuration at 126 `https://github.com/mastodon/mastodon/blob/master/dist/nginx.conf`. 127 ''; 128 type = lib.types.bool; 129 default = false; 130 }; 131 132 user = lib.mkOption { 133 description = lib.mdDoc '' 134 User under which mastodon runs. If it is set to "mastodon", 135 that user will be created, otherwise it should be set to the 136 name of a user created elsewhere. In both cases, 137 `mastodon` and a package containing only 138 the shell script `mastodon-env` will be added to 139 the user's package set. To run a command from 140 `mastodon` such as `tootctl` 141 with the environment configured by this module use 142 `mastodon-env`, as in: 143 144 `mastodon-env tootctl accounts create newuser --email newuser@example.com` 145 ''; 146 type = lib.types.str; 147 default = "mastodon"; 148 }; 149 150 group = lib.mkOption { 151 description = lib.mdDoc '' 152 Group under which mastodon runs. 153 ''; 154 type = lib.types.str; 155 default = "mastodon"; 156 }; 157 158 streamingPort = lib.mkOption { 159 description = lib.mdDoc "TCP port used by the mastodon-streaming service."; 160 type = lib.types.port; 161 default = 55000; 162 }; 163 streamingProcesses = lib.mkOption { 164 description = lib.mdDoc '' 165 Processes used by the mastodon-streaming service. 166 Defaults to the number of CPU cores minus one. 167 ''; 168 type = lib.types.nullOr lib.types.int; 169 default = null; 170 }; 171 172 webPort = lib.mkOption { 173 description = lib.mdDoc "TCP port used by the mastodon-web service."; 174 type = lib.types.port; 175 default = 55001; 176 }; 177 webProcesses = lib.mkOption { 178 description = lib.mdDoc "Processes used by the mastodon-web service."; 179 type = lib.types.int; 180 default = 2; 181 }; 182 webThreads = lib.mkOption { 183 description = lib.mdDoc "Threads per process used by the mastodon-web service."; 184 type = lib.types.int; 185 default = 5; 186 }; 187 188 sidekiqPort = lib.mkOption { 189 description = lib.mdDoc "TCP port used by the mastodon-sidekiq service."; 190 type = lib.types.port; 191 default = 55002; 192 }; 193 sidekiqThreads = lib.mkOption { 194 description = lib.mdDoc "Worker threads used by the mastodon-sidekiq service."; 195 type = lib.types.int; 196 default = 25; 197 }; 198 199 vapidPublicKeyFile = lib.mkOption { 200 description = lib.mdDoc '' 201 Path to file containing the public key used for Web Push 202 Voluntary Application Server Identification. A new keypair can 203 be generated by running: 204 205 `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys` 206 207 If {option}`mastodon.vapidPrivateKeyFile`does not 208 exist, it and this file will be created with a new keypair. 209 ''; 210 default = "/var/lib/mastodon/secrets/vapid-public-key"; 211 type = lib.types.str; 212 }; 213 214 localDomain = lib.mkOption { 215 description = lib.mdDoc "The domain serving your Mastodon instance."; 216 example = "social.example.org"; 217 type = lib.types.str; 218 }; 219 220 secretKeyBaseFile = lib.mkOption { 221 description = lib.mdDoc '' 222 Path to file containing the secret key base. 223 A new secret key base can be generated by running: 224 225 `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret` 226 227 If this file does not exist, it will be created with a new secret key base. 228 ''; 229 default = "/var/lib/mastodon/secrets/secret-key-base"; 230 type = lib.types.str; 231 }; 232 233 otpSecretFile = lib.mkOption { 234 description = lib.mdDoc '' 235 Path to file containing the OTP secret. 236 A new OTP secret can be generated by running: 237 238 `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret` 239 240 If this file does not exist, it will be created with a new OTP secret. 241 ''; 242 default = "/var/lib/mastodon/secrets/otp-secret"; 243 type = lib.types.str; 244 }; 245 246 vapidPrivateKeyFile = lib.mkOption { 247 description = lib.mdDoc '' 248 Path to file containing the private key used for Web Push 249 Voluntary Application Server Identification. A new keypair can 250 be generated by running: 251 252 `nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys` 253 254 If this file does not exist, it will be created with a new 255 private key. 256 ''; 257 default = "/var/lib/mastodon/secrets/vapid-private-key"; 258 type = lib.types.str; 259 }; 260 261 trustedProxy = lib.mkOption { 262 description = lib.mdDoc '' 263 You need to set it to the IP from which your reverse proxy sends requests to Mastodon's web process, 264 otherwise Mastodon will record the reverse proxy's own IP as the IP of all requests, which would be 265 bad because IP addresses are used for important rate limits and security functions. 266 ''; 267 type = lib.types.str; 268 default = "127.0.0.1"; 269 }; 270 271 enableUnixSocket = lib.mkOption { 272 description = lib.mdDoc '' 273 Instead of binding to an IP address like 127.0.0.1, you may bind to a Unix socket. This variable 274 is process-specific, e.g. you need different values for every process, and it works for both web (Puma) 275 processes and streaming API (Node.js) processes. 276 ''; 277 type = lib.types.bool; 278 default = true; 279 }; 280 281 redis = { 282 createLocally = lib.mkOption { 283 description = lib.mdDoc "Configure local Redis server for Mastodon."; 284 type = lib.types.bool; 285 default = true; 286 }; 287 288 host = lib.mkOption { 289 description = lib.mdDoc "Redis host."; 290 type = lib.types.str; 291 default = "127.0.0.1"; 292 }; 293 294 port = lib.mkOption { 295 description = lib.mdDoc "Redis port."; 296 type = lib.types.port; 297 default = 31637; 298 }; 299 }; 300 301 database = { 302 createLocally = lib.mkOption { 303 description = lib.mdDoc "Configure local PostgreSQL database server for Mastodon."; 304 type = lib.types.bool; 305 default = true; 306 }; 307 308 host = lib.mkOption { 309 type = lib.types.str; 310 default = "/run/postgresql"; 311 example = "192.168.23.42"; 312 description = lib.mdDoc "Database host address or unix socket."; 313 }; 314 315 port = lib.mkOption { 316 type = lib.types.int; 317 default = 5432; 318 description = lib.mdDoc "Database host port."; 319 }; 320 321 name = lib.mkOption { 322 type = lib.types.str; 323 default = "mastodon"; 324 description = lib.mdDoc "Database name."; 325 }; 326 327 user = lib.mkOption { 328 type = lib.types.str; 329 default = "mastodon"; 330 description = lib.mdDoc "Database user."; 331 }; 332 333 passwordFile = lib.mkOption { 334 type = lib.types.nullOr lib.types.path; 335 default = "/var/lib/mastodon/secrets/db-password"; 336 example = "/run/keys/mastodon-db-password"; 337 description = lib.mdDoc '' 338 A file containing the password corresponding to 339 {option}`database.user`. 340 ''; 341 }; 342 }; 343 344 smtp = { 345 createLocally = lib.mkOption { 346 description = lib.mdDoc "Configure local Postfix SMTP server for Mastodon."; 347 type = lib.types.bool; 348 default = true; 349 }; 350 351 authenticate = lib.mkOption { 352 description = lib.mdDoc "Authenticate with the SMTP server using username and password."; 353 type = lib.types.bool; 354 default = false; 355 }; 356 357 host = lib.mkOption { 358 description = lib.mdDoc "SMTP host used when sending emails to users."; 359 type = lib.types.str; 360 default = "127.0.0.1"; 361 }; 362 363 port = lib.mkOption { 364 description = lib.mdDoc "SMTP port used when sending emails to users."; 365 type = lib.types.port; 366 default = 25; 367 }; 368 369 fromAddress = lib.mkOption { 370 description = lib.mdDoc ''"From" address used when sending Emails to users.''; 371 type = lib.types.str; 372 }; 373 374 user = lib.mkOption { 375 description = lib.mdDoc "SMTP login name."; 376 type = lib.types.str; 377 }; 378 379 passwordFile = lib.mkOption { 380 description = lib.mdDoc '' 381 Path to file containing the SMTP password. 382 ''; 383 default = "/var/lib/mastodon/secrets/smtp-password"; 384 example = "/run/keys/mastodon-smtp-password"; 385 type = lib.types.str; 386 }; 387 }; 388 389 elasticsearch = { 390 host = lib.mkOption { 391 description = lib.mdDoc '' 392 Elasticsearch host. 393 If it is not null, Elasticsearch full text search will be enabled. 394 ''; 395 type = lib.types.nullOr lib.types.str; 396 default = null; 397 }; 398 399 port = lib.mkOption { 400 description = lib.mdDoc "Elasticsearch port."; 401 type = lib.types.port; 402 default = 9200; 403 }; 404 }; 405 406 package = lib.mkOption { 407 type = lib.types.package; 408 default = pkgs.mastodon; 409 defaultText = lib.literalExpression "pkgs.mastodon"; 410 description = lib.mdDoc "Mastodon package to use."; 411 }; 412 413 extraConfig = lib.mkOption { 414 type = lib.types.attrs; 415 default = {}; 416 description = lib.mdDoc '' 417 Extra environment variables to pass to all mastodon services. 418 ''; 419 }; 420 421 automaticMigrations = lib.mkOption { 422 type = lib.types.bool; 423 default = true; 424 description = lib.mdDoc '' 425 Do automatic database migrations. 426 ''; 427 }; 428 429 mediaAutoRemove = { 430 enable = lib.mkOption { 431 type = lib.types.bool; 432 default = true; 433 example = false; 434 description = lib.mdDoc '' 435 Automatically remove remote media attachments and preview cards older than the configured amount of days. 436 437 Recommended in https://docs.joinmastodon.org/admin/setup/. 438 ''; 439 }; 440 441 startAt = lib.mkOption { 442 type = lib.types.str; 443 default = "daily"; 444 example = "hourly"; 445 description = lib.mdDoc '' 446 How often to remove remote media. 447 448 The format is described in {manpage}`systemd.time(7)`. 449 ''; 450 }; 451 452 olderThanDays = lib.mkOption { 453 type = lib.types.int; 454 default = 30; 455 example = 14; 456 description = lib.mdDoc '' 457 How old remote media needs to be in order to be removed. 458 ''; 459 }; 460 }; 461 }; 462 }; 463 464 config = lib.mkIf cfg.enable { 465 assertions = [ 466 { 467 assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user); 468 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.''; 469 } 470 ]; 471 472 systemd.services.mastodon-init-dirs = { 473 script = '' 474 umask 077 475 476 if ! test -f ${cfg.secretKeyBaseFile}; then 477 mkdir -p $(dirname ${cfg.secretKeyBaseFile}) 478 bin/rake secret > ${cfg.secretKeyBaseFile} 479 fi 480 if ! test -f ${cfg.otpSecretFile}; then 481 mkdir -p $(dirname ${cfg.otpSecretFile}) 482 bin/rake secret > ${cfg.otpSecretFile} 483 fi 484 if ! test -f ${cfg.vapidPrivateKeyFile}; then 485 mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile}) 486 keypair=$(bin/rake webpush:generate_keys) 487 echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile} 488 echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile} 489 fi 490 491 cat > /var/lib/mastodon/.secrets_env <<EOF 492 SECRET_KEY_BASE="$(cat ${cfg.secretKeyBaseFile})" 493 OTP_SECRET="$(cat ${cfg.otpSecretFile})" 494 VAPID_PRIVATE_KEY="$(cat ${cfg.vapidPrivateKeyFile})" 495 VAPID_PUBLIC_KEY="$(cat ${cfg.vapidPublicKeyFile})" 496 DB_PASS="$(cat ${cfg.database.passwordFile})" 497 '' + (if cfg.smtp.authenticate then '' 498 SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})" 499 '' else "") + '' 500 EOF 501 ''; 502 environment = env; 503 serviceConfig = { 504 Type = "oneshot"; 505 WorkingDirectory = cfg.package; 506 # System Call Filtering 507 SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ]; 508 } // cfgService; 509 510 after = [ "network.target" ]; 511 }; 512 513 systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations { 514 script = '' 515 if [ `psql ${cfg.database.name} -c \ 516 "select count(*) from pg_class c \ 517 join pg_namespace s on s.oid = c.relnamespace \ 518 where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \ 519 and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then 520 SAFETY_ASSURED=1 rails db:schema:load 521 rails db:seed 522 else 523 rails db:migrate 524 fi 525 ''; 526 path = [ cfg.package pkgs.postgresql ]; 527 environment = env; 528 serviceConfig = { 529 Type = "oneshot"; 530 EnvironmentFile = "/var/lib/mastodon/.secrets_env"; 531 WorkingDirectory = cfg.package; 532 # System Call Filtering 533 SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ])) "@chown" "pipe" "pipe2" ]; 534 } // cfgService; 535 after = [ "network.target" "mastodon-init-dirs.service" ] 536 ++ lib.optional databaseActuallyCreateLocally "postgresql.service"; 537 requires = [ "mastodon-init-dirs.service" ] 538 ++ lib.optional databaseActuallyCreateLocally "postgresql.service"; 539 }; 540 541 systemd.services.mastodon-streaming = { 542 after = [ "network.target" "mastodon-init-dirs.service" ] 543 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 544 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 545 requires = [ "mastodon-init-dirs.service" ] 546 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 547 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 548 wantedBy = [ "multi-user.target" ]; 549 description = "Mastodon streaming"; 550 environment = env // (if cfg.enableUnixSocket 551 then { SOCKET = "/run/mastodon-streaming/streaming.socket"; } 552 else { PORT = toString(cfg.streamingPort); } 553 ); 554 serviceConfig = { 555 ExecStart = "${cfg.package}/run-streaming.sh"; 556 Restart = "always"; 557 RestartSec = 20; 558 EnvironmentFile = "/var/lib/mastodon/.secrets_env"; 559 WorkingDirectory = cfg.package; 560 # Runtime directory and mode 561 RuntimeDirectory = "mastodon-streaming"; 562 RuntimeDirectoryMode = "0750"; 563 # System Call Filtering 564 SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ]; 565 } // cfgService; 566 }; 567 568 systemd.services.mastodon-web = { 569 after = [ "network.target" "mastodon-init-dirs.service" ] 570 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 571 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 572 requires = [ "mastodon-init-dirs.service" ] 573 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 574 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 575 wantedBy = [ "multi-user.target" ]; 576 description = "Mastodon web"; 577 environment = env // (if cfg.enableUnixSocket 578 then { SOCKET = "/run/mastodon-web/web.socket"; } 579 else { PORT = toString(cfg.webPort); } 580 ); 581 serviceConfig = { 582 ExecStart = "${cfg.package}/bin/puma -C config/puma.rb"; 583 Restart = "always"; 584 RestartSec = 20; 585 EnvironmentFile = "/var/lib/mastodon/.secrets_env"; 586 WorkingDirectory = cfg.package; 587 # Runtime directory and mode 588 RuntimeDirectory = "mastodon-web"; 589 RuntimeDirectoryMode = "0750"; 590 # System Call Filtering 591 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ]; 592 } // cfgService; 593 path = with pkgs; [ file imagemagick ffmpeg ]; 594 }; 595 596 systemd.services.mastodon-sidekiq = { 597 after = [ "network.target" "mastodon-init-dirs.service" ] 598 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 599 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 600 requires = [ "mastodon-init-dirs.service" ] 601 ++ lib.optional databaseActuallyCreateLocally "postgresql.service" 602 ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service"; 603 wantedBy = [ "multi-user.target" ]; 604 description = "Mastodon sidekiq"; 605 environment = env // { 606 PORT = toString(cfg.sidekiqPort); 607 DB_POOL = toString cfg.sidekiqThreads; 608 }; 609 serviceConfig = { 610 ExecStart = "${cfg.package}/bin/sidekiq -c ${toString cfg.sidekiqThreads} -r ${cfg.package}"; 611 Restart = "always"; 612 RestartSec = 20; 613 EnvironmentFile = "/var/lib/mastodon/.secrets_env"; 614 WorkingDirectory = cfg.package; 615 # System Call Filtering 616 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ]; 617 } // cfgService; 618 path = with pkgs; [ file imagemagick ffmpeg ]; 619 }; 620 621 systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable { 622 description = "Mastodon media auto remove"; 623 environment = env; 624 serviceConfig = { 625 Type = "oneshot"; 626 EnvironmentFile = "/var/lib/mastodon/.secrets_env"; 627 } // cfgService; 628 script = let 629 olderThanDays = toString cfg.mediaAutoRemove.olderThanDays; 630 in '' 631 ${cfg.package}/bin/tootctl media remove --days=${olderThanDays} 632 ${cfg.package}/bin/tootctl preview_cards remove --days=${olderThanDays} 633 ''; 634 startAt = cfg.mediaAutoRemove.startAt; 635 }; 636 637 services.nginx = lib.mkIf cfg.configureNginx { 638 enable = true; 639 recommendedProxySettings = true; # required for redirections to work 640 virtualHosts."${cfg.localDomain}" = { 641 root = "${cfg.package}/public/"; 642 forceSSL = true; # mastodon only supports https 643 enableACME = true; 644 645 locations."/system/".alias = "/var/lib/mastodon/public-system/"; 646 647 locations."/" = { 648 tryFiles = "$uri @proxy"; 649 }; 650 651 locations."@proxy" = { 652 proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-web/web.socket" else "http://127.0.0.1:${toString(cfg.webPort)}"); 653 proxyWebsockets = true; 654 }; 655 656 locations."/api/v1/streaming/" = { 657 proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/"); 658 proxyWebsockets = true; 659 }; 660 }; 661 }; 662 663 services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") { 664 enable = true; 665 hostname = lib.mkDefault "${cfg.localDomain}"; 666 }; 667 services.redis.servers.mastodon = lib.mkIf (cfg.redis.createLocally && cfg.redis.host == "127.0.0.1") { 668 enable = true; 669 port = cfg.redis.port; 670 bind = "127.0.0.1"; 671 }; 672 services.postgresql = lib.mkIf databaseActuallyCreateLocally { 673 enable = true; 674 ensureUsers = [ 675 { 676 name = cfg.database.user; 677 ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; 678 } 679 ]; 680 ensureDatabases = [ cfg.database.name ]; 681 }; 682 683 users.users = lib.mkMerge [ 684 (lib.mkIf (cfg.user == "mastodon") { 685 mastodon = { 686 isSystemUser = true; 687 home = cfg.package; 688 inherit (cfg) group; 689 }; 690 }) 691 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package mastodonEnv pkgs.imagemagick ]) 692 ]; 693 694 users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user; 695 }; 696 697 meta.maintainers = with lib.maintainers; [ happy-river erictapen ]; 698 699}