{ config, inputs, lib, pkgs, this, ... }: with lib; let cfg = config.nixfiles.modules.matrix.dendrite; in { options.nixfiles.modules.matrix.dendrite = { enable = mkEnableOption "Dendrite Matrix server"; port = mkOption { description = "Port."; type = with types; port; default = 8008; }; domain = mkOption { type = types.str; default = config.networking.domain; description = "Domain name sans protocol scheme."; }; }; config = let db = "dendrite"; in mkIf cfg.enable { ark.directories = [ "/var/lib/dendrite" "/var/lib/private/dendrite" ]; # FIXME Use systemd secrets/environment for this. secrets.dendrite-private-key = { file = "${inputs.self}/secrets/dendrite-private-key"; mode = "0444"; }; secrets.dendrite-environment-file = { file = "${inputs.self}/secrets/dendrite-environment-file"; mode = "0444"; }; nixfiles.modules = { nginx = { enable = true; upstreams.dendrite.servers."127.0.0.1:${toString config.services.dendrite.httpPort}" = { }; virtualHosts.${cfg.domain}.locations = { "/_matrix".proxyPass = "http://dendrite"; "= /.well-known/matrix/server" = { extraConfig = '' add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; ''; return = "200 '${generators.toJSON { } { "m.server" = "${cfg.domain}:443"; }}'"; }; "= /.well-known/matrix/client" = { extraConfig = '' add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; ''; return = "200 '${generators.toJSON { } { "m.homeserver".base_url = "https://${cfg.domain}"; }}'"; }; }; }; postgresql = { enable = true; extraPostStart = [ '' $PSQL "${db}" -tAc 'GRANT ALL ON SCHEMA "public" TO "${db}"' '' ]; }; promtail.filters = [ { match = { selector = ''{syslog_identifier="dendrite"} |~ ".*Failed to fetch key for server.*"''; action = "drop"; }; } { match = { selector = ''{syslog_identifier="dendrite"} |~ ".*could not download key for.*"''; action = "drop"; }; } ]; }; services.postgresql = { ensureDatabases = [ db ]; ensureUsers = [ { name = db; ensureDBOwnership = true; } ]; }; systemd.services.dendrite = { description = "Dendrite Matrix homeserver"; wantedBy = [ "multi-user.target" ]; requires = [ "network.target" "postgresql.service" ]; after = [ "network.target" "postgresql.service" ]; serviceConfig = let needsPrivileges = cfg.port < 1024; capabilities = [ "" ] ++ optionals needsPrivileges [ "CAP_NET_BIND_SERVICE" ]; in { Restart = "on-failure"; ExecStartPre = let settings = { version = 2; global = { server_name = cfg.domain; private_key = config.secrets.dendrite-private-key.path; database = { connection_string = "postgresql://${db}@/${db}?host=/run/postgresql"; max_open_conns = 64; max_idle_connections = 8; }; cache = { max_size_estimated = "1gb"; max_age = "1h"; }; trusted_third_party_id_servers = [ "matrix.org" "nixos.org" "vector.im" ]; presence = { enable_inbound = false; enable_outbound = false; }; }; client_api = { registration_disabled = true; guests_disabled = true; registration_shared_secret = "$REGISTRATION_SHARED_SECRET"; }; media_api = { base_path = "/var/lib/dendrite/media_store"; max_file_size_bytes = 0; dynamic_thumbnails = true; max_thumbnail_generators = 8; thumbnail_sizes = [ { width = 32; height = 32; method = "crop"; } { width = 96; height = 96; method = "crop"; } { width = 640; height = 480; method = "scale"; } ]; }; logging = [ { type = "std"; level = "info"; } ]; }; in concatStringsSep " " [ (getExe pkgs.envsubst) "-i ${(pkgs.formats.yaml { }).generate "dendrite.yaml" settings}" "-o /run/dendrite/dendrite.yaml" ]; ExecStart = concatStringsSep " " [ (getExe' pkgs.dendrite "dendrite") "--config /run/dendrite/dendrite.yaml" "--http-bind-address 127.0.0.1:${toString cfg.port}" ]; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; EnvironmentFile = config.secrets.dendrite-environment-file.path; DynamicUser = true; StateDirectory = "dendrite"; RuntimeDirectory = "dendrite"; RuntimeDirectoryMode = "0700"; AmbientCapabilities = capabilities; CapabilityBoundingSet = capabilities; UMask = "0077"; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; PrivateTmp = true; PrivateUsers = !needsPrivileges; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectSystem = "strict"; ProtectProc = "noaccess"; ProcSubset = "pid"; RemoveIPC = true; RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" ]; }; }; topology = with cfg; { nodes.${this.hostname}.services.dendrite = { name = "Dendrite"; icon = "${inputs.homelab-svg-assets}/assets/matrix-white.svg"; info = domain; details.listen.text = "127.0.0.1:${toString port}"; }; }; }; }