{ config, lib, pkgs, ... }: let svc_domain = "matrix.${config.mine.shared.settings.domain}"; max_upload_size = "50M"; name = "matrix-synapse"; matrix_synapse_user = name; matrix_synapse_group = name; oidcConfigPath = "/run/${name}/oidc.yaml"; stateDir = config.mine.zfsMounts."rpool/safe/svcs/synapse"; matrix_port = 8448; in { services.matrix-synapse = { enable = true; dataDir = stateDir; extras = [ "oidc" ]; extraConfigFiles = [ oidcConfigPath ]; settings = { server_name = config.mine.shared.settings.domain; public_baseurl = "https://${svc_domain}"; enable_registration = false; password_config.enabled = false; listeners = [ { port = matrix_port; bind_addresses = [ "localhost" ]; x_forwarded = true; tls = false; resources = [{ names = [ "federation" "client" ]; compress = false; }]; } ]; # database # shut up shut up shut up shut up!!! database.allow_unsafe_locale = true; database_type = "psycopg2"; database_args.database = "matrix-synapse"; # increase max_upload_size, otherwise might not # be able to watch cat videos max_upload_size = max_upload_size; # only authenticated media # TODO: Should default to true at some point enable_authenticated_media = true; # retentien policies media_retention = { local_media_lifetime = "90d"; remote_media_lifetime = "14d"; }; # keep messages MIN 1 day, MAX 1 year retention = { enabled = true; default_policy = { min_lifetime = "1d"; max_lifetime = "1y"; }; allowed_lifetime_min = "1d"; allowed_lifetime_max = "1y"; }; }; }; # setup OIDC/OpenID/OAUTH2/whatever for synapse # do this way, instead of having EVERYTHING in a secret file. systemd.services.matrix-synapse = { # https://linux-audit.com/systemd/systemd-syscall-filtering/ # https://github.com/restic/rest-server/pull/249 # https://github.com/golang/go/issues/46279 serviceConfig.SystemCallFilter = [ "setrlimit" ]; serviceConfig.EnvironmentFile = config.age.secrets.matrix-synapse-config-authelia-secret.path; preStart = let oidc_config = (pkgs.formats.yaml {}).generate "matrix-synapse-oidc-config" { oidc_providers = [{ idp_id = "authelia"; idp_name = "Authelia"; idp_icon = "mxc://authelia.com/cKlrTPsGvlpKxAYeHWJsdVHI"; client_id = "synapse"; client_secret = "$CLIENT_SECRET"; issuer = "https://${config.mine.shared.settings.authelia.domain}"; discover = true; scopes = [ "openid" "profile" "email" ]; user_mapping_provider.config = { subject_claim = "sub"; localpart_template = "{{ user.preferred_username }}"; display_name_template = "{{ user.name }}"; email_template = "{{ user.email }}"; }; }]; }; in '' ${pkgs.envsubst}/bin/envsubst \ -o ${oidcConfigPath} \ -i ${oidc_config} ''; }; # setup for oidc services.authelia.instances.main.settings.identity_providers.oidc.clients = [{ client_id = "synapse"; client_name = "Synapse"; client_secret = "$pbkdf2-sha512$310000$SmE9y.LA9lnzxNWL6CeWQA$zcrum.Rst9xQy/MKBI5i.UiUdSjx/F0ak65Z3vYk0w7/GMWIqXaW3GnE7bJQw6nHi5eZ2uhKHtW/DKp2TDVhbQ"; redirect_uris = [ "https://${svc_domain}/_synapse/client/oidc/callback" ]; scopes = [ "openid" "profile" "email" ]; }]; # ensure databases + user is setup services.postgresql = { ensureDatabases = [ matrix_synapse_user ]; ensureUsers = [{ name = matrix_synapse_user; ensureDBOwnership = true; }]; }; services.nginx = { virtualHosts."${svc_domain}" = { enableACME = true; forceSSL = true; # only proxy `_matrix` and `_synapse/client`, it is not ideal to proxy `_synapse/admin` locations."~ ^(/_matrix|/_synapse/client)" = { proxyPass = "http://localhost:${builtins.toString matrix_port}"; extraConfig = "client_max_body_size ${max_upload_size};"; }; }; # serve `.well-known/matrix/{server,client}` because matrix synapse runs on subdomain virtualHosts."${config.mine.shared.settings.domain}" = let client = { "m.homeserver" = { "base_url" = "https://${svc_domain}"; }; "m.identity_server" = { "base_url" = "https://matrix.org"; }; }; server = { "m.server" = "${svc_domain}:443"; }; in { locations."/.well-known/matrix/server".extraConfig = '' add_header Content-Type application/json; return 200 '${builtins.toJSON server}'; ''; locations."/.well-known/matrix/client".extraConfig = '' add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; return 200 '${builtins.toJSON client}'; ''; }; }; systemd.tmpfiles.rules = [ "Z ${stateDir} 0770 ${matrix_synapse_user} ${matrix_synapse_group} -" ]; age.secrets = { matrix-synapse-config-authelia-secret.owner = matrix_synapse_user; }; mine.shared.settings.matrix-synapse.domain = svc_domain; mine.shared.meta.matrix-synapse = { name = "Matrix Synapse"; description = "We host our own Matrix homeserver using Synapse! Login using your favourite which supports OpenID."; url = "https://${svc_domain}"; package = let pkg = pkgs.matrix-synapse-unwrapped; in { name = pkg.pname; version = pkg.version; meta = pkg.meta; }; }; }