{ pkgs, lib, config, ... }: let hostname = "matrix"; fqdn = "${hostname}.cloonar.com"; baseUrl = "https://${fqdn}"; clientConfig."m.homeserver".base_url = baseUrl; serverConfig."m.server" = "${fqdn}:443"; mkWellKnown = data: '' default_type application/json; add_header Access-Control-Allow-Origin *; return 200 '${builtins.toJSON data}'; ''; in { # Secrets for Synapse sops.secrets.synapse-oidc-client-secret = { owner = "matrix-synapse"; }; sops.secrets.mautrix-whatsapp-env = { }; sops.secrets.mautrix-signal-env = { }; sops.secrets.mautrix-discord-env = { }; # PostgreSQL database for Synapse services.postgresql = { enable = true; # Synapse requires C locale for correct collation behavior initdbArgs = [ "--lc-collate=C" "--lc-ctype=C" ]; ensureDatabases = [ "matrix-synapse" ]; ensureUsers = [ { name = "matrix-synapse"; ensureDBOwnership = true; } ]; }; services.postgresqlBackup.enable = true; services.postgresqlBackup.databases = [ "matrix-synapse" ]; # Synapse homeserver services.matrix-synapse = { enable = true; settings = { server_name = "cloonar.com"; public_baseurl = baseUrl; listeners = [ { port = 8008; bind_addresses = [ "::1" ]; type = "http"; tls = false; x_forwarded = true; resources = [ { compress = true; names = [ "client" "federation" ]; } ]; } ]; database = { name = "psycopg2"; args = { host = "/run/postgresql"; database = "matrix-synapse"; user = "matrix-synapse"; }; }; # Disable registration - users created via OIDC enable_registration = false; allow_guest_access = false; # OIDC SSO via Authelia oidc_providers = [ { idp_id = "authelia"; idp_name = "Authelia"; discover = true; issuer = "https://auth.cloonar.com"; user_profile_method = "userinfo_endpoint"; client_id = "synapse"; client_secret_path = config.sops.secrets.synapse-oidc-client-secret.path; scopes = [ "openid" "profile" "email" ]; allow_existing_users = true; user_mapping_provider.config = { subject_claim = "sub"; localpart_template = "{{ user.email | localpart_from_email }}"; display_name_template = "{{ user.name }}"; email_template = "{{ user.email }}"; }; } ]; }; }; # Synapse runs inside an isolated microVM, so PrivateUsers provides minimal # additional security. Disabling it allows Synapse to read bridge registration # files via SupplementaryGroups (user namespace blocks mapped GIDs otherwise). systemd.services.matrix-synapse.serviceConfig.PrivateUsers = lib.mkForce false; # Element Web client services.nginx.virtualHosts."element.cloonar.com" = { forceSSL = true; enableACME = true; acmeRoot = null; root = pkgs.element-web.override { conf = { default_theme = "dark"; default_server_config = { "m.homeserver" = { base_url = "https://matrix.cloonar.com"; server_name = "cloonar.com"; }; }; disable_custom_urls = true; disable_3pid_login = true; default_country_code = "AT"; }; }; }; # Synapse nginx reverse proxy services.nginx.virtualHosts."${fqdn}" = { forceSSL = true; enableACME = true; acmeRoot = null; locations."/".extraConfig = '' return 404; ''; locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; locations."= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; locations."/_matrix".proxyPass = "http://[::1]:8008"; locations."/_synapse/client".proxyPass = "http://[::1]:8008"; }; # # Mautrix bridges (using NixOS modules) # Modules handle users, groups, registration files, Synapse integration, # and service ordering automatically via registerToSynapse. # # WhatsApp bridge services.mautrix-whatsapp = { enable = true; registerToSynapse = true; environmentFile = config.sops.secrets.mautrix-whatsapp-env.path; settings = { homeserver = { address = "http://[::1]:8008"; domain = "cloonar.com"; }; bridge = { command_prefix = "!wa"; permissions."*" = "relay"; permissions."cloonar.com" = "user"; relay.enabled = true; }; encryption = { allow = true; default = true; require = true; pickle_key = "$MAUTRIX_WHATSAPP_PICKLE_KEY"; }; }; }; # Signal bridge services.mautrix-signal = { enable = true; registerToSynapse = true; environmentFile = config.sops.secrets.mautrix-signal-env.path; settings = { homeserver = { address = "http://[::1]:8008"; domain = "cloonar.com"; }; bridge = { command_prefix = "!signal"; permissions."*" = "relay"; permissions."cloonar.com" = "user"; relay.enabled = true; }; encryption = { allow = true; default = true; require = true; pickle_key = "$MAUTRIX_SIGNAL_PICKLE_KEY"; }; matrix.sync_direct_chat_list = true; }; }; # Discord bridge services.mautrix-discord = { enable = true; registerToSynapse = true; environmentFile = config.sops.secrets.mautrix-discord-env.path; settings = { homeserver = { address = "http://[::1]:8008"; domain = "cloonar.com"; }; bridge = { command_prefix = "!discord"; permissions."*" = "relay"; permissions."cloonar.com" = "user"; relay.enabled = true; }; # Override dummy token defaults so env var substitution writes real tokens # into the config and registration file (module defaults are placeholder strings) appservice = { as_token = "$MAUTRIX_DISCORD_AS_TOKEN"; hs_token = "$MAUTRIX_DISCORD_HS_TOKEN"; }; encryption = { allow = true; default = true; require = true; pickle_key = "$MAUTRIX_DISCORD_PICKLE_KEY"; }; }; }; }