+{ lib, pkgs, config, mylibs, ... }:
+let
+ mastodon = pkgs.callPackage ./mastodon.nix {
+ inherit (mylibs) fetchedGithub checkEnv;
+ };
+
+ cfg = config.services.myWebsites.tools.mastodon;
+in {
+ options.services.myWebsites.tools.mastodon = {
+ enable = lib.mkEnableOption "enable mastodon's website";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # FIXME: Can we use dynamic users from systemd?
+ # nixos/modules/misc/ids.nix
+ ids.uids.mastodon = 399;
+ ids.gids.mastodon = 399;
+
+ users.users.mastodon = {
+ name = "mastodon";
+ uid = config.ids.uids.mastodon;
+ group = "mastodon";
+ description = "Mastodon user";
+ home = "${mastodon.railsRoot}";
+ useDefaultShell = true;
+ };
+
+ users.groups.mastodon.gid = config.ids.gids.mastodon;
+
+ systemd.services.mastodon-streaming = {
+ description = "Mastodon Streaming";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "mastodon-web.service" ];
+
+ environment.NODE_ENV = "production";
+ environment.SOCKET = mastodon.nodeSocket;
+
+ path = [ pkgs.nodejs pkgs.bashInteractive ];
+
+ script = ''
+ exec npm run start
+ '';
+
+ postStart = ''
+ while [ ! -S $SOCKET ]; do
+ sleep 0.5
+ done
+ chmod a+w $SOCKET
+ '';
+
+ postStop = ''
+ rm $SOCKET
+ '';
+
+ serviceConfig = {
+ User = "mastodon";
+ EnvironmentFile = mastodon.config;
+ PrivateTmp = true;
+ Restart = "always";
+ TimeoutSec = 15;
+ Type = "simple";
+ WorkingDirectory = mastodon.railsRoot;
+ };
+
+ unitConfig.RequiresMountsFor = mastodon.varDir;
+ };
+
+ systemd.services.mastodon-web = {
+ description = "Mastodon Web app";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+
+ environment.RAILS_ENV = "production";
+ environment.SOCKET = mastodon.railsSocket;
+
+ path = [ pkgs.bundler ];
+
+ preStart = ''
+ bundle exec rails db:migrate
+ '';
+
+ script = ''
+ exec bundle exec puma -C config/puma.rb
+ '';
+
+ serviceConfig = {
+ User = "mastodon";
+ EnvironmentFile = mastodon.config;
+ PrivateTmp = true;
+ Restart = "always";
+ TimeoutSec = 15;
+ Type = "simple";
+ WorkingDirectory = mastodon.railsRoot;
+ };
+
+ unitConfig.RequiresMountsFor = mastodon.varDir;
+ };
+
+ systemd.services.mastodon-sidekiq = {
+ description = "Mastodon Sidekiq";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "mastodon-web.service" ];
+
+ environment.RAILS_ENV="production";
+ environment.DB_POOL="5";
+
+ path = [ pkgs.bundler ];
+
+ script = ''
+ exec bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
+ '';
+
+ serviceConfig = {
+ User = "mastodon";
+ EnvironmentFile = mastodon.config;
+ PrivateTmp = true;
+ Restart = "always";
+ TimeoutSec = 15;
+ Type = "simple";
+ WorkingDirectory = mastodon.railsRoot;
+ };
+
+ unitConfig.RequiresMountsFor = mastodon.varDir;
+ };
+
+ # FIXME: initial sync
+ system.activationScripts.mastodon = {
+ deps = [ "users" ];
+ text = ''
+ install -m 0755 -o mastodon -g mastodon -d ${mastodon.socketsDir}
+ install -m 0755 -o mastodon -g mastodon -d ${mastodon.varDir}
+ '';
+ };
+
+ services.myWebsites.tools.modules = [
+ "headers" "proxy" "proxy_wstunnel" "proxy_http" "proxy_balancer"
+ # FIXME: probably only one balancer method is needed:
+ "lbmethod_byrequests" "lbmethod_bytraffic" "lbmethod_bybusyness" "lbmethod_heartbeat"
+ ];
+ security.acme.certs."eldiron".extraDomains."mastodon.immae.eu" = null;
+ services.myWebsites.tools.vhostConfs.mastodon = {
+ certName = "eldiron";
+ hosts = ["mastodon.immae.eu" ];
+ root = "${mastodon.railsRoot}/public/";
+ extraConfig = [ ''
+ Header always set Referrer-Policy "strict-origin-when-cross-origin"
+ Header always set Strict-Transport-Security "max-age=31536000"
+
+ <LocationMatch "^/(assets|avatars|emoji|headers|packs|sounds|system)>
+ Header always set Cache-Control "public, max-age=31536000, immutable"
+ Require all granted
+ </LocationMatch>
+
+ ProxyPreserveHost On
+ RequestHeader set X-Forwarded-Proto "https"
+
+ RewriteEngine On
+
+ ProxyPass /500.html !
+ ProxyPass /sw.js !
+ ProxyPass /embed.js !
+ ProxyPass /robots.txt !
+ ProxyPass /manifest.json !
+ ProxyPass /browserconfig.xml !
+ ProxyPass /mask-icon.svg !
+ ProxyPassMatch ^(/.*\.(png|ico|gif)$) !
+ ProxyPassMatch ^/(assets|avatars|emoji|headers|packs|sounds|system|.well-known/acme-challenge) !
+
+ ProxyPassMatch /api/v1/streaming/(.+)$ balancer://node_servers_http/api/v1/streaming/$1
+ ProxyPass /api/v1/streaming/ balancer://node_servers/
+ ProxyPassReverse /api/v1/streaming/ balancer://node_servers/
+ ProxyPass / balancer://puma_servers/
+ ProxyPassReverse / balancer://puma_servers/
+
+ <Proxy balancer://puma_servers>
+ BalancerMember unix://${mastodon.railsSocket}|http://
+ </Proxy>
+
+ <Proxy balancer://node_servers>
+ BalancerMember unix://${mastodon.nodeSocket}|ws://localhost
+ </Proxy>
+
+ <Proxy balancer://node_servers_http>
+ BalancerMember unix://${mastodon.nodeSocket}|http://localhost
+ </Proxy>
+
+ Alias /system ${mastodon.varDir}
+
+ <Directory ${mastodon.varDir}>
+ Require all granted
+ Options -MultiViews
+ </Directory>
+
+ <Directory ${mastodon.railsRoot}/public/>
+ Require all granted
+ Options -MultiViews +FollowSymlinks
+ </Directory>
+
+ ErrorDocument 500 /500.html
+ ErrorDocument 501 /500.html
+ ErrorDocument 502 /500.html
+ ErrorDocument 503 /500.html
+ ErrorDocument 504 /500.html
+ '' ];
+ };
+ };
+}