{ ... }: { _class = "clan.service"; manifest.name = "clan-core/matrix-synapese"; manifest.description = "A federated messaging server with end-to-end encryption."; manifest.categories = [ "Social" ]; roles.default = { interface = { lib, ... }: { options = { acmeEmail = lib.mkOption { type = lib.types.str; description = '' Email address for account creation and correspondence from the CA. It is recommended to use the same email for all certs to avoid account creation limits. ''; }; services.matrix-synapse.package = lib.mkOption { readOnly = false; description = "Package to use for matrix-synapse"; }; server_tld = lib.mkOption { type = lib.types.str; description = "The address that is suffixed after your username i.e @alice:example.com"; example = "example.com"; }; app_domain = lib.mkOption { type = lib.types.str; description = "The matrix server hostname also serves the element client"; example = "matrix.example.com"; }; users = lib.mkOption { default = { }; type = lib.types.attrsOf ( lib.types.submodule ( { name, ... }: { options = { name = lib.mkOption { type = lib.types.str; default = name; description = "The name of the user"; }; admin = lib.mkOption { type = lib.types.bool; default = false; description = "Whether the user should be an admin"; }; }; } ) ); description = "A list of users. Not that only new users will be created and existing ones are not modified."; example.alice = { admin = true; }; }; }; }; perInstance = { settings, ... }: { nixosModule = { pkgs, config, lib, ... }: { imports = [ ./nginx.nix ]; security.acme.defaults.email = settings.acmeEmail; services.matrix-synapse = { enable = true; settings = { server_name = settings.server_tld; database = { args.user = "matrix-synapse"; args.database = "matrix-synapse"; name = "psycopg2"; }; turn_uris = [ "turn:turn.matrix.org?transport=udp" "turn:turn.matrix.org?transport=tcp" ]; registration_shared_secret_path = "/run/synapse-registration-shared-secret"; listeners = [ { port = 8008; bind_addresses = [ "::1" ]; type = "http"; tls = false; x_forwarded = true; resources = [ { names = [ "client" ]; compress = true; } { names = [ "federation" ]; compress = false; } ]; } ]; }; }; clan.core.postgresql.enable = true; clan.core.postgresql.users.matrix-synapse = { }; clan.core.postgresql.databases.matrix-synapse.create.options = { TEMPLATE = "template0"; LC_COLLATE = "C"; LC_CTYPE = "C"; ENCODING = "UTF8"; OWNER = "matrix-synapse"; }; clan.core.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ]; clan.core.vars.generators = { "matrix-synapse" = { files."synapse-registration_shared_secret" = { }; runtimeInputs = with pkgs; [ coreutils pwgen ]; migrateFact = "matrix-synapse"; script = '' echo -n "$(pwgen -s 32 1)" > "$out"/synapse-registration_shared_secret ''; }; } // lib.mapAttrs' ( name: user: lib.nameValuePair "matrix-password-${user.name}" { files."matrix-password-${user.name}" = { }; migrateFact = "matrix-password-${user.name}"; runtimeInputs = with pkgs; [ xkcdpass ]; script = '' xkcdpass -n 4 -d - > "$out"/${lib.escapeShellArg "matrix-password-${user.name}"} ''; } ) settings.users; systemd.services.matrix-synapse = let usersScript = '' while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do if ! kill -0 "$MAINPID"; then exit 1; fi sleep 1; done '' + lib.concatMapStringsSep "\n" (user: '' # only create user if it doesn't exist /run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${ config.clan.core.vars.generators."matrix-password-${user.name}".files."matrix-password-${user.name}".path } --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"} '') (lib.attrValues settings.users); in { path = [ pkgs.curl ]; serviceConfig.ExecStartPre = lib.mkBefore [ "+${pkgs.coreutils}/bin/install -o matrix-synapse -g matrix-synapse ${ lib.escapeShellArg config.clan.core.vars.generators.matrix-synapse.files."synapse-registration_shared_secret".path } /run/synapse-registration-shared-secret" ]; serviceConfig.ExecStartPost = [ ''+${pkgs.writeShellScript "matrix-synapse-create-users" usersScript}'' ]; }; services.nginx = { enable = true; virtualHosts = { "${settings.server_tld}" = { locations."= /.well-known/matrix/server".extraConfig = '' add_header Content-Type application/json; return 200 '${builtins.toJSON { "m.server" = "${settings.app_domain}:443"; }}'; ''; locations."= /.well-known/matrix/client".extraConfig = '' add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; return 200 '${ builtins.toJSON { "m.homeserver" = { "base_url" = "https://${settings.app_domain}"; }; "m.identity_server" = { "base_url" = "https://vector.im"; }; } }'; ''; forceSSL = true; enableACME = true; }; "${settings.app_domain}" = let # FIXME: This was taken from upstream. Drop this when our patch is upstream element-web = pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; } '' cp -r ${pkgs.element-web} $out chmod -R u+w $out jq '."default_server_config"."m.homeserver" = { "base_url": "https://${settings.app_domain}:443", "server_name": "${settings.server_tld}" }' \ > $out/config.json < ${pkgs.element-web}/config.json ln -s $out/config.json $out/config.${settings.app_domain}.json ''; in { forceSSL = true; enableACME = true; locations."/".root = element-web; locations."/_matrix".proxyPass = "http://localhost:8008"; # TODO: We should make the port configurable locations."/_synapse".proxyPass = "http://localhost:8008"; }; }; }; }; }; }; }