{ config, lib, pkgs, ... }: let sp = config.selfprivacy; cfg = sp.modules.mastodon; oauthClientID = "mastodon"; auth-passthru = config.selfprivacy.passthru.auth; oauthDiscoveryURL = config.services.kanidm.serverSettings.origin; issuer = lib.strings.removeSuffix "/.well-known/openid-configuration" oauthDiscoveryURL; usersGroup = "sp.mastodon.users"; adminsGroup = "sp.mastodon.admins"; oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP oauthClientID; oauthRedirectURL = "https://${cfg.subdomain}.${sp.domain}/auth/auth/openid_connect/callback"; # emailPassword = pkgs.runCommand "genpassword" {} "echo `head -c 32 /dev/urandom | base64 | sed 's/[+=\\/A-Z]//g'` > $out"; # emailPasswordHash = pkgs.runCommand "genpassword" {} "echo `head -c 32 /dev/urandom | base64 | sed 's/[+=\\/A-Z]//g'` > $out"; in { options.selfprivacy.modules.mastodon = { enable = (lib.mkOption { default = false; type = lib.types.bool; description = "Enable Mastodon"; }) // { meta = { type = "enable"; }; }; location = (lib.mkOption { type = lib.types.str; description = "Mastodon location"; }) // { meta = { type = "location"; }; }; subdomain = (lib.mkOption { default = "mastodon"; type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]"; description = "Subdomain"; }) // { meta = { widget = "subdomain"; type = "string"; regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]"; weight = 0; }; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = sp.sso.enable; message = "Mastodon cannot be enabled when SSO is disabled."; } ]; fileSystems = lib.mkIf sp.useBinds { "/var/lib/mastodon" = { device = "/volumes/${cfg.location}/mastodon"; options = [ "bind" ]; }; }; # services.postgresql = { # ensureDatabases = [ "mastodon" ]; # ensureUsers = [ # { # name = "mastodon"; # ensureDBOwnership = true; # } # ]; # }; services.mastodon = { enable = true; localDomain = "${cfg.subdomain}.${sp.domain}"; enableUnixSocket = false; configureNginx = true; database.createLocally = true; streamingProcesses = 3; smtp = { createLocally = false; user = "noreply.mastodon@${sp.domain}"; fromAddress = "noreply.mastodon@${sp.domain}"; passwordFile = "/run/keys/mastodon/email_password"; authenticate = true; }; }; mailserver.loginAccounts."noreply.mastodon@${sp.domain}" = { hashedPassword = "/run/keys/mastodon/email_password"; sendOnly = true; }; services.postfix.config.virtual_mailbox_maps = [ "hash:/run/postfix/mastodon.cf" ]; systemd = { services.mastodon-email-password-setup = { enable = true; wantedBy = [ "multi-user.target" "mastodon-web.service" "postfix.service" ]; serviceConfig = { Type = "oneshot"; ExecStart = pkgs.writeShellScript "gen-mastodon-email-password" '' export password=$(head -c 32 /dev/urandom | base64 | sed 's/[+=\\/A-Z]//g') rm -f /run/keys/mastodon/email_password || true mkdir /run/keys/mastodon/ || true # Create /run/keys/mastodon if it doesn't exist echo $password > /run/keys/mastodon/email_password chmod 400 /run/keys/mastodon/email_password chown ${config.services.mastodon.user}:${config.services.mastodon.group} /run/keys/mastodon/email_password rm -f /run/postfix/mastodon.cf || true mkdir /run/postfix/ || true # Create /run/postfix if it doesn't exist export hashedPassword=$(mkpasswd -sm bcrypt "$password") echo "noreply.mastodon@${sp.domain}: $hashedPassword" > /run/postfix/mastodon.cf chmod 440 /run/postfix/mastodon.cf chown ${config.services.postfix.user}:${config.services.postfix.group} /run/postfix/mastodon.cf ''; }; }; services.mastodon-web = { unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/mastodon"; serviceConfig = { loadCredentials = ["client-secret:${oauthClientSecretFP}"]; ExecStart = lib.mkForce '' export CLIENT_SECRET=$(cat $CREDENTIALS_DIRECTORY/client-secret) ${config.services.mastodon.package}/bin/puma -C config/puma.rb` ''; }; environment = { OIDC_ENABLED = "true"; OIDC_DISPLAY_NAME= "Kanidm"; OIDC_ISSUER = issuer; OIDC_DISCOVERY = "true"; OIDC_SCOPE = "openid,profile"; OIDC_UID_FIELD = "sub"; OIDC_CLIENT_ID = oauthClientID; OIDC_REDIRECT_URI = oauthRedirectURL; }; }; slices.mastodon = { description = "Mastodon service slice"; }; }; selfprivacy.auth.clients.${oauthClientID} = { inherit usersGroup; inherit adminsGroup; subdomain = cfg.subdomain; originLanding = "https://${cfg.subdomain}.${sp.domain}/"; originUrl = oauthRedirectURL; clientSystemdUnits = [ "mastodon.service" ]; enablePkce = true; linuxUserOfClient = "mastodon"; linuxGroupOfClient = "mastodon"; }; }; }