From c635a33654a2b3426057e3ec5b18a3dfe02e06cd Mon Sep 17 00:00:00 2001 From: Thary Date: Thu, 11 Sep 2025 17:30:40 +0300 Subject: [PATCH] Initial commit --- config-paths-needed.json | 11 +++ flake.nix | 41 ++++++++++ hedgedocClientSecretAsFile.patch | 27 +++++++ icon.svg | 20 +++++ module.nix | 131 +++++++++++++++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 config-paths-needed.json create mode 100644 flake.nix create mode 100644 hedgedocClientSecretAsFile.patch create mode 100644 icon.svg create mode 100644 module.nix diff --git a/config-paths-needed.json b/config-paths-needed.json new file mode 100644 index 0000000..0b9c963 --- /dev/null +++ b/config-paths-needed.json @@ -0,0 +1,11 @@ +[ + [ "selfprivacy", "domain" ], + [ "selfprivacy", "modules", "auth", "enable" ], + [ "selfprivacy", "modules", "hedgedoc" ], + [ "selfprivacy", "passthru", "auth", "mkOAuth2ClientSecretFP" ], + [ "selfprivacy", "passthru", "auth", "oauth2-discovery-url" ], + [ "selfprivacy", "passthru", "auth", "oauth2-provider-name" ], + [ "selfprivacy", "sso", "enable" ], + [ "selfprivacy", "useBinds" ], + [ "services", "kanidm", "serverSettings", "origin" ] +] diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a8d364c --- /dev/null +++ b/flake.nix @@ -0,0 +1,41 @@ +{ + # TODO: check whether there is no TODOs + # TODO: check whether there is no hedgegdoc mentions + description = "Mastodon module"; + + outputs = { ... }: + { + nixosModules.default = import ./module.nix; + configPathsNeeded = builtins.fromJSON (builtins.readFile ./config-paths-needed.json); + meta = + { lib, ... }: + { + spModuleSchemaVersion = 1; + id = "mastodon"; + name = "Mastodon"; + description = "Mastodon is a free non-profit decentralized social media, a part of Fediverse network, offering a web client and a mobile app."; + svgIcon = builtins.readFile ./icon.svg; + isMovable = true; + isRequired = false; + backupDescription = "."; + systemdServices = [ + "mastodon.service" + ]; + folders = [ + "/var/lib/mastodon" + ]; + postgreDatabases = [ + "mastodon" + ]; + license = [ + lib.licenses.agpl3Only # TODO: plus or only + ]; + homepage = "https://joinmastodon.org"; + sourcePage = "https://github.com/mastodon/mastodon"; + supportLevel = "normal"; + sso = { + userGroup = "sp.mastodon.users"; + }; + }; + }; +} diff --git a/hedgedocClientSecretAsFile.patch b/hedgedocClientSecretAsFile.patch new file mode 100644 index 0000000..b58d158 --- /dev/null +++ b/hedgedocClientSecretAsFile.patch @@ -0,0 +1,27 @@ +diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js +index b0ffa5e8a..45229358e 100644 +--- a/lib/web/auth/oauth2/index.js ++++ b/lib/web/auth/oauth2/index.js +@@ -6,6 +6,7 @@ const { Strategy, InternalOAuthError } = require('passport-oauth2') + const config = require('../../../config') + const logger = require('../../../logger') + const { passportGeneralCallback } = require('../utils') ++const fs = require('fs') + + const oauth2Auth = module.exports = Router() + +@@ -130,11 +131,13 @@ OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { + }) + } + ++const OAuth2clientSecretText = fs.readFileSync(config.oauth2.clientSecret, 'utf8').split("\n")[0] ++ + passport.use(new OAuth2CustomStrategy({ + authorizationURL: config.oauth2.authorizationURL, + tokenURL: config.oauth2.tokenURL, + clientID: config.oauth2.clientID, +- clientSecret: config.oauth2.clientSecret, ++ clientSecret: OAuth2clientSecretText, + callbackURL: config.serverURL + '/auth/oauth2/callback', + userProfileURL: config.oauth2.userProfileURL, + scope: config.oauth2.scope, diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..e561ed3 --- /dev/null +++ b/icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..1030fea --- /dev/null +++ b/module.nix @@ -0,0 +1,131 @@ +{ + 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; + + # SelfPrivacy uses SP Module ID to identify the group! + usersGroup = "sp.mastodon.users"; + + 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 { + 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; + }; + + + systemd = { + services.mastodon = { + unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/mastodon"; + serviceConfig = { + loadCredentials = ["client-secret:${oauthClientSecretFP}"]; + ExecStart = '' + 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; + subdomain = cfg.subdomain; + originLanding = "https://${cfg.subdomain}.${sp.domain}/"; + originUrl = oauthRedirectURL; + clientSystemdUnits = [ "mastodon.service" ]; + enablePkce = true; + linuxUserOfClient = "mastodon"; + linuxGroupOfClient = "mastodon"; + }; + }; +}