{ config, lib, pkgs, ... }: let sp = config.selfprivacy; cfg = sp.modules.mastodon; oauthClientID = "mastodon"; auth-passthru = config.selfprivacy.passthru.auth; oauthDiscoveryURL = auth-passthru.oauth2-discovery-url oauthClientID; 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"; 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 { fileSystems = lib.mkIf sp.useBinds { "/var/lib/mastodon" = { device = "/volumes/${cfg.location}/mastodon"; options = [ "bind" ]; }; }; 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}"; user = "noreply.mastodon"; host = "127.0.0.1"; port = 25; passwordFile = "/run/keys/mastodon/email_password"; authenticate = true; }; extraConfig = { "SMTP_ENABLE_STARTTLS_AUTO" = false; "SMTP_TLS" = false; "SMTP_SSL" = false; }; }; # services.postfix.config = { # smtp_sasl_auth_enable = "yes"; # smtp_sasl_security_options = ""; # smtp_sasl_password_maps = "texthash:/run/postfix/mastodon.cf"; # virtual_mailbox_maps = [ "hash:/run/postfix/mastodon.cf" ]; # }; mailserver.loginAccounts."noreply.mastodon@${sp.domain}" = { hashedPasswordFile = "/run/postfix/mastodon.cf"; sendOnly = true; }; 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=$(${lib.getExe pkgs.mkpasswd} -sm bcrypt "$password") # echo "noreply.mastodon@${sp.domain}: $hashedPassword" > /run/postfix/mastodon.cf echo "$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 = { Slice = "hedgedoc.slice"; LoadCredential = ["client-secret:${oauthClientSecretFP}"]; ExecStart = lib.mkForce (pkgs.writeShellScript "run-mastodon-with-client-secret" '' export OIDC_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,email"; OIDC_UID_FIELD = "preferred_username"; OIDC_CLIENT_ID = oauthClientID; OIDC_REDIRECT_URI = oauthRedirectURL; OIDC_SECURITY_ASSUME_EMAIL_IS_VERIFIED = "false"; }; }; 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 = false; useShortPreferredUsername = true; linuxUserOfClient = "mastodon"; linuxGroupOfClient = "mastodon"; }; }; }