Files
sp-mastodon-module/module.nix

175 lines
5.5 KiB
Nix
Raw Normal View History

2025-09-11 17:30:40 +03:00
{
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";
2025-09-11 18:08:18 +03:00
adminsGroup = "sp.mastodon.admins";
2025-09-11 17:30:40 +03:00
oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP oauthClientID;
oauthRedirectURL = "https://${cfg.subdomain}.${sp.domain}/auth/auth/openid_connect/callback";
2025-09-11 22:38:06 +03:00
# 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";
2025-09-11 17:30:40 +03:00
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;
2025-09-11 18:20:50 +03:00
streamingProcesses = 3;
2025-09-11 22:38:06 +03:00
smtp = {
createLocally = false;
user = "noreply.mastodon@${sp.domain}";
2025-09-11 22:41:09 +03:00
fromAddress = "noreply.mastodon@${sp.domain}";
2025-09-12 14:45:39 +03:00
passwordFile = "/run/keys/mastodon/email_password";
2025-09-11 22:38:06 +03:00
authenticate = true;
};
2025-09-11 17:30:40 +03:00
};
2025-09-11 22:38:06 +03:00
mailserver.loginAccounts."noreply.mastodon@${sp.domain}" = {
2025-09-12 14:42:50 +03:00
hashedPassword = "/run/keys/mastodon/email_password";
2025-09-11 22:38:06 +03:00
sendOnly = true;
};
services.postfix.config.virtual_mailbox_maps = [ "hash:/run/postfix/mastodon.cf" ];
2025-09-11 17:30:40 +03:00
systemd = {
2025-09-11 22:38:06 +03:00
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
'';
};
};
2025-09-11 18:51:52 +03:00
services.mastodon-web = {
2025-09-11 17:30:40 +03:00
unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/mastodon";
serviceConfig = {
loadCredentials = ["client-secret:${oauthClientSecretFP}"];
2025-09-12 15:03:34 +03:00
ExecStart = lib.mkForce pkgs.writeShellScript "run-mastodon-with-client-secret" ''
export CLIENT_SECRET=$(cat $CREDENTIALS_DIRECTORY/client-secret)
${config.services.mastodon.package}/bin/puma -C config/puma.rb`
2025-09-11 17:30:40 +03:00
'';
};
environment = {
2025-09-12 14:42:50 +03:00
OIDC_ENABLED = "true";
2025-09-11 17:30:40 +03:00
OIDC_DISPLAY_NAME= "Kanidm";
OIDC_ISSUER = issuer;
2025-09-12 14:42:50 +03:00
OIDC_DISCOVERY = "true";
2025-09-11 17:30:40 +03:00
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;
2025-09-11 18:08:18 +03:00
inherit adminsGroup;
2025-09-11 17:30:40 +03:00
subdomain = cfg.subdomain;
originLanding = "https://${cfg.subdomain}.${sp.domain}/";
originUrl = oauthRedirectURL;
clientSystemdUnits = [ "mastodon.service" ];
enablePkce = true;
linuxUserOfClient = "mastodon";
linuxGroupOfClient = "mastodon";
};
};
}