diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 97721868660..5242ac05357 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -891,6 +891,7 @@
./services/web-apps/jitsi-meet.nix
./services/web-apps/keycloak.nix
./services/web-apps/limesurvey.nix
+ ./services/web-apps/mastodon.nix
./services/web-apps/mattermost.nix
./services/web-apps/mediawiki.nix
./services/web-apps/miniflux.nix
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
new file mode 100644
index 00000000000..47da8b9867a
--- /dev/null
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -0,0 +1,541 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.mastodon;
+ # We only want to create a database if we're actually going to connect to it.
+ databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "/run/postgresql";
+
+ env = {
+ RAILS_ENV = "production";
+ NODE_ENV = "production";
+
+ DB_USER = cfg.database.user;
+
+ REDIS_HOST = cfg.redis.host;
+ REDIS_PORT = toString(cfg.redis.port);
+ DB_HOST = cfg.database.host;
+ DB_PORT = toString(cfg.database.port);
+ DB_NAME = cfg.database.name;
+ LOCAL_DOMAIN = cfg.localDomain;
+ SMTP_SERVER = cfg.smtp.host;
+ SMTP_PORT = toString(cfg.smtp.port);
+ SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
+ PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
+ PAPERCLIP_ROOT_URL = "/system";
+ ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
+ ES_HOST = cfg.elasticsearch.host;
+ ES_PORT = toString(cfg.elasticsearch.port);
+ }
+ // (if cfg.smtp.authenticate then { SMTP_LOGIN = cfg.smtp.user; } else {})
+ // cfg.extraConfig;
+
+ envFile = pkgs.writeText "mastodon.env" (lib.concatMapStrings (s: s + "\n") (
+ (lib.concatLists (lib.mapAttrsToList (name: value:
+ if value != null then [
+ "${name}=\"${toString value}\""
+ ] else []
+ ) env))));
+
+ mastodonEnv = pkgs.writeShellScriptBin "mastodon-env" ''
+ set -a
+ source "${envFile}"
+ source /var/lib/mastodon/.secrets_env
+ eval -- "\$@"
+ '';
+
+in {
+
+ options = {
+ services.mastodon = {
+ enable = lib.mkEnableOption "Mastodon, a federated social network server";
+
+ configureNginx = lib.mkOption {
+ description = ''
+ Configure nginx as a reverse proxy for mastodon.
+ Note that this makes some assumptions on your setup, and sets settings that will
+ affect other virtualHosts running on your nginx instance, if any.
+ Alternatively you can configure a reverse-proxy of your choice to serve these paths:
+
+ / -> $(nix-instantiate --eval '<nixpkgs>' -A mastodon.outPath)/public
+
+ / -> 127.0.0.1:{{ webPort }}
(If there was no file in the directory above.)
+
+ /system/ -> /var/lib/mastodon/public-system/
+
+ /api/v1/streaming/ -> 127.0.0.1:{{ streamingPort }}
+
+ Make sure that websockets are forwarded properly. You might want to set up caching
+ of some requests. Take a look at mastodon's provided nginx configuration at
+ https://github.com/tootsuite/mastodon/blob/master/dist/nginx.conf
.
+ '';
+ type = lib.types.bool;
+ default = false;
+ };
+
+ user = lib.mkOption {
+ description = ''
+ User under which mastodon runs. If it is set to "mastodon",
+ that user will be created, otherwise it should be set to the
+ name of a user created elsewhere. In both cases,
+ mastodon and a package containing only
+ the shell script mastodon-env
will be added to
+ the user's package set. To run a command from
+ mastodon such as tootctl
+ with the environment configured by this module use
+ mastodon-env
, as in:
+
+ mastodon-env tootctl accounts create newuser --email newuser@example.com
+ '';
+ type = lib.types.str;
+ default = "mastodon";
+ };
+
+ group = lib.mkOption {
+ description = ''
+ Group under which mastodon runs.
+ If it is set to "mastodon", a group will be created.
+ '';
+ type = lib.types.str;
+ default = "mastodon";
+ };
+
+ streamingPort = lib.mkOption {
+ description = "TCP port used by the mastodon-streaming service.";
+ type = lib.types.port;
+ default = 55000;
+ };
+
+ webPort = lib.mkOption {
+ description = "TCP port used by the mastodon-web service.";
+ type = lib.types.port;
+ default = 55001;
+ };
+
+ sidekiqPort = lib.mkOption {
+ description = "TCP port used by the mastodon-sidekiq service";
+ type = lib.types.port;
+ default = 55002;
+ };
+
+ vapidPublicKeyFile = lib.mkOption {
+ description = ''
+ Path to file containing the public key used for Web Push
+ Voluntary Application Server Identification. A new keypair can
+ be generated by running:
+
+ nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys
+
+ If does not
+ exist, it and this file will be created with a new keypair.
+ '';
+ default = "/var/lib/mastodon/secrets/vapid-public-key";
+ type = lib.types.str;
+ };
+
+ localDomain = lib.mkOption {
+ description = "The domain serving your Mastodon instance.";
+ example = "social.example.org";
+ type = lib.types.str;
+ };
+
+ secretKeyBaseFile = lib.mkOption {
+ description = ''
+ Path to file containing the secret key base.
+ A new secret key base can be generated by running:
+
+ nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret
+
+ If this file does not exist, it will be created with a new secret key base.
+ '';
+ default = "/var/lib/mastodon/secrets/secret-key-base";
+ type = lib.types.str;
+ };
+
+ otpSecretFile = lib.mkOption {
+ description = ''
+ Path to file containing the OTP secret.
+ A new OTP secret can be generated by running:
+
+ nix build -f '<nixpkgs>' mastodon; cd result; bin/rake secret
+
+ If this file does not exist, it will be created with a new OTP secret.
+ '';
+ default = "/var/lib/mastodon/secrets/otp-secret";
+ type = lib.types.str;
+ };
+
+ vapidPrivateKeyFile = lib.mkOption {
+ description = ''
+ Path to file containing the private key used for Web Push
+ Voluntary Application Server Identification. A new keypair can
+ be generated by running:
+
+ nix build -f '<nixpkgs>' mastodon; cd result; bin/rake webpush:generate_keys
+
+ If this file does not exist, it will be created with a new
+ private key.
+ '';
+ default = "/var/lib/mastodon/secrets/vapid-private-key";
+ type = lib.types.str;
+ };
+
+ redis = {
+ createLocally = lib.mkOption {
+ description = "Configure local Redis server for Mastodon.";
+ type = lib.types.bool;
+ default = true;
+ };
+
+ host = lib.mkOption {
+ description = "Redis host.";
+ type = lib.types.str;
+ default = "127.0.0.1";
+ };
+
+ port = lib.mkOption {
+ description = "Redis port.";
+ type = lib.types.port;
+ default = 6379;
+ };
+ };
+
+ database = {
+ createLocally = lib.mkOption {
+ description = "Configure local PostgreSQL database server for Mastodon.";
+ type = lib.types.bool;
+ default = true;
+ };
+
+ host = lib.mkOption {
+ type = lib.types.str;
+ default = "/run/postgresql";
+ example = "192.168.23.42";
+ description = "Database host address or unix socket.";
+ };
+
+ port = lib.mkOption {
+ type = lib.types.int;
+ default = 5432;
+ description = "Database host port.";
+ };
+
+ name = lib.mkOption {
+ type = lib.types.str;
+ default = "mastodon";
+ description = "Database name.";
+ };
+
+ user = lib.mkOption {
+ type = lib.types.str;
+ default = "mastodon";
+ description = "Database user.";
+ };
+
+ passwordFile = lib.mkOption {
+ type = lib.types.nullOr lib.types.path;
+ default = "/var/lib/mastodon/secrets/db-password";
+ example = "/run/keys/mastodon-db-password";
+ description = ''
+ A file containing the password corresponding to
+ .
+ '';
+ };
+ };
+
+ smtp = {
+ createLocally = lib.mkOption {
+ description = "Configure local Postfix SMTP server for Mastodon.";
+ type = lib.types.bool;
+ default = true;
+ };
+
+ authenticate = lib.mkOption {
+ description = "Authenticate with the SMTP server using username and password.";
+ type = lib.types.bool;
+ default = true;
+ };
+
+ host = lib.mkOption {
+ description = "SMTP host used when sending emails to users.";
+ type = lib.types.str;
+ default = "127.0.0.1";
+ };
+
+ port = lib.mkOption {
+ description = "SMTP port used when sending emails to users.";
+ type = lib.types.port;
+ default = 25;
+ };
+
+ fromAddress = lib.mkOption {
+ description = ''"From" address used when sending Emails to users.'';
+ type = lib.types.str;
+ };
+
+ user = lib.mkOption {
+ description = "SMTP login name.";
+ type = lib.types.str;
+ };
+
+ passwordFile = lib.mkOption {
+ description = ''
+ Path to file containing the SMTP password.
+ '';
+ default = "/var/lib/mastodon/secrets/smtp-password";
+ example = "/run/keys/mastodon-smtp-password";
+ type = lib.types.str;
+ };
+ };
+
+ elasticsearch = {
+ host = lib.mkOption {
+ description = ''
+ Elasticsearch host.
+ If it is not null, Elasticsearch full text search will be enabled.
+ '';
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+
+ port = lib.mkOption {
+ description = "Elasticsearch port.";
+ type = lib.types.port;
+ default = 9200;
+ };
+ };
+
+ package = lib.mkOption {
+ type = lib.types.package;
+ default = pkgs.mastodon;
+ defaultText = "pkgs.mastodon";
+ description = "Mastodon package to use.";
+ };
+
+ extraConfig = lib.mkOption {
+ type = lib.types.attrs;
+ default = {};
+ description = ''
+ Extra environment variables to pass to all mastodon services.
+ '';
+ };
+
+ automaticMigrations = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = ''
+ Do automatic database migrations.
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
+ message = ''For local automatic database provisioning (services.mastodon.database.createLocally == true) with peer authentication (services.mastodon.database.host == "/run/postgresql") to work services.mastodon.user and services.mastodon.database.user must be identical.'';
+ }
+ ];
+
+ systemd.services.mastodon-init-dirs = {
+ script = ''
+ umask 077
+
+ if ! test -f ${cfg.secretKeyBaseFile}; then
+ mkdir -p $(dirname ${cfg.secretKeyBaseFile})
+ bin/rake secret > ${cfg.secretKeyBaseFile}
+ fi
+ if ! test -f ${cfg.otpSecretFile}; then
+ mkdir -p $(dirname ${cfg.otpSecretFile})
+ bin/rake secret > ${cfg.otpSecretFile}
+ fi
+ if ! test -f ${cfg.vapidPrivateKeyFile}; then
+ mkdir -p $(dirname ${cfg.vapidPrivateKeyFile}) $(dirname ${cfg.vapidPublicKeyFile})
+ keypair=$(bin/rake webpush:generate_keys)
+ echo $keypair | grep --only-matching "Private -> [^ ]\+" | sed 's/^Private -> //' > ${cfg.vapidPrivateKeyFile}
+ echo $keypair | grep --only-matching "Public -> [^ ]\+" | sed 's/^Public -> //' > ${cfg.vapidPublicKeyFile}
+ fi
+
+ cat > /var/lib/mastodon/.secrets_env <