tuwunel_core/config/mod.rs
1//! Loads and validates server configuration.
2//!
3//! Configuration types preserve startup sources for reloads and expose typed
4//! settings to the rest of the workspace. Field documentation also supplies the
5//! generated example configuration.
6
7pub mod check;
8mod format;
9mod identity_provider_serde;
10pub mod ip_source;
11mod jwt;
12pub mod manager;
13mod net;
14pub mod proxy;
15mod regenerate;
16pub mod room_version;
17pub mod sources;
18#[cfg(test)]
19mod tests;
20pub mod well_known;
21
22use std::{
23 collections::{BTreeMap, BTreeSet},
24 net::IpAddr,
25 path::{Path, PathBuf},
26};
27
28use bytesize::ByteSize;
29use derive_more::Debug;
30use either::{Either, Either::Left};
31pub use figment::{Figment, value::Value as FigmentValue};
32use figment::{
33 Profile, Provider,
34 providers::{Env, Format},
35};
36use ipnet::IpNet;
37use itertools::Itertools;
38use regex::RegexSet;
39use ruma::{
40 OwnedMxcUri, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
41 api::client::discovery::discover_support::ContactRole,
42};
43use serde::{Deserialize, de::IgnoredAny};
44use smallstr::SmallString;
45use tuwunel_macros::config_example_generator;
46use url::Url;
47
48pub use self::{
49 check::check,
50 ip_source::IpSource,
51 manager::Manager,
52 regenerate::{
53 Overwrite, RegenerateOptions, RegenerationSummary, example_config, regenerate_config,
54 write_example_config,
55 },
56 sources::Sources,
57};
58use self::{
59 format::Toml,
60 net::{ListeningAddr, ListeningPort},
61 proxy::ProxyConfig,
62};
63use crate::{
64 Err, Result, err, implement,
65 matrix::pdu::MAX_PREV_EVENTS,
66 redacted_debug,
67 utils::{self, bytes::deserialize_bytesize_usize, hash::Cost, sys},
68};
69
70// Later prefixes override earlier ones.
71pub(crate) const ENV_PREFIXES: [&str; 3] = ["CONDUIT_", "CONDUWUIT_", "TUWUNEL_"];
72
73/// Stores the configured localpart for the server's administrative user.
74///
75/// The inline budget covers ordinary Matrix localparts without a heap
76/// allocation. Longer valid localparts spill transparently.
77pub type ServerUserLocalpart = SmallString<[u8; 32]>;
78
79/// All the config options for tuwunel.
80#[expect(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
81#[derive(Clone, Deserialize)]
82#[config_example_generator(
83 filename = "tuwunel-example.toml",
84 section = "global",
85 undocumented = "# This item is undocumented. Please contribute documentation for it.",
86 header = r#"### Tuwunel Configuration
87###
88### THIS FILE IS GENERATED. CHANGES/CONTRIBUTIONS IN THE REPO WILL BE
89### OVERWRITTEN!
90###
91### You should rename this file before configuring your server. Changes to
92### documentation and defaults can be contributed in source code at
93### src/core/config/mod.rs. This file is generated when building.
94###
95### Any values pre-populated are the default values for said config option.
96###
97### At the minimum, you MUST edit all the config options to your environment
98### that say "YOU NEED TO EDIT THIS".
99###
100### For more information, see:
101### https://tuwunel.chat/configuration.html
102"#,
103 ignore = "catchall well_known tls rate_limiting ldap jwt appservice identity_provider \
104 storage_provider registration_terms smtp",
105 hidden = "allow_invalid_tls_certificates resolve_state_locally_shadow",
106 forbidden = "database_restore_backup force_migration"
107)]
108pub struct Config {
109 /// The server_name is the pretty name of this server. It is used as a
110 /// suffix for user and room IDs/aliases.
111 ///
112 /// See the docs for reverse proxying and delegation:
113 /// https://tuwunel.chat/deploying/generic.html#setting-up-the-reverse-proxy
114 ///
115 /// Also see the `[global.well_known]` config section at the very bottom.
116 ///
117 /// Examples of delegation:
118 /// - https://matrix.org/.well-known/matrix/server
119 /// - https://matrix.org/.well-known/matrix/client
120 ///
121 /// YOU NEED TO EDIT THIS. THIS CANNOT BE CHANGED AFTER WITHOUT A DATABASE
122 /// WIPE.
123 ///
124 /// example: "girlboss.ceo"
125 #[cfg_attr(test, serde(default = "default_server_name"))]
126 pub server_name: OwnedServerName,
127
128 /// This is the only directory where tuwunel will save its data, including
129 /// media. Note: this was previously "/var/lib/matrix-conduit".
130 ///
131 /// default: "/var/lib/tuwunel"
132 #[serde(default = "default_database_path")]
133 pub database_path: PathBuf,
134
135 /// Text which will be added to the end of the user's displayname upon
136 /// registration with a space before the text. In Conduit, this was the
137 /// lightning bolt emoji.
138 ///
139 /// To disable, set this to "" (an empty string).
140 ///
141 /// reloadable: yes
142 /// default: "💕"
143 #[serde(default = "default_new_user_displayname_suffix")]
144 pub new_user_displayname_suffix: String,
145
146 #[expect(clippy::doc_link_with_quotes)]
147 /// The default address (IPv4 or IPv6) tuwunel will listen on.
148 ///
149 /// If you are using Docker or a container NAT networking setup, this must
150 /// be "0.0.0.0".
151 ///
152 /// To listen on multiple addresses, specify a vector e.g. ["127.0.0.1",
153 /// "::1"]
154 ///
155 /// An address set here must bind or the server refuses to start. The
156 /// default is only a guess that both loopback families exist, so one of
157 /// them failing to bind is logged and skipped instead.
158 ///
159 /// default: ["127.0.0.1", "::1"]
160 #[serde(default)]
161 address: Option<ListeningAddr>,
162
163 /// The port(s) tuwunel will listen on.
164 ///
165 /// For reverse proxying, see:
166 /// https://tuwunel.chat/deploying/generic.html#setting-up-the-reverse-proxy
167 ///
168 /// If you are using Docker, don't change this, you'll need to map an
169 /// external port to this.
170 ///
171 /// To listen on multiple ports, specify a vector e.g. [8080, 8448]
172 ///
173 /// default: 8008
174 #[serde(default = "default_port")]
175 port: ListeningPort,
176
177 /// Configures direct TLS listeners.
178 ///
179 /// Values are read from the separate `[global.tls]` section. Certificate
180 /// and key paths must be supplied together before TLS is enabled.
181 // external structure; separate section
182 #[serde(default)]
183 pub tls: TlsConfig,
184
185 /// The UNIX socket tuwunel will listen on.
186 ///
187 /// Remember to make sure that your reverse proxy has access to this socket
188 /// file, either by adding your reverse proxy to the 'tuwunel' group or
189 /// granting world R/W permissions with `unix_socket_perms` (666 minimum).
190 ///
191 /// example: "/run/tuwunel/tuwunel.sock"
192 pub unix_socket_path: Option<PathBuf>,
193
194 /// The default permissions (in octal) to create the UNIX socket with.
195 ///
196 /// default: 660
197 #[serde(default = "default_unix_socket_perms")]
198 pub unix_socket_perms: u32,
199
200 /// Error on startup if any config option specified is unknown to Tuwunel.
201 ///
202 /// This is false by default to allow easier deprecation or removal of
203 /// config options in the future without breaking existing deployments. The
204 /// default behaviour is to simply warn on startup.
205 /// reloadable: yes
206 #[serde(default)]
207 pub error_on_unknown_config_opts: bool,
208
209 /// tuwunel supports online database backups using RocksDB's Backup engine
210 /// API. To use this, set a database backup path that tuwunel can write
211 /// to.
212 ///
213 /// For more information, see:
214 /// https://tuwunel.chat/maintenance.html#backups
215 ///
216 /// reloadable: yes
217 /// example: "/opt/tuwunel-db-backups"
218 pub database_backup_path: Option<PathBuf>,
219
220 /// The amount of online RocksDB database backups to keep/retain, if using
221 /// "database_backup_path", before deleting the oldest one. This must be at
222 /// least 1; "backup-database" is an error at 0 or below.
223 ///
224 /// reloadable: yes
225 /// default: 1
226 #[serde(default = "default_database_backups_to_keep")]
227 pub database_backups_to_keep: i16,
228
229 /// Restore this online database backup on startup, before the database is
230 /// opened. The value is a backup ID as listed by `!admin server
231 /// list-backups`, or 0 for the most recent backup. Set by the
232 /// `--restore-backup` command line argument, and refused from a
233 /// configuration file, where it would repeat the restore on every
234 /// startup.
235 pub database_restore_backup: Option<u32>,
236
237 /// Set this to any float value to multiply tuwunel's in-memory LRU caches
238 /// with such as "auth_chain_cache_capacity".
239 ///
240 /// May be useful if you have significant memory to spare to increase
241 /// performance.
242 ///
243 /// If you have low memory, reducing this may be viable.
244 ///
245 /// By default, the individual caches such as "auth_chain_cache_capacity"
246 /// are scaled by your CPU core count.
247 ///
248 /// default: 1.0
249 #[serde(
250 default = "default_cache_capacity_modifier",
251 alias = "conduit_cache_capacity_modifier"
252 )]
253 pub cache_capacity_modifier: f64,
254
255 /// Set this to any float value in megabytes for tuwunel to tell the
256 /// database engine that this much memory is available for database read
257 /// caches.
258 ///
259 /// May be useful if you have significant memory to spare to increase
260 /// performance.
261 ///
262 /// Similar to the individual LRU caches, this is scaled up with your CPU
263 /// core count.
264 ///
265 /// This defaults to 128.0 + (64.0 * CPU core count).
266 ///
267 /// default: varies by system
268 #[serde(default = "default_db_cache_capacity_mb")]
269 pub db_cache_capacity_mb: f64,
270
271 /// Set this to any float value in megabytes for tuwunel to tell the
272 /// database engine that this much memory is available for database write
273 /// caches.
274 ///
275 /// May be useful if you have significant memory to spare to increase
276 /// performance.
277 ///
278 /// Similar to the individual LRU caches, this is scaled up with your CPU
279 /// core count.
280 ///
281 /// This defaults to 48.0 + (4.0 * CPU core count).
282 ///
283 /// default: varies by system
284 #[serde(default = "default_db_write_buffer_capacity_mb")]
285 pub db_write_buffer_capacity_mb: f64,
286
287 /// Maximum number of entries in the RocksDB block cache shared by the
288 /// `pduid_pdu` and `eventid_outlierpdu` column families: a PDU's full
289 /// body, keyed by its PDU ID, and the same body keyed by event ID when
290 /// the PDU is an outlier.
291 ///
292 /// This is an entry count, not a byte size. The cache's actual capacity
293 /// in bytes is this value multiplied by an internal per-column-family
294 /// key+value size estimate, then multiplied by `cache_capacity_modifier`.
295 ///
296 /// Scaled by your CPU core count by default; see
297 /// `cache_capacity_modifier` to scale this along with the other
298 /// individual LRU caches at once.
299 ///
300 /// default: varies by system
301 #[serde(default = "default_pdu_cache_capacity")]
302 pub pdu_cache_capacity: u32,
303
304 /// Maximum number of entries in the RocksDB block cache for the
305 /// `authchainkey_authchain` column family: a room event's full auth
306 /// chain, keyed by the set of events the chain was derived from.
307 ///
308 /// Same entry-count semantics as `pdu_cache_capacity` above; see there
309 /// for how this becomes a byte capacity and how
310 /// `cache_capacity_modifier` applies.
311 ///
312 /// default: varies by system
313 #[serde(default = "default_auth_chain_cache_capacity")]
314 pub auth_chain_cache_capacity: u32,
315
316 /// Maximum number of entries in the RocksDB block cache for the
317 /// `shorteventid_eventid` column family: an event's full event ID,
318 /// looked up from its short event ID.
319 ///
320 /// Same entry-count semantics as `pdu_cache_capacity`.
321 ///
322 /// default: varies by system
323 #[serde(default = "default_shorteventid_cache_capacity")]
324 pub shorteventid_cache_capacity: u32,
325
326 /// Maximum number of entries in the RocksDB block cache for the
327 /// `eventid_shorteventid` column family: an event's short event ID,
328 /// looked up from its full event ID. The reverse lookup of
329 /// `shorteventid_cache_capacity`.
330 ///
331 /// Same entry-count semantics as `pdu_cache_capacity`.
332 ///
333 /// default: varies by system
334 #[serde(default = "default_eventidshort_cache_capacity")]
335 pub eventidshort_cache_capacity: u32,
336
337 /// Maximum number of entries in the RocksDB block cache for the
338 /// `eventid_pduid` column family: an event's PDU ID, looked up from its
339 /// full event ID.
340 ///
341 /// Same entry-count semantics as `pdu_cache_capacity`.
342 ///
343 /// default: varies by system
344 #[serde(default = "default_eventid_pdu_cache_capacity")]
345 pub eventid_pdu_cache_capacity: u32,
346
347 /// Maximum number of entries in the RocksDB block cache for the
348 /// `eventid_backoff` column family: the recent fetch, auth, upgrade, and
349 /// delivery outcomes an event is rate-gated against, keyed by federation
350 /// step, event ID, and time bucket.
351 ///
352 /// Same entry-count semantics as `pdu_cache_capacity`. A server working
353 /// through a large missing-ancestry gap reads this column heavily.
354 ///
355 /// default: varies by system
356 #[serde(default = "default_eventid_backoff_cache_capacity")]
357 pub eventid_backoff_cache_capacity: u32,
358
359 /// Maximum number of entries in the RocksDB block cache for the
360 /// `shortstatekey_statekey` column family: a state event's full state
361 /// key, looked up from its short state key.
362 ///
363 /// Same entry-count semantics as `pdu_cache_capacity`.
364 ///
365 /// default: varies by system
366 #[serde(default = "default_shortstatekey_cache_capacity")]
367 pub shortstatekey_cache_capacity: u32,
368
369 /// Maximum number of entries in the RocksDB block cache for the
370 /// `statekey_shortstatekey` column family: a state event's short state
371 /// key, looked up from its full state key. The reverse lookup of
372 /// `shortstatekey_cache_capacity`.
373 ///
374 /// Same entry-count semantics as `pdu_cache_capacity`.
375 ///
376 /// default: varies by system
377 #[serde(default = "default_statekeyshort_cache_capacity")]
378 pub statekeyshort_cache_capacity: u32,
379
380 /// Maximum number of entries in the RocksDB block cache for the
381 /// `servernameevent_data` column family: outbound federation events
382 /// (PDUs and EDUs) queued for delivery, keyed by destination server
383 /// name.
384 ///
385 /// Same entry-count semantics as `pdu_cache_capacity`.
386 ///
387 /// default: varies by system
388 #[serde(default = "default_servernameevent_data_cache_capacity")]
389 pub servernameevent_data_cache_capacity: u32,
390
391 /// Maximum number of entries in the RocksDB block cache for the
392 /// `mediaid_lazycontent` column family: preview images staged by the URL
393 /// preview fetcher, keyed by media ID, until a download promotes the row.
394 ///
395 /// Same entry-count semantics as `pdu_cache_capacity`, against a modal
396 /// 256 KiB entry. Staged rows are read once and deleted at promotion, so
397 /// this mostly holds index blocks, and a single outsized preview can
398 /// exceed the whole pool.
399 ///
400 /// default: 128
401 #[serde(default = "default_mediaid_lazycontent_cache_capacity")]
402 pub mediaid_lazycontent_cache_capacity: u32,
403
404 /// Maximum number of entries in the RocksDB block cache pool shared by the
405 /// `servername_destination` and `servername_override` column families: a
406 /// remote server's resolved federation destination, keyed by server name,
407 /// and the address override for a resolved hostname.
408 ///
409 /// Same entry-count semantics as `pdu_cache_capacity`, counted across the
410 /// pool rather than per column.
411 ///
412 /// default: varies by system
413 #[serde(default = "default_resolver_cache_capacity")]
414 pub resolver_cache_capacity: u32,
415
416 /// Maximum number of entries in the RocksDB block cache for the
417 /// `servername_status` column family: a remote server's recent
418 /// reachability outcome, keyed by server name and time bucket.
419 ///
420 /// Same entry-count semantics as `pdu_cache_capacity`.
421 ///
422 /// default: varies by system
423 #[serde(default = "default_servername_status_cache_capacity")]
424 pub servername_status_cache_capacity: u32,
425
426 /// Maximum number of entries in the in-memory LRU cache of decompressed
427 /// room state (a list of short state-info entries per state hash), used
428 /// by the state compressor to avoid re-walking
429 /// `shortstatehash_statediff` on every lookup.
430 ///
431 /// Unlike the other caches on this page, this one is not backed by
432 /// RocksDB: it is a plain in-process cache sized directly in entries,
433 /// with no per-entry byte-size conversion. `cache_capacity_modifier`
434 /// still applies to it.
435 ///
436 /// default: varies by system
437 #[serde(default = "default_stateinfo_cache_capacity")]
438 pub stateinfo_cache_capacity: u32,
439
440 /// Minimum time-to-live in seconds for room summary entries in the spaces
441 /// cache.
442 ///
443 /// reloadable: yes
444 /// default: 10800
445 #[serde(default = "default_spacehierarchy_cache_ttl_min")]
446 pub spacehierarchy_cache_ttl_min: u64,
447
448 /// Maximum time-to-live in seconds for room summary entries in the spaces
449 /// cache.
450 ///
451 /// reloadable: yes
452 /// default: 64800
453 #[serde(default = "default_spacehierarchy_cache_ttl_max")]
454 pub spacehierarchy_cache_ttl_max: u64,
455
456 /// Minimum timeout a client can request for long-polling sync. Requests
457 /// will be clamped up to this value if smaller.
458 ///
459 /// reloadable: yes
460 /// default: 5000
461 #[serde(default = "default_client_sync_timeout_min")]
462 pub client_sync_timeout_min: u64,
463
464 /// Default timeout for long-polling sync if a client does not request
465 /// another in their query-string.
466 ///
467 /// reloadable: yes
468 /// default: 30000
469 #[serde(default = "default_client_sync_timeout_default")]
470 pub client_sync_timeout_default: u64,
471
472 /// Maximum timeout a client can request for long-polling sync. Requests
473 /// will be clamped down to this value if larger.
474 ///
475 /// reloadable: yes
476 /// default: 90000
477 #[serde(default = "default_client_sync_timeout_max")]
478 pub client_sync_timeout_max: u64,
479
480 /// Custom DNS servers to query instead of the operating system's default
481 /// resolvers; when this list is non-empty, `/etc/resolv.conf` is never
482 /// read. Each entry is an IP address with an optional port, defaulting to
483 /// port 53. The servers are assumed to support both UDP and TCP on that
484 /// port; enable `query_over_tcp_only` if any of them is TCP-only.
485 ///
486 /// example: ["127.0.0.53", "1.1.1.1:5353", "[fd00::1]:53"]
487 ///
488 /// default: []
489 #[serde(default)]
490 pub dns_servers: Vec<String>,
491
492 /// Maximum entries stored in DNS memory-cache. The size of an entry may
493 /// vary so please take care if raising this value excessively. Only
494 /// decrease this when using an external DNS cache. Please note that
495 /// systemd-resolved does *not* count as an external cache, even when
496 /// configured to do so.
497 ///
498 /// default: 32768
499 #[serde(default = "default_dns_cache_entries")]
500 pub dns_cache_entries: u32,
501
502 /// Minimum time-to-live in seconds for entries in the DNS cache. The
503 /// default may appear high to most administrators; this is by design as the
504 /// exotic loads of federating to many other servers require a higher TTL
505 /// than many domains have set. Even when using an external DNS cache the
506 /// problem is shifted to that cache which is ignorant of its role for
507 /// this application and can adhere to many low TTL's increasing its load.
508 ///
509 /// default: 10800
510 #[serde(default = "default_dns_min_ttl")]
511 pub dns_min_ttl: u64,
512
513 /// Minimum time-to-live in seconds for NXDOMAIN entries in the DNS cache.
514 /// This value is critical for the server to federate efficiently.
515 /// NXDOMAIN's are assumed to not be returning to the federation and
516 /// aggressively cached rather than constantly rechecked.
517 ///
518 /// Defaults to 3 days as these are *very rarely* false negatives.
519 ///
520 /// default: 259200
521 #[serde(default = "default_dns_min_ttl_nxdomain")]
522 pub dns_min_ttl_nxdomain: u64,
523
524 /// Number of DNS nameserver retries after a timeout or error.
525 ///
526 /// default: 10
527 #[serde(default = "default_dns_attempts")]
528 pub dns_attempts: u16,
529
530 /// The number of seconds to wait for a reply to a DNS query. Please note
531 /// that recursive queries can take up to several seconds for some domains,
532 /// so this value should not be too low, especially on slower hardware or
533 /// resolvers.
534 ///
535 /// default: 10
536 #[serde(default = "default_dns_timeout")]
537 pub dns_timeout: u64,
538
539 /// Fallback to TCP on DNS errors. Set this to false if unsupported by
540 /// nameserver.
541 #[serde(default = "true_fn")]
542 pub dns_tcp_fallback: bool,
543
544 /// Enable to query all nameservers until the domain is found. Referred to
545 /// as "trust_negative_responses" in hickory_resolver. This can avoid
546 /// useless DNS queries if the first nameserver responds with NXDOMAIN or
547 /// an empty NOERROR response.
548 #[serde(default = "true_fn")]
549 pub query_all_nameservers: bool,
550
551 /// Enable using *only* TCP for querying your specified nameservers instead
552 /// of UDP.
553 ///
554 /// If you are running tuwunel in a container environment, this config
555 /// option may need to be enabled. For more details, see:
556 /// https://tuwunel.chat/troubleshooting.html#potential-dns-issues-when-using-docker
557 #[serde(default)]
558 pub query_over_tcp_only: bool,
559
560 /// DNS A/AAAA record lookup strategy
561 ///
562 /// Takes a number of one of the following options:
563 /// 1 - Ipv4Only (Only query for A records, no AAAA/IPv6)
564 ///
565 /// 2 - Ipv6Only (Only query for AAAA records, no A/IPv4)
566 ///
567 /// 3 - Ipv4AndIpv6 (Query for A and AAAA records in parallel, uses whatever
568 /// returns a successful response first)
569 ///
570 /// 4 - Ipv6thenIpv4 (Query for AAAA record, if that fails then query the A
571 /// record)
572 ///
573 /// 5 - Ipv4thenIpv6 (Query for A record, if that fails then query the AAAA
574 /// record)
575 ///
576 /// If you don't have IPv6 networking, then for better DNS performance it
577 /// may be suitable to set this to Ipv4Only (1) as you will never ever use
578 /// the AAAA record contents even if the AAAA record is successful instead
579 /// of the A record.
580 ///
581 /// default: 5
582 #[serde(default = "default_ip_lookup_strategy")]
583 pub ip_lookup_strategy: u8,
584
585 /// List of domain patterns resolved via the alternative path without any
586 /// persistent cache, very small memory cache, and no enforced TTL. This
587 /// is intended for internal network and application services which require
588 /// these specific properties. This path does not support federation or
589 /// general purposes.
590 ///
591 /// reloadable: yes
592 /// example: ["*\.dns\.podman$"]
593 ///
594 /// default: []
595 #[serde(default, with = "serde_regex")]
596 pub dns_passthru_domains: RegexSet,
597
598 /// Whether to resolve appservices via the alternative path; setting this is
599 /// superior to providing domains in `dns_passthru_domains` if all
600 /// appservices intend to be matched anyway. The overhead of matching regex
601 /// and maintaining the list of domains can be avoided.
602 #[serde(default)]
603 pub dns_passthru_appservices: bool,
604
605 /// Enable or disable case randomization for DNS queries. This is a security
606 /// mitigation where answer spoofing is prevented by having to exactly match
607 /// the question. Occasional errors seen in logs which may have lead you
608 /// here tend to be from overloading DNS. Nevertheless for servers which
609 /// are truly incapable this can be set to false.
610 ///
611 /// This currently defaults to false due to user reports regarding some
612 /// popular DNS caches which may or may not be patched soon. It may again
613 /// default to true in an upcoming release.
614 #[serde(default)]
615 pub dns_case_randomization: bool,
616
617 /// Max request size for file uploads. Accepts an integer byte count or a
618 /// string with SI/IEC suffix such as "24 MiB".
619 ///
620 /// default: 24 MiB
621 #[serde(
622 default = "default_max_request_size",
623 deserialize_with = "deserialize_bytesize_usize"
624 )]
625 pub max_request_size: usize,
626
627 /// Maximum size of a response body buffered from a remote server. Applies
628 /// to federation requests, push gateway and appservice transactions, and
629 /// remote media fetched for URL previews. A peer cannot be trusted to honor
630 /// a requested limit, so this bounds the response held in memory
631 /// regardless, guarding against a remote driving the process out of
632 /// memory. Accepts an integer byte count or a string with SI/IEC suffix
633 /// such as "256 MiB".
634 ///
635 /// default: 256 MiB
636 #[serde(
637 default = "default_max_response_size",
638 deserialize_with = "deserialize_bytesize_usize"
639 )]
640 pub max_response_size: usize,
641
642 /// Maximum number of concurrently pending (asynchronous) media uploads a
643 /// user can have.
644 ///
645 /// reloadable: yes
646 /// default: 5
647 #[serde(default = "default_max_pending_media_uploads")]
648 pub max_pending_media_uploads: usize,
649
650 /// The time in seconds before an unused pending MXC URI expires and is
651 /// removed.
652 ///
653 /// reloadable: yes
654 /// default: 86400 (24 hours)
655 #[serde(default = "default_media_create_unused_expiration_time")]
656 pub media_create_unused_expiration_time: u64,
657
658 /// The maximum number of media create requests per second allowed from a
659 /// single user.
660 ///
661 /// reloadable: yes
662 /// default: 10
663 #[serde(default = "default_media_rc_create_per_second")]
664 pub media_rc_create_per_second: u32,
665
666 /// The maximum burst count for media create requests from a single user.
667 ///
668 /// reloadable: yes
669 /// default: 50
670 #[serde(default = "default_media_rc_create_burst_count")]
671 pub media_rc_create_burst_count: u32,
672
673 /// reloadable: yes
674 /// default: 1024
675 ///
676 /// This limits how many prior events one recovery traversal visits. Raising
677 /// it increases recovery work; it is not a count of network requests.
678 #[serde(default = "default_max_fetch_prev_events")]
679 pub max_fetch_prev_events: u16,
680
681 /// Simultaneous backward-extremity upgrades during incoming-event recovery.
682 ///
683 /// Lower values reduce recovery load at the cost of latency. This does not
684 /// change the protocol `prev_events` bound or the traversal budget above.
685 ///
686 /// reloadable: yes
687 /// default: 20
688 #[serde(default = "default_prev_events_concurrency")]
689 pub prev_events_concurrency: u16,
690
691 /// Maximum time, in milliseconds, to wait for the missing prev_events of an
692 /// incoming timeline event to arrive on their own before fetching them over
693 /// federation. A gap that closes within this window skips the fetch. The
694 /// wait is event-driven and wakes the instant the events arrive, so this is
695 /// a ceiling on added latency, not a fixed cost. Set to 0 to fetch
696 /// immediately.
697 ///
698 /// reloadable: yes
699 /// default: 750
700 #[serde(default = "default_fetch_prev_wait_ms")]
701 pub fetch_prev_wait_ms: u64,
702
703 /// Default connection timeout (seconds) for outbound HTTP clients;
704 /// individual clients may override it.
705 ///
706 /// default: 10
707 #[serde(default = "default_request_conn_timeout")]
708 pub request_conn_timeout: u64,
709
710 /// Default read timeout (seconds) for outbound HTTP clients; individual
711 /// clients may override it.
712 ///
713 /// default: 35
714 #[serde(default = "default_request_timeout")]
715 pub request_timeout: u64,
716
717 /// Default whole-request timeout (seconds) for outbound HTTP clients.
718 ///
719 /// Set high enough not to cancel healthy requests while remaining a
720 /// backstop. Individual clients may override it.
721 ///
722 /// default: 320
723 #[serde(default = "default_request_total_timeout")]
724 pub request_total_timeout: u64,
725
726 /// Default idle connection pool timeout (seconds) for outbound HTTP
727 /// clients; individual clients may override it.
728 ///
729 /// default: 5
730 #[serde(default = "default_request_idle_timeout")]
731 pub request_idle_timeout: u64,
732
733 /// Default maximum idle connections per host for outbound HTTP clients;
734 /// individual clients may override it.
735 ///
736 /// default: 1
737 #[serde(default = "default_request_idle_per_host")]
738 pub request_idle_per_host: u16,
739
740 /// Allow the outbound HTTP client to negotiate gzip with other servers:
741 /// advertise it in Accept-Encoding and transparently decompress responses.
742 /// This covers federation, media, and URL preview traffic, and is separate
743 /// from `gzip_compression`, which compresses tuwunel's own responses.
744 ///
745 /// Enabled by default. Set to false to force the client to neither request
746 /// nor decompress gzip. Does nothing unless tuwunel was built with the
747 /// `gzip_compression` feature.
748 ///
749 /// default: true
750 #[serde(default = "true_fn")]
751 pub request_gzip: bool,
752
753 /// Allow the outbound HTTP client to negotiate brotli with other servers:
754 /// advertise it in Accept-Encoding and transparently decompress responses.
755 /// This covers federation, media, and URL preview traffic, and is separate
756 /// from `brotli_compression`, which compresses tuwunel's own responses.
757 ///
758 /// Enabled by default. Set to false to force the client to neither request
759 /// nor decompress brotli. Does nothing unless tuwunel was built with the
760 /// `brotli_compression` feature.
761 ///
762 /// default: true
763 #[serde(default = "true_fn")]
764 pub request_brotli: bool,
765
766 /// Allow the outbound HTTP client to negotiate zstd with other servers:
767 /// advertise it in Accept-Encoding and transparently decompress responses.
768 /// This covers federation, media, and URL preview traffic, and is separate
769 /// from `zstd_compression`, which compresses tuwunel's own responses.
770 ///
771 /// Enabled by default. Set to false to force the client to neither request
772 /// nor decompress zstd. Does nothing unless tuwunel was built with the
773 /// `zstd_compression` feature.
774 ///
775 /// default: true
776 #[serde(default = "true_fn")]
777 pub request_zstd: bool,
778
779 /// Federation well-known resolution connection timeout (seconds).
780 ///
781 /// default: 6
782 #[serde(default = "default_well_known_conn_timeout")]
783 pub well_known_conn_timeout: u64,
784
785 /// Federation HTTP well-known resolution request timeout (seconds).
786 ///
787 /// default: 10
788 #[serde(default = "default_well_known_timeout")]
789 pub well_known_timeout: u64,
790
791 /// Federation client request timeout (seconds). This applies to each read
792 /// from the remote server rather than to the request as a whole, which
793 /// remains bounded by `request_total_timeout`.
794 ///
795 /// default: 25
796 #[serde(default = "default_federation_timeout")]
797 pub federation_timeout: u64,
798
799 /// Timeout (seconds) for client-initiated federation key lookups, namely
800 /// /keys/query and /keys/claim against remote servers. Should be well
801 /// below `federation_timeout` so an interactive request to an unresponsive
802 /// server does not outlast the requesting client's own send deadline. A
803 /// lookup that exceeds this bound records a transient federation failure
804 /// for that server, so subsequent lookups back off instead of blocking
805 /// again.
806 ///
807 /// default: 8
808 #[serde(default = "default_federation_keys_timeout")]
809 pub federation_keys_timeout: u64,
810
811 /// Timeout (seconds) for each request in a bounded federation fanout.
812 ///
813 /// This applies to admin surveys and migrated room fanouts. A server that
814 /// exceeds the deadline is reported as timed out while other destinations
815 /// continue. Keep this below `federation_timeout` and above
816 /// `federation_keys_timeout`.
817 ///
818 /// reloadable: yes
819 /// default: 15
820 #[serde(default = "default_feds_timeout")]
821 pub feds_timeout: u64,
822
823 /// Federation client idle connection pool timeout (seconds).
824 ///
825 /// default: 25
826 #[serde(default = "default_federation_idle_timeout")]
827 pub federation_idle_timeout: u64,
828
829 /// Federation client max idle connections per host. Defaults to 1 as
830 /// generally the same open connection can be re-used.
831 ///
832 /// default: 1
833 #[serde(default = "default_federation_idle_per_host")]
834 pub federation_idle_per_host: u16,
835
836 /// Federation sender request timeout (seconds). The time it takes for the
837 /// remote server to process sent transactions can take a while.
838 ///
839 /// default: 180
840 #[serde(default = "default_sender_timeout")]
841 pub sender_timeout: u64,
842
843 /// Federation sender idle connection pool timeout (seconds).
844 ///
845 /// default: 180
846 #[serde(default = "default_sender_idle_timeout")]
847 pub sender_idle_timeout: u64,
848
849 /// Federation sender transaction retry backoff limit (seconds).
850 ///
851 /// reloadable: yes
852 /// default: 86400
853 #[serde(default = "default_sender_retry_backoff_limit")]
854 pub sender_retry_backoff_limit: u64,
855
856 /// Grace period (seconds) before the first retry of a federation
857 /// destination that has failed exactly once, applied in place of the
858 /// quadratic backoff curve so a single transient failure does not hold
859 /// delivery until the next backoff window. A second consecutive failure
860 /// returns to the backoff curve. Set to 0 to disable the grace and back off
861 /// from the first failure.
862 ///
863 /// default: 15
864 #[serde(default = "default_sender_retry_grace")]
865 pub sender_retry_grace: u64,
866
867 /// Appservice URL request connection timeout. Defaults to 35 seconds as
868 /// generally appservices are hosted within the same network.
869 ///
870 /// default: 35
871 #[serde(default = "default_appservice_timeout")]
872 pub appservice_timeout: u64,
873
874 /// Appservice URL idle connection pool timeout (seconds).
875 ///
876 /// default: 300
877 #[serde(default = "default_appservice_idle_timeout")]
878 pub appservice_idle_timeout: u64,
879
880 /// Notification gateway pusher idle connection pool timeout.
881 ///
882 /// default: 15
883 #[serde(default = "default_pusher_idle_timeout")]
884 pub pusher_idle_timeout: u64,
885
886 /// Maximum time to receive a request from a client (seconds).
887 ///
888 /// default: 75
889 #[serde(default = "default_client_receive_timeout")]
890 pub client_receive_timeout: u64,
891
892 /// Maximum time to process a request received from a client (seconds).
893 ///
894 /// default: 240
895 #[serde(default = "default_client_request_timeout")]
896 pub client_request_timeout: u64,
897
898 /// Maximum time to transmit a response to a client (seconds)
899 ///
900 /// default: 120
901 #[serde(default = "default_client_response_timeout")]
902 pub client_response_timeout: u64,
903
904 /// Grace period for clean shutdown of client requests (seconds).
905 ///
906 /// reloadable: yes
907 /// default: 15
908 #[serde(default = "default_client_shutdown_timeout")]
909 pub client_shutdown_timeout: u64,
910
911 /// Source of the client IP address for rate limiting, logging, and
912 /// security tooling.
913 ///
914 /// When unset (the default), the `ClientIp` extractor scans common
915 /// proxy headers in leftmost-IP mode (`X-Forwarded-For`, RFC 7239
916 /// `Forwarded`, `X-Real-IP`, `Fly-Client-IP`, `True-Client-IP`,
917 /// `CF-Connecting-IP`, `CloudFront-Viewer-Address`) and falls back
918 /// to the TCP peer address; clients can spoof their address via
919 /// request headers in that mode.
920 ///
921 /// When set, `ClientIp` resolves exclusively from the selected
922 /// source. The rightmost value is used for multi-valued headers;
923 /// only the proxy can append to the right, so this is resistant to
924 /// client spoofing.
925 ///
926 /// Supported values:
927 /// - "connect_info" - TCP peer address only (direct connections)
928 /// - "rightmost_x_forwarded_for" - nginx, Caddy
929 /// - "rightmost_forwarded" - RFC 7239 proxies
930 /// - "x_real_ip" - nginx `X-Real-IP`
931 /// - "cf_connecting_ip" - Cloudflare / cloudflared
932 /// - "true_client_ip" - Akamai, Cloudflare Enterprise
933 /// - "fly_client_ip" - Fly.io
934 /// - "cloudfront_viewer_address" - AWS CloudFront
935 ///
936 /// On Unix-socket deployments, leave this unset rather than setting
937 /// "connect_info"; that source requires a TCP peer address.
938 ///
939 /// WARNING: A header-based value without a trusted reverse proxy in
940 /// front of tuwunel allows clients to forge their IP. Changing this
941 /// value requires a server restart.
942 ///
943 /// default: unset
944 /// config-example: "connect_info"
945 #[serde(default)]
946 pub ip_source: Option<IpSource>,
947
948 /// Subnets whose TCP peers are treated as trusted and bypass the
949 /// `ip_source`-based extraction, falling through to the same
950 /// insecure header-scan + `ConnectInfo` fallback used when
951 /// `ip_source` is unset. Each entry is CIDR notation, including
952 /// the prefix length (use `/32` or `/128` to trust a single host).
953 ///
954 /// Loopback (`127.0.0.0/8`, `::1/128`) is always bypassed and
955 /// need not be listed.
956 ///
957 /// Use this when locally attached bridges or other server-side
958 /// clients connect from a private container or VPN subnet that
959 /// cannot carry the configured proxy header (e.g. a user-defined
960 /// Docker bridge network without `network_mode: host`).
961 ///
962 /// NOTE: If you configure an entire subnet here, be sure that it
963 /// does not include the address Tuwunel receives external traffic
964 /// from, i.e. that of your proxy. This would, for example, happen
965 /// if you deployed the proxy in a common bridge network with your
966 /// other components (e.g. in a Compose deployment) and specified
967 /// said network's subnet here. Traffic from the proxy would then
968 /// also have the bypass applied, rendering the `ip_source` option
969 /// effectively useless.
970 ///
971 /// WARNING: Any peer in these subnets can forge the client IP via
972 /// request headers. Only include subnets you control end-to-end.
973 /// Changing this value requires a server restart.
974 ///
975 /// default: []
976 /// config-example: ["172.18.0.0/16", "fd00::/8"]
977 #[expect(
978 clippy::doc_link_with_quotes,
979 reason = "config-example directive emits literal quoted strings, not an intra-doc link"
980 )]
981 #[serde(default)]
982 pub ip_source_trusted_subnets: Vec<IpNet>,
983
984 /// Grace period for clean shutdown of federation requests (seconds).
985 ///
986 /// reloadable: yes
987 /// default: 5
988 #[serde(default = "default_sender_shutdown_timeout")]
989 pub sender_shutdown_timeout: u64,
990
991 /// Enables registration. If set to false, no users can register on this
992 /// server.
993 ///
994 /// If set to true without a token configured, users can register with no
995 /// form of 2nd-step only if you set the following option to true:
996 /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
997 ///
998 /// If you would like registration only via token reg, please configure
999 /// `registration_token` or `registration_token_file`.
1000 /// reloadable: yes
1001 #[serde(default)]
1002 pub allow_registration: bool,
1003
1004 /// Enabling this setting opens registration to anyone without restrictions.
1005 /// This makes your server vulnerable to abuse
1006 /// reloadable: yes
1007 #[serde(default)]
1008 pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
1009
1010 /// A static registration token that new users will have to provide when
1011 /// creating an account. If unset and `allow_registration` is true,
1012 /// you must set
1013 /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
1014 /// to true to allow open registration without any conditions.
1015 ///
1016 /// YOU NEED TO EDIT THIS OR USE registration_token_file.
1017 ///
1018 /// reloadable: yes
1019 /// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
1020 ///
1021 /// display: sensitive
1022 pub registration_token: Option<String>,
1023
1024 /// Path to a file on the system that gets read for additional registration
1025 /// tokens. Multiple tokens can be added if you separate them with
1026 /// whitespace
1027 ///
1028 /// tuwunel must be able to access the file, and it must not be empty
1029 ///
1030 /// reloadable: yes
1031 /// example: "/etc/tuwunel/.reg_token"
1032 pub registration_token_file: Option<PathBuf>,
1033
1034 /// A pre-shared secret enabling out-of-band account creation via the
1035 /// Synapse-style `/_synapse/admin/v1/register` endpoint. The endpoint is
1036 /// only available when this is set. Requests authenticate by HMAC-SHA1
1037 /// keyed on this value; UIAA is bypassed.
1038 ///
1039 /// Use a high-entropy value (at least 32 bytes) and treat it as a
1040 /// secret of equivalent power to a server admin's access token.
1041 ///
1042 /// reloadable: yes
1043 /// example: "kZ2hN5pQ8wXyL4mR7tBfCgJxV3aD6sE1u"
1044 ///
1045 /// display: sensitive
1046 pub registration_shared_secret: Option<String>,
1047
1048 /// Path to a file containing the registration shared secret. Takes
1049 /// precedence over `registration_shared_secret`, and falls back to it when
1050 /// the file cannot be opened. Surrounding whitespace is trimmed off, so a
1051 /// trailing newline does not become part of the secret. A file which is
1052 /// present but blank resolves to no secret rather than falling back.
1053 ///
1054 /// reloadable: yes
1055 /// example: "/etc/tuwunel/.reg_shared_secret"
1056 pub registration_shared_secret_file: Option<PathBuf>,
1057
1058 /// Shared secret the Matrix Authentication Service (MAS) authenticates its
1059 /// provisioning calls with. When set, the `/_synapse/mas/*` endpoints
1060 /// accept only requests bearing this exact secret as their bearer token,
1061 /// rejecting all others; when unset, those endpoints reject every request.
1062 ///
1063 /// Use a high-entropy value and keep it identical to the secret configured
1064 /// on the MAS side.
1065 ///
1066 /// reloadable: yes
1067 /// example: "kZ2hN5pQ8wXyL4mR7tBfCgJxV3aD6sE1u"
1068 ///
1069 /// display: sensitive
1070 pub mas_secret: Option<String>,
1071
1072 /// Size of the Argon2id working buffer used to hash a new password, in 1
1073 /// KiB blocks.
1074 ///
1075 /// Each hash holds the buffer for its duration, so peak memory rises by
1076 /// roughly this much for every password operation in flight, with no bound
1077 /// on how many run at once. Stored hashes carry the values they were made
1078 /// with, so a change reaches an account only when its password is next set.
1079 ///
1080 /// Lowering this weakens the hash against parallel cracking hardware unless
1081 /// `argon2_t_cost` rises to compensate; the equivalent-strength pairs of
1082 /// (`argon2_m_cost`, `argon2_t_cost`) are (47104, 1), (19456, 2), (12288,
1083 /// 3), (9216, 4) and (7168, 5). The default and those pairs are the OWASP
1084 /// Argon2id recommendation:
1085 /// <https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id>
1086 ///
1087 /// reloadable: yes
1088 ///
1089 /// default: 19456
1090 #[serde(default = "default_argon2_m_cost")]
1091 pub argon2_m_cost: u32,
1092
1093 /// Number of passes an Argon2id hash makes over its working buffer.
1094 ///
1095 /// Raising it scales the CPU cost of a hash without changing its memory
1096 /// cost. See `argon2_m_cost` for the equivalent-strength pairs.
1097 ///
1098 /// reloadable: yes
1099 ///
1100 /// default: 2
1101 #[serde(default = "default_argon2_t_cost")]
1102 pub argon2_t_cost: u32,
1103
1104 /// Number of lanes an Argon2id working buffer is divided into.
1105 ///
1106 /// Lanes are computed sequentially here, so raising this changes the hash
1107 /// without buying any parallelism. `argon2_m_cost` must be at least eight
1108 /// times this value.
1109 ///
1110 /// reloadable: yes
1111 ///
1112 /// default: 1
1113 #[serde(default = "default_argon2_p_cost")]
1114 pub argon2_p_cost: u32,
1115
1116 /// Controls whether encrypted rooms and events are allowed.
1117 /// reloadable: yes
1118 #[serde(default = "true_fn")]
1119 pub allow_encryption: bool,
1120
1121 /// Controls whether locally-created rooms should be end-to-end encrypted by
1122 /// default. This option is equivalent to the one found in Synapse.
1123 ///
1124 /// Options:
1125 /// - "all": All created rooms are encrypted.
1126 /// - "invite": Any room created with `private_chat` or
1127 /// `trusted_private_chat` presets.
1128 /// - "none": Explicit value for no effect.
1129 /// - Other values default to no effect.
1130 ///
1131 /// reloadable: yes
1132 /// default: "none"
1133 #[serde(default)]
1134 pub encryption_enabled_by_default_for_room_type: Option<String>,
1135
1136 /// Controls whether federation is allowed or not. It is not recommended to
1137 /// disable this after installation due to potential federation breakage but
1138 /// this is technically not a permanent setting.
1139 #[serde(default = "true_fn")]
1140 pub allow_federation: bool,
1141
1142 /// (EXPERIMENTAL) Resolve the base event of a room context request by
1143 /// fetching it from federation when the server never received it.
1144 ///
1145 /// When a client requests
1146 /// `/_matrix/client/v3/rooms/{roomId}/context/{eventId}` for an event
1147 /// the server does not hold locally, the server fetches it from a room
1148 /// peer and persists it before responding, rather than returning a
1149 /// 404. This is gated on `allow_federation`; with federation disabled
1150 /// it has no effect. Other on-demand federation fetch sites are gated
1151 /// separately.
1152 ///
1153 /// reloadable: yes
1154 /// default: false
1155 #[serde(default)]
1156 pub fetch_unreceived_contexts_over_federation: bool,
1157
1158 /// Per-round ceiling on how many servers a federation event fetch contacts
1159 /// concurrently. Tightens the built-in fan-out profile of every fetch kind;
1160 /// it never widens one. 0 leaves the profiles unchanged.
1161 ///
1162 /// reloadable: yes
1163 /// default: 0
1164 #[serde(default)]
1165 pub fetch_fanout_max_width: usize,
1166
1167 /// Ceiling on how many staged rounds a federation event fetch runs before
1168 /// giving up. Tightens the built-in round count of every fetch kind; it
1169 /// never raises one. 0 leaves the profiles unchanged.
1170 ///
1171 /// reloadable: yes
1172 /// default: 0
1173 #[serde(default)]
1174 pub fetch_fanout_rounds: usize,
1175
1176 /// Maximum concurrency for bounded federation fanouts.
1177 ///
1178 /// This caps admin surveys and migrated room and key fanouts. Lower values
1179 /// trade sweep latency for load without dropping destinations. 0 selects
1180 /// the built-in bounded default.
1181 ///
1182 /// reloadable: yes
1183 /// default: 0
1184 #[serde(default)]
1185 pub feds_max_width: usize,
1186
1187 /// Maximum destinations an admin feds survey contacts without confirmation.
1188 ///
1189 /// A room exceeding this requires the command's confirmation flag. Higher
1190 /// values permit longer diagnostic commands on the serial admin worker.
1191 ///
1192 /// reloadable: yes
1193 /// default: 2048
1194 #[serde(default = "default_feds_destination_limit")]
1195 pub feds_destination_limit: usize,
1196
1197 /// Derive the state at an incoming federation event from locally held
1198 /// events when its previous events are stored but not yet resolved,
1199 /// instead of requesting /state_ids from the origin server. Only an event
1200 /// whose entire unresolved local ancestry is present participates; any
1201 /// other case still falls back to the federation state fetch. Disabling
1202 /// this restores the previous behavior of always fetching.
1203 ///
1204 /// reloadable: yes
1205 #[serde(default = "true_fn")]
1206 pub resolve_state_locally: bool,
1207
1208 /// Ceiling on how many unresolved local events one local state derivation
1209 /// may visit before falling back to the federation state fetch. Bounds
1210 /// worst-case memory and latency in rooms with a large unresolved
1211 /// backlog. 0 disables local derivation entirely.
1212 ///
1213 /// reloadable: yes
1214 /// default: 256
1215 #[serde(default = "default_resolve_state_locally_max")]
1216 pub resolve_state_locally_max: usize,
1217
1218 /// Legacy compatibility switch for local state derivation.
1219 ///
1220 /// A configured value of true disables local derivation and its memo
1221 /// shortcut. Use `resolve_state_locally = false` in new configurations.
1222 ///
1223 /// reloadable: yes
1224 /// display: hidden
1225 #[serde(default)]
1226 pub resolve_state_locally_shadow: bool,
1227
1228 /// Soft cap on the number of forward extremities tracked per room. When
1229 /// applying an incoming federation event would leave the room's frontier
1230 /// larger than this, the least useful leaves are pruned from the tracked
1231 /// set until it is back at the cap. Pruned events are not deleted and can
1232 /// still be referenced by other servers; this server merely stops citing
1233 /// them as frontier tips. Events created by this server are never pruned.
1234 /// 0 disables automatic pruning.
1235 ///
1236 /// reloadable: yes
1237 /// default: 60
1238 #[serde(default = "default_forward_extremities_max")]
1239 pub forward_extremities_max: usize,
1240
1241 /// Emergency bound on the per-room frontier. A frontier larger than this
1242 /// is cut down to it in a single step, ignoring the per-event pruning
1243 /// batch limit. Values at or below forward_extremities_max remove the
1244 /// pacing entirely, pruning straight to the cap in one step.
1245 ///
1246 /// reloadable: yes
1247 /// default: 256
1248 #[serde(default = "default_forward_extremities_emergency_max")]
1249 pub forward_extremities_emergency_max: usize,
1250
1251 /// Upper bound on how many forward extremities one incoming event may
1252 /// prune while the frontier is between the cap and the emergency bound.
1253 /// Spreads convergence across events to bound the work done by any single
1254 /// one. 0 stops paced pruning, leaving only the emergency bound.
1255 ///
1256 /// reloadable: yes
1257 /// default: 32
1258 #[serde(default = "default_forward_extremities_prune_batch")]
1259 pub forward_extremities_prune_batch: usize,
1260
1261 /// Sets the default `m.federate` property for newly created rooms when the
1262 /// client does not request one. If `allow_federation` is set to false at
1263 /// the same this value is set to false it then always overrides the client
1264 /// requested `m.federate` value to false.
1265 ///
1266 /// Rooms are fixed to the setting at the time of their creation and can
1267 /// never be changed; changing this value only affects new rooms.
1268 /// reloadable: yes
1269 #[serde(default = "true_fn")]
1270 pub federate_created_rooms: bool,
1271
1272 /// Allows federation requests to be made to itself
1273 ///
1274 /// This isn't intended and is very likely a bug if federation requests are
1275 /// being sent to yourself. This currently mainly exists for development
1276 /// purposes.
1277 /// reloadable: yes
1278 #[serde(default)]
1279 pub federation_loopback: bool,
1280
1281 /// Always calls /forget on behalf of the user if leaving a room. This is a
1282 /// part of MSC4267 "Automatically forgetting rooms on leave"
1283 /// reloadable: yes
1284 #[serde(default)]
1285 pub forget_forced_upon_leave: bool,
1286
1287 /// Set this to true to require authentication on the normally
1288 /// unauthenticated profile retrieval endpoints (GET)
1289 /// "/_matrix/client/v3/profile/{userId}".
1290 ///
1291 /// This can prevent profile scraping.
1292 /// reloadable: yes
1293 #[serde(default)]
1294 pub require_auth_for_profile_requests: bool,
1295
1296 /// Allow standard users to set or clear their display names through the
1297 /// client profile API.
1298 ///
1299 /// Server admins and appservices are always allowed to change display
1300 /// names.
1301 ///
1302 /// reloadable: yes
1303 /// default: true
1304 #[serde(default = "true_fn")]
1305 pub enable_set_displayname: bool,
1306
1307 /// Restrict client profile retrieval to the user themselves or users who
1308 /// currently share a joined room.
1309 ///
1310 /// Appservices are exempt, and other users are refused with 403
1311 /// `M_FORBIDDEN`. The server refuses to start, or to reload its
1312 /// configuration, with this enabled unless
1313 /// `require_auth_for_profile_requests` is enabled too. Federation profile
1314 /// lookups are unaffected; `allow_inbound_profile_lookup_federation_requests`
1315 /// controls those.
1316 ///
1317 /// reloadable: yes
1318 /// default: false
1319 #[serde(default)]
1320 pub limit_profile_requests_to_users_who_share_rooms: bool,
1321
1322 /// Preserve per-room profile overrides during a global profile update.
1323 ///
1324 /// When `true` (default), a profile change (displayname or avatar_url)
1325 /// arriving via the profile endpoints skips rooms whose current
1326 /// `m.room.member` already differs from the user's prior global
1327 /// profile. This is the natural behavior users expect after setting a
1328 /// per-room nickname or avatar with a client's `/myroomnick`-style
1329 /// command: a subsequent global change does not clobber the override.
1330 ///
1331 /// Set to `false` to always rewrite every joined room's member event
1332 /// to match the new global profile. That matches the literal spec
1333 /// reading.
1334 ///
1335 /// MSC4466 lets clients pick this per request via the
1336 /// `org.matrix.msc4466.propagate_to` query parameter
1337 /// (`all` / `unchanged` / `none`); an explicit value overrides this
1338 /// default in either direction.
1339 ///
1340 /// reloadable: yes
1341 /// default: true
1342 #[serde(default = "true_fn")]
1343 pub preserve_room_profile_overrides: bool,
1344
1345 /// Set this to true to allow your server's public room directory to be
1346 /// federated. Set this to false to protect against /publicRooms spiders,
1347 /// but will forbid external users from viewing your server's public room
1348 /// directory. If federation is disabled entirely (`allow_federation`), this
1349 /// is inherently false.
1350 /// reloadable: yes
1351 #[serde(default)]
1352 pub allow_public_room_directory_over_federation: bool,
1353
1354 /// Set this to true to allow your server's public room directory to be
1355 /// queried without client authentication (access token) through the Client
1356 /// APIs. Set this to false to protect against /publicRooms spiders.
1357 /// reloadable: yes
1358 #[serde(default)]
1359 pub allow_public_room_directory_without_auth: bool,
1360
1361 /// Allows room directory searches to match on partial room_id's when the
1362 /// search term starts with '!'.
1363 ///
1364 /// reloadable: yes
1365 /// default: true
1366 #[serde(default = "true_fn")]
1367 pub allow_public_room_search_by_id: bool,
1368
1369 /// Set this to false to limit results of rooms when searching by ID to
1370 /// those that would be found by an alias or other query; specifically
1371 /// those listed in the public rooms directory. By default this is set to
1372 /// true allowing any joinable room to match. This satisfies the Principle
1373 /// of Least Expectation when pasting a room_id into a search box with
1374 /// intent to join; many rooms simply opt-out of public listings. Therefor
1375 /// to prevent this feature from abuse, knowledge of several characters of
1376 /// the room_id is required before any results are returned.
1377 ///
1378 /// reloadable: yes
1379 /// default: true
1380 #[serde(default = "true_fn")]
1381 pub allow_unlisted_room_search_by_id: bool,
1382
1383 /// Show all local users in user directory.
1384 ///
1385 /// With this set to false, only users in public rooms or those that share
1386 /// a room with the user making the search will be shown. Appservice
1387 /// senders and users in exclusive appservice user namespaces stay hidden
1388 /// unless `show_appservice_users_in_user_directory` is also enabled.
1389 ///
1390 /// reloadable: yes
1391 /// default: false
1392 #[serde(default)]
1393 pub show_all_local_users_in_user_directory: bool,
1394
1395 /// Include appservice senders and users in exclusive appservice user
1396 /// namespaces in user directory searches.
1397 ///
1398 /// They remain subject to the normal room visibility rules unless
1399 /// `show_all_local_users_in_user_directory` is also enabled. Synapse has
1400 /// no equivalent and always hides them.
1401 ///
1402 /// reloadable: yes
1403 /// default: false
1404 #[serde(default)]
1405 pub show_appservice_users_in_user_directory: bool,
1406
1407 /// Allow guest users to access TURN credentials.
1408 ///
1409 /// This is the equivalent of Synapse's `turn_allow_guests` config option.
1410 /// Setting this to true allows guest users to call the endpoint
1411 /// `/_matrix/client/v3/voip/turnServer`.
1412 /// reloadable: yes
1413 #[serde(default)]
1414 pub turn_allow_guests: bool,
1415
1416 /// Set this to true to lock down your server's public room directory and
1417 /// only allow admins to publish rooms to the room directory. Unpublishing
1418 /// is still allowed by all users with this enabled.
1419 /// reloadable: yes
1420 #[serde(default)]
1421 pub lockdown_public_room_directory: bool,
1422
1423 /// Set this to true to allow federating device display names / allow
1424 /// external users to see your device display name. If federation is
1425 /// disabled entirely (`allow_federation`), this is inherently false. For
1426 /// privacy reasons, this is best left disabled.
1427 /// reloadable: yes
1428 #[serde(default)]
1429 pub allow_device_name_federation: bool,
1430
1431 /// Config option to allow or disallow incoming federation requests that
1432 /// obtain the profiles of our local users from
1433 /// `/_matrix/federation/v1/query/profile`
1434 ///
1435 /// Increases privacy of your local user's such as display names, but some
1436 /// remote users may get a false "this user does not exist" error when they
1437 /// try to invite you to a DM or room. Also can protect against profile
1438 /// spiders.
1439 ///
1440 /// This is inherently false if `allow_federation` is disabled
1441 /// reloadable: yes
1442 #[serde(
1443 default = "true_fn",
1444 alias = "allow_profile_lookup_federation_requests"
1445 )]
1446 pub allow_inbound_profile_lookup_federation_requests: bool,
1447
1448 /// Allow standard users to create rooms. Appservices and admins are always
1449 /// allowed to create rooms
1450 /// reloadable: yes
1451 #[serde(default = "true_fn")]
1452 pub allow_room_creation: bool,
1453
1454 /// Allow standard users to create room aliases. Appservices and admins are
1455 /// always allowed to create room aliases.
1456 /// reloadable: yes
1457 /// default: true
1458 #[serde(default = "true_fn")]
1459 pub allow_room_alias_creation: bool,
1460
1461 /// Set to false to disable users from joining or creating room versions
1462 /// that aren't officially supported by tuwunel. Unstable room versions may
1463 /// have flawed specifications or our implementation may be non-conforming.
1464 /// Correct operation may not be guaranteed, but incorrect operation may be
1465 /// tolerable and unnoticed.
1466 ///
1467 /// tuwunel officially supports room versions 6+. tuwunel has slightly
1468 /// experimental (though works fine in practice) support for versions 3 - 5.
1469 ///
1470 /// reloadable: yes
1471 /// default: true
1472 #[serde(default = "true_fn")]
1473 pub allow_unstable_room_versions: bool,
1474
1475 /// Set to true to enable experimental room versions.
1476 ///
1477 /// Unlike unstable room versions these versions are either under
1478 /// development, protype spec-changes, or somehow present a serious risk to
1479 /// the server's operation or database corruption. This is for developer use
1480 /// only.
1481 /// reloadable: yes
1482 #[serde(default)]
1483 pub allow_experimental_room_versions: bool,
1484
1485 /// MSC4284: ask the room's policy server to sign outgoing events. When a
1486 /// room has a valid `m.room.policy` state event, the homeserver requests a
1487 /// signature from that policy server's federation `/sign` endpoint before
1488 /// federating each event. Refusal aborts the local request; network or
1489 /// timeout failures fail open with a warn log so a transient policy-server
1490 /// outage does not silently take the room offline.
1491 ///
1492 /// reloadable: yes
1493 /// default: false
1494 #[serde(default)]
1495 pub enable_policy_servers: bool,
1496
1497 /// MSC4284: timeout (seconds) for requests to a room's policy server.
1498 /// Applies to both outbound `/sign` calls and inbound signature-fetches.
1499 ///
1500 /// reloadable: yes
1501 /// default: 5
1502 #[serde(default = "default_policy_server_request_timeout")]
1503 pub policy_server_request_timeout: u64,
1504
1505 /// MSC3925: fold the most recent message edit (an `m.replace` relation)
1506 /// into `unsigned.m.relations` on a served event as the full replacement
1507 /// event, on the client read endpoints. Off by default: it adds a typed
1508 /// index seek per served event and a server-authoritative edit summary that
1509 /// most clients reconstruct locally anyway, so it is opt-in.
1510 ///
1511 /// reloadable: yes
1512 /// default: false
1513 #[serde(default)]
1514 pub bundle_edit_relations: bool,
1515
1516 /// MSC2675/MSC3267: fold reference relations (`m.reference`) into
1517 /// `unsigned.m.relations` on a served event as `{ chunk: [{ event_id },
1518 /// ...] }`, on the client read endpoints. Off by default: no surveyed
1519 /// client renders reference bundles (references are plumbing for polls,
1520 /// beacons, and verification, which clients resolve directly), so most
1521 /// deployments gain nothing from the added read-time cost.
1522 ///
1523 /// reloadable: yes
1524 /// default: false
1525 #[serde(default)]
1526 pub bundle_reference_relations: bool,
1527
1528 /// Default room version tuwunel will create rooms with.
1529 ///
1530 /// The default is prescribed by the spec, but may be selected by developer
1531 /// recommendation. To prevent stale documentation we no longer list it
1532 /// here. It is only advised to override this if you know what you are
1533 /// doing, and by doing so, updates with new versions are precluded.
1534 /// reloadable: yes
1535 #[serde(default = "default_default_room_version")]
1536 pub default_room_version: RoomVersionId,
1537
1538 /// Default power-level overrides applied when this homeserver creates a new
1539 /// room.
1540 ///
1541 /// Uses the same top-level shape as the client `/createRoom`
1542 /// `power_level_content_override` parameter and is merged before any
1543 /// per-request override, so a client can still override it per room. Only
1544 /// affects newly created rooms. Top-level keys replace wholesale rather
1545 /// than deep-merging (matching the client parameter): setting `users` or
1546 /// `events` replaces the entire computed default submap for that key.
1547 ///
1548 /// reloadable: yes
1549 /// default: unset
1550 /// config-example: { users_default = 50 }
1551 #[serde(default)]
1552 pub default_power_level_content_override: Option<serde_json::Value>,
1553
1554 /// Configures Matrix discovery documents and related endpoints.
1555 ///
1556 /// Values are read from the separate `[global.well_known]` section. Client,
1557 /// server, support, and MatrixRTC responses consume these settings.
1558 // external structure; separate section
1559 #[serde(default)]
1560 pub well_known: WellKnownConfig,
1561
1562 /// Enables OTLP span export for Jaeger-compatible tracing.
1563 ///
1564 /// A build with performance measurements installs an OpenTelemetry layer
1565 /// when this is enabled. It defaults to false, and `jaeger_filter` selects
1566 /// the exported spans.
1567 #[serde(default)]
1568 pub allow_jaeger: bool,
1569
1570 /// default: "info"
1571 #[serde(default = "default_jaeger_filter")]
1572 pub jaeger_filter: String,
1573
1574 /// If the 'perf_measurements' compile-time feature is enabled, enables
1575 /// collecting folded stack trace profile of tracing spans using
1576 /// tracing_flame. The resulting profile can be visualized with inferno[1],
1577 /// speedscope[2], or a number of other tools.
1578 ///
1579 /// [1]: https://github.com/jonhoo/inferno
1580 /// [2]: www.speedscope.app
1581 #[serde(default)]
1582 pub tracing_flame: bool,
1583
1584 /// default: "info"
1585 #[serde(default = "default_tracing_flame_filter")]
1586 pub tracing_flame_filter: String,
1587
1588 /// default: "./tracing.folded"
1589 #[serde(default = "default_tracing_flame_output_path")]
1590 pub tracing_flame_output_path: String,
1591
1592 #[cfg(not(doctest))]
1593 /// Examples:
1594 ///
1595 /// - No proxy (default):
1596 ///
1597 /// proxy = "none"
1598 ///
1599 /// - For global proxy, create the section at the bottom of this file:
1600 ///
1601 /// [global.proxy]
1602 /// global = { url = "socks5h://localhost:9050" }
1603 ///
1604 /// - To proxy some domains:
1605 ///
1606 /// [global.proxy]
1607 /// [[global.proxy.by_domain]]
1608 /// url = "socks5h://localhost:9050"
1609 /// include = ["*.onion", "matrix.myspecial.onion"]
1610 /// exclude = ["*.myspecial.onion"]
1611 ///
1612 /// Include vs. Exclude:
1613 ///
1614 /// - If include is an empty list, it is assumed to be `["*"]`.
1615 ///
1616 /// - If a domain matches both the exclude and include list, the proxy will
1617 /// only be used if it was included because of a more specific rule than
1618 /// it was excluded. In the above example, the proxy would be used for
1619 /// `ordinary.onion`, `matrix.myspecial.onion`, but not
1620 /// `hello.myspecial.onion`.
1621 ///
1622 /// default: "none"
1623 #[serde(default)]
1624 pub proxy: ProxyConfig,
1625
1626 #[expect(clippy::doc_link_with_quotes)]
1627 /// Servers listed here will be used to gather public keys of other servers
1628 /// (notary trusted key servers).
1629 ///
1630 /// Currently, tuwunel doesn't support inbound batched key requests, so
1631 /// this list should only contain other Synapse servers.
1632 ///
1633 /// reloadable: yes
1634 /// example: ["matrix.org", "tchncs.de"]
1635 ///
1636 /// default: ["matrix.org"]
1637 #[serde(default = "default_trusted_servers")]
1638 pub trusted_servers: Vec<OwnedServerName>,
1639
1640 /// Whether to query the servers listed in trusted_servers first or query
1641 /// the origin server first. For best security, querying the origin server
1642 /// first is advised to minimize the exposure to a compromised trusted
1643 /// server. For maximum federation/join performance this can be set to true,
1644 /// however other options exist to query trusted servers first under
1645 /// specific high-load circumstances and should be evaluated before setting
1646 /// this to true.
1647 /// reloadable: yes
1648 #[serde(default)]
1649 pub query_trusted_key_servers_first: bool,
1650
1651 /// Whether to query the servers listed in trusted_servers first
1652 /// specifically on room joins. This option limits the exposure to a
1653 /// compromised trusted server to room joins only. The join operation
1654 /// requires gathering keys from many origin servers which can cause
1655 /// significant delays. Therefor this defaults to true to mitigate
1656 /// unexpected delays out-of-the-box. The security-paranoid or those willing
1657 /// to tolerate delays are advised to set this to false. Note that setting
1658 /// query_trusted_key_servers_first to true causes this option to be
1659 /// ignored.
1660 /// reloadable: yes
1661 #[serde(default = "true_fn")]
1662 pub query_trusted_key_servers_first_on_join: bool,
1663
1664 /// Only query trusted servers for keys and never the origin server. This is
1665 /// intended for clusters or custom deployments using their trusted_servers
1666 /// as forwarding-agents to cache and deduplicate requests. Notary servers
1667 /// do not act as forwarding-agents by default, therefor do not enable this
1668 /// unless you know exactly what you are doing.
1669 /// reloadable: yes
1670 #[serde(default)]
1671 pub only_query_trusted_key_servers: bool,
1672
1673 /// Maximum number of keys to request in each trusted server batch query.
1674 ///
1675 /// reloadable: yes
1676 /// default: 192
1677 #[serde(default = "default_trusted_server_batch_size")]
1678 pub trusted_server_batch_size: usize,
1679
1680 /// Maximum number of request batches in flight simultaneously when querying
1681 /// a trusted server.
1682 ///
1683 /// reloadable: yes
1684 /// default: 2
1685 #[serde(default = "default_trusted_server_batch_concurrency")]
1686 pub trusted_server_batch_concurrency: usize,
1687
1688 /// Max log level for tuwunel. Allows debug, info, warn, or error.
1689 ///
1690 /// See also:
1691 /// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
1692 ///
1693 /// **Caveat**:
1694 /// For release builds, the tracing crate is configured to only implement
1695 /// levels higher than error to avoid unnecessary overhead in the compiled
1696 /// binary from trace macros. For debug builds, this restriction is not
1697 /// applied.
1698 ///
1699 /// default: "info"
1700 #[serde(default = "default_log")]
1701 pub log: String,
1702
1703 /// Output logs with ANSI colours.
1704 ///
1705 /// Colours are suppressed while entries are submitted to journald, which
1706 /// takes the formatted line verbatim and reads control bytes in it as
1707 /// binary rather than text.
1708 #[serde(default = "true_fn", alias = "log_colours")]
1709 pub log_colors: bool,
1710
1711 /// Sets the log format to compact mode.
1712 #[serde(default)]
1713 pub log_compact: bool,
1714
1715 /// Configures the span events which will be outputted with the log.
1716 ///
1717 /// default: "none"
1718 #[serde(default = "default_log_span_events")]
1719 pub log_span_events: String,
1720
1721 /// Configures whether TUWUNEL_LOG EnvFilter matches values using regular
1722 /// expressions. See the tracing_subscriber documentation on Directives.
1723 ///
1724 /// default: true
1725 #[serde(default = "true_fn")]
1726 pub log_filter_regex: bool,
1727
1728 /// Toggles the display of ThreadId in tracing log output.
1729 ///
1730 /// default: false
1731 #[serde(default)]
1732 pub log_thread_ids: bool,
1733
1734 /// Redirects logging to standard error (stderr). The default is false for
1735 /// stdout. For those using our systemd features the redirection to stderr
1736 /// occurs as necessary and setting this option should not be required. We
1737 /// offer this option for all other users who desire such redirection.
1738 ///
1739 /// default: false
1740 #[serde(default)]
1741 pub log_to_stderr: bool,
1742
1743 /// Submits log output directly to the journald socket instead of the
1744 /// console when running under systemd. Each entry carries its actual
1745 /// severity as the journal priority, so tools such as `journalctl
1746 /// --priority warning` catch Tuwunel's warnings and errors; console output
1747 /// is captured by journald at a single fixed priority instead. The message
1748 /// is formatted exactly as the console formats it, span fields included,
1749 /// while the target, source location and every tracing field are attached
1750 /// as journal fields, the latter under an `F_` prefix for queries such as
1751 /// `journalctl F_ROOM_ID='!room:example.com'`. This option has no effect
1752 /// when not running under systemd, and the console is kept when the
1753 /// journald socket cannot be opened.
1754 ///
1755 /// default: true
1756 #[serde(default = "true_fn")]
1757 pub log_journald: bool,
1758
1759 /// Setting to false disables the logging/tracing system at a lower level.
1760 /// In contrast to configuring an empty `log` string where the system is
1761 /// still operating but muted, when this option is false the system was not
1762 /// initialized and is not operating. Changing this option has no effect
1763 /// after startup. This option is intended for developers and expert use
1764 /// only: configuring an empty log string is preferred over using this.
1765 ///
1766 /// default: true
1767 #[serde(default = "true_fn")]
1768 pub log_enable: bool,
1769
1770 /// Setting to false disables the logging/tracing system at a lower level
1771 /// similar to `log_enable`. In this case the system is configured normally,
1772 /// but not registered as the global handler in the final steps. This option
1773 /// is for developers and expert use only.
1774 ///
1775 /// default: true
1776 #[serde(default = "true_fn")]
1777 pub log_global_default: bool,
1778
1779 /// OpenID token expiration/TTL in seconds.
1780 ///
1781 /// These are the OpenID tokens that are primarily used for Matrix account
1782 /// integrations (e.g. Vector Integrations in Element), *not* OIDC/OpenID
1783 /// Connect/etc.
1784 ///
1785 /// reloadable: yes
1786 /// default: 3600
1787 #[serde(default = "default_openid_token_ttl")]
1788 pub openid_token_ttl: u64,
1789
1790 /// Allow an existing session to mint a login token for another client.
1791 /// This requires interactive authentication, but has security ramifications
1792 /// as a malicious client could use the mechanism to spawn more than one
1793 /// session. Enabled by default.
1794 ///
1795 /// reloadable: yes
1796 /// default: true
1797 #[serde(default = "true_fn")]
1798 pub login_via_existing_session: bool,
1799
1800 /// Whether to enable the login token route to accept login tokens at all.
1801 /// Login tokens may be generated by the server for authorization flows such
1802 /// as SSO; disabling tokens may break such features.
1803 ///
1804 /// This option is distinct from `login_via_existing_session` and does not
1805 /// carry the same security implications; the intent is to leave this
1806 /// enabled while disabling the former to prevent clients from commanding
1807 /// login token creation but without preventing the server from doing so.
1808 ///
1809 /// reloadable: yes
1810 /// default: true
1811 #[serde(default = "true_fn")]
1812 pub login_via_token: bool,
1813
1814 /// Whether to enable login using traditional user/password authorization
1815 /// flow.
1816 ///
1817 /// Set this option to false if you intend to allow logging in only using
1818 /// other mechanisms, such as SSO.
1819 ///
1820 /// reloadable: yes
1821 /// default: true
1822 #[serde(default = "true_fn")]
1823 pub login_with_password: bool,
1824
1825 /// Configures request rate limits, one `[global.rate_limiting]` subsection
1826 /// per kind of request.
1827 // external structure; separate section
1828 #[serde(default)]
1829 pub rate_limiting: RateLimits,
1830
1831 /// Login token expiration/TTL in milliseconds.
1832 ///
1833 /// These are short-lived tokens for the m.login.token endpoint.
1834 /// This is used to allow existing sessions to create new sessions.
1835 /// see login_via_existing_session.
1836 ///
1837 /// reloadable: yes
1838 /// default: 120000
1839 #[serde(default = "default_login_token_ttl")]
1840 pub login_token_ttl: u64,
1841
1842 /// Access token TTL in seconds.
1843 ///
1844 /// For clients that support refresh-tokens, the access-token provided on
1845 /// login will be invalidated after this amount of time and the client will
1846 /// be soft-logged-out until refreshing it.
1847 ///
1848 /// reloadable: yes
1849 /// default: 604800
1850 #[serde(default = "default_access_token_ttl")]
1851 pub access_token_ttl: u64,
1852
1853 /// Refresh token TTL in seconds.
1854 ///
1855 /// Refresh tokens are rejected once this lifetime elapses. Whether the
1856 /// deadline slides forward on each use or stays fixed at issuance is
1857 /// controlled by `refresh_token_idle_only`. The default of `0` disables
1858 /// refresh-token expiry entirely; a typical enabled value is `259200`
1859 /// (three days).
1860 ///
1861 /// reloadable: yes
1862 /// default: 0
1863 #[serde(default)]
1864 pub refresh_token_ttl: u64,
1865
1866 /// Whether `refresh_token_ttl` acts as an idle timeout or an absolute
1867 /// session lifetime.
1868 ///
1869 /// When `true` (default), each successful refresh resets the deadline to
1870 /// `now + refresh_token_ttl`. A session in continuous use never expires.
1871 /// When `false`, the deadline is fixed at first issuance and rotation
1872 /// carries it forward, forcing re-auth after `refresh_token_ttl`
1873 /// regardless of activity. This setting applies only to tokens issued with
1874 /// a nonzero refresh-token TTL; a zero TTL issues tokens without expiry.
1875 ///
1876 /// reloadable: yes
1877 /// default: true
1878 #[serde(default = "true_fn")]
1879 pub refresh_token_idle_only: bool,
1880
1881 /// Whether refresh-token expiry triggers a hard logout instead of a soft
1882 /// one.
1883 ///
1884 /// When `false` (default), an expired refresh token is rejected with
1885 /// `M_UNKNOWN_TOKEN` carrying `soft_logout: true`. The client can preserve
1886 /// E2EE keys and local state, then re-authenticate to resume the same
1887 /// device.
1888 ///
1889 /// When `true`, the device is removed entirely on expiry: the access
1890 /// token is invalidated, the device record is deleted, and the client is
1891 /// signalled with `soft_logout: false`. The next session is a brand-new
1892 /// device, so the client cannot recover E2EE history from local state
1893 /// alone; this is the CWE-613 stance and trades usability for that
1894 /// guarantee. Tokens issued with a zero refresh-token TTL do not expire,
1895 /// and changing the current TTL does not erase an expiry already stored.
1896 ///
1897 /// reloadable: yes
1898 /// default: false
1899 #[serde(default)]
1900 pub refresh_token_hard_logout: bool,
1901
1902 /// Grace window in seconds for a benign refresh-token double-submit.
1903 ///
1904 /// After a refresh token rotates, the spent token is retained for one
1905 /// generation so a later reuse is detectable. If that spent token is
1906 /// presented again within this window while its successor is still the
1907 /// device's current refresh token, the request is treated as a client that
1908 /// lost the rotated response: a fresh access token is issued for the
1909 /// unchanged refresh token rather than revoking the device. Outside the
1910 /// window, or once the chain has advanced, a replayed refresh token revokes
1911 /// the device as a suspected compromise. Set to `0` to treat every reuse as
1912 /// a compromise.
1913 ///
1914 /// reloadable: yes
1915 /// default: 15
1916 #[serde(default = "default_refresh_token_reuse_grace")]
1917 pub refresh_token_reuse_grace: u64,
1918
1919 /// Whether a detected refresh-token reuse revokes the device.
1920 ///
1921 /// When true (default), presenting a refresh token that was already rotated
1922 /// (outside the `refresh_token_reuse_grace` window) removes the device, the
1923 /// RFC 6819 stance that treats reuse as a compromised session. When false,
1924 /// the replayed request is rejected but the device is left intact, the
1925 /// laxer behaviour an operator fronting another OAuth client may prefer.
1926 ///
1927 /// reloadable: yes
1928 /// default: true
1929 #[serde(default = "true_fn")]
1930 pub refresh_token_reuse_revoke: bool,
1931
1932 /// Enable native registration and login on the built-in OIDC provider
1933 /// (next-gen auth), authenticating Matrix clients against this server's own
1934 /// accounts without a third-party `identity_provider`.
1935 ///
1936 /// When false (default), the OIDC server runs only to broker for a
1937 /// configured `identity_provider`, redirecting users to that upstream IdP.
1938 /// When true, an authorization request that selects no provider is served a
1939 /// login or registration page; `well_known.client` must be set. The login
1940 /// page offers local accounts and each configured identity provider (one
1941 /// single sign-on entry under `single_sso` or `sso_custom_providers_page`),
1942 /// and an explicit `idp_id` goes directly to that provider. Registration
1943 /// here honors `allow_registration`, the registration token, and
1944 /// `registration_terms` exactly as the client registration endpoint does.
1945 ///
1946 /// reloadable: yes
1947 /// default: false
1948 #[serde(default)]
1949 pub oidc_native_auth: bool,
1950
1951 /// Require OIDC clients (next-gen auth) to request an MSC2967 device scope.
1952 ///
1953 /// When false, a client that omits the `urn:matrix:client:device:<id>`
1954 /// scope is assigned a server-generated device id, which is echoed back in
1955 /// the granted scope. When true, the authorization-code grant is rejected
1956 /// unless the client supplies a device scope, per the MSC2967 expectation
1957 /// that the client owns its device id.
1958 ///
1959 /// reloadable: yes
1960 /// default: false
1961 #[serde(default)]
1962 pub oidc_require_device_scope: bool,
1963
1964 /// Require PKCE (RFC 7636) with the S256 method on the OIDC
1965 /// authorization-code grant.
1966 ///
1967 /// When true, the authorize endpoint rejects a request that carries no
1968 /// `code_challenge`, as MSC2964 mandates for public clients. A present
1969 /// challenge must always use S256; the `plain` method is rejected
1970 /// regardless of this setting. Set to false only as a transition escape
1971 /// hatch for a legacy client that cannot send a challenge.
1972 ///
1973 /// reloadable: yes
1974 /// default: true
1975 #[serde(default = "true_fn")]
1976 pub oidc_require_pkce: bool,
1977
1978 /// Reject an OIDC authorization-code grant that requests a scope this
1979 /// server does not recognise, instead of narrowing the granted scope down
1980 /// to the recognised tokens.
1981 ///
1982 /// When false (default), an unrecognised scope token is dropped and the
1983 /// narrowed `scope` is echoed back to the client per RFC 6749. When true,
1984 /// an unrecognised scope is rejected. `openid` and the MSC2967 device and
1985 /// api scopes (both spellings) are always recognised.
1986 ///
1987 /// reloadable: yes
1988 /// default: false
1989 #[serde(default)]
1990 pub oidc_strict_scope: bool,
1991
1992 /// Initial access token required to register an OIDC client dynamically
1993 /// (RFC 7591).
1994 ///
1995 /// When set, the registration endpoint requires the caller to present this
1996 /// token as an `Authorization: Bearer` credential. No Matrix client sends
1997 /// one, so any value here blocks next-gen auth login for every ordinary
1998 /// client, Element X included. Leave it empty unless every OAuth client on
1999 /// this server is registered out of band.
2000 ///
2001 /// display: sensitive
2002 /// reloadable: yes
2003 /// default:
2004 #[serde(default)]
2005 pub oidc_registration_access_token: String,
2006
2007 /// Allowlist of hostnames permitted in a dynamically-registered OIDC
2008 /// client's redirect_uris.
2009 ///
2010 /// When non-empty, every redirect_uri presented at registration must match
2011 /// this list or the registration is rejected. A redirect_uri with a host
2012 /// matches an entry naming that host, compared case-insensitively; a
2013 /// private-use scheme carries no host (RFC 8252, as in
2014 /// `io.element.android:/`) and matches an entry naming the scheme, so
2015 /// `["element.io", "io.element.android"]` covers a client on both web and
2016 /// mobile.
2017 ///
2018 /// The default (empty) imposes no restriction at registration, but then
2019 /// vouches for no client either, so every sign-in is subject to
2020 /// `oidc_require_client_approval`. Listing an entry vouches for whatever
2021 /// answers to it, so a scheme entry covers any app on the device claiming
2022 /// that scheme (RFC 8252 §8.6) and a host entry covers every path on it.
2023 ///
2024 /// reloadable: yes
2025 /// default: []
2026 #[serde(default)]
2027 pub oidc_registration_allowed_redirect_hosts: Vec<String>,
2028
2029 /// Ask the user to approve an unvetted OIDC client before its
2030 /// authorization code is issued.
2031 ///
2032 /// Dynamic client registration is open by default, so anyone can register a
2033 /// client with a redirect target they control and send an authorization
2034 /// link to one of your users; without this prompt a single click hands that
2035 /// client an authorization code for the account. When true (default), a
2036 /// sign-in whose redirect target is not vetted shows an approve/deny page
2037 /// naming the client first.
2038 ///
2039 /// A client is vetted when its redirect host or private-use scheme appears
2040 /// in `oidc_registration_allowed_redirect_hosts`, so listing the clients
2041 /// you serve restores a prompt-free sign-in. Because the prompt is a step
2042 /// a person takes, a server that shows it wants a `login_token_ttl` long
2043 /// enough to read the page.
2044 ///
2045 /// reloadable: yes
2046 /// default: true
2047 #[serde(default = "true_fn")]
2048 pub oidc_require_client_approval: bool,
2049
2050 /// Require a `client_uri` in dynamic client registration requests
2051 /// (RFC 7591 / MSC2966).
2052 ///
2053 /// When false (default), `client_uri` is optional; a client that supplies
2054 /// one still has it validated (https, host, no userinfo) and the other URLs
2055 /// in the request must share its host or a subdomain. When true, a
2056 /// registration without an https `client_uri` is rejected with
2057 /// `invalid_client_metadata`, enforcing the MSC2966 common-base model on
2058 /// every client.
2059 ///
2060 /// reloadable: yes
2061 /// default: false
2062 #[serde(default)]
2063 pub oidc_registration_require_client_uri: bool,
2064
2065 /// Token-bucket refill rate (requests per second) for the OIDC endpoints.
2066 ///
2067 /// Applies a shared per-client-IP throttle across the authorize, token,
2068 /// dynamic-registration and device-grant endpoints. The default of `0`
2069 /// disables the throttle, preserving open
2070 /// access; raise it together with `oidc_rc_burst_count` to protect a server
2071 /// exposed to a hostile network. The key is the client IP, so a rate low
2072 /// enough to bite a brute-force attempt can also throttle many users behind
2073 /// one NAT; size the burst accordingly.
2074 ///
2075 /// reloadable: yes
2076 /// default: 0
2077 #[serde(default)]
2078 pub oidc_rc_per_second: u32,
2079
2080 /// Token-bucket depth (burst size) for the OIDC endpoint throttle.
2081 ///
2082 /// The number of requests a single client IP may make in a burst before the
2083 /// `oidc_rc_per_second` refill rate governs. Ignored while
2084 /// `oidc_rc_per_second` is `0`.
2085 ///
2086 /// reloadable: yes
2087 /// default: 0
2088 #[serde(default)]
2089 pub oidc_rc_burst_count: u32,
2090
2091 /// Enable the rendezvous session APIs used to sign in with a QR code
2092 /// (MSC4108 and MSC4388).
2093 ///
2094 /// The rendezvous session relays the handshake between two devices before
2095 /// the OAuth device authorization grant completes the sign-in. This
2096 /// requires the built-in OIDC server. When disabled, clients hide the
2097 /// feature and the endpoints return an unrecognized response.
2098 ///
2099 /// reloadable: yes
2100 /// default: true
2101 #[serde(default = "true_fn")]
2102 pub rendezvous_enabled: bool,
2103
2104 /// Maximum size in bytes of a rendezvous session payload.
2105 ///
2106 /// QR sign-in handshake messages are normally much smaller than the
2107 /// default.
2108 ///
2109 /// reloadable: yes
2110 /// default: 4096
2111 #[serde(default = "default_rendezvous_session_max_bytes")]
2112 pub rendezvous_session_max_bytes: usize,
2113
2114 /// Seconds a rendezvous session lives after its last write.
2115 ///
2116 /// Each update restarts the window, but the device displaying the QR
2117 /// times the whole sign-in against the expiry advertised at creation.
2118 /// The default leaves time for an interactive account login on the
2119 /// approval page.
2120 ///
2121 /// reloadable: yes
2122 /// default: 600
2123 #[serde(default = "default_rendezvous_session_ttl")]
2124 pub rendezvous_session_ttl: u64,
2125
2126 /// Maximum number of concurrent rendezvous sessions.
2127 ///
2128 /// Creating a session beyond this limit evicts the oldest session instead
2129 /// of failing. A value of zero retains one session so creation remains
2130 /// available.
2131 ///
2132 /// reloadable: yes
2133 /// default: 100
2134 #[serde(default = "default_rendezvous_max_sessions")]
2135 pub rendezvous_max_sessions: usize,
2136
2137 /// Require an access token for MSC4388 discovery and session creation.
2138 ///
2139 /// When disabled, clients without an access token may discover and create
2140 /// MSC4388 sessions. The MSC4108 endpoint remains open in either mode.
2141 ///
2142 /// reloadable: yes
2143 /// default: true
2144 #[serde(default = "true_fn")]
2145 pub rendezvous_authenticated_only: bool,
2146
2147 /// Per-client-IP request refill rate for the MSC4388 rendezvous endpoints.
2148 ///
2149 /// A value of zero is treated as one request per second.
2150 ///
2151 /// reloadable: yes
2152 /// default: 10
2153 #[serde(default = "default_rendezvous_rc_per_second")]
2154 pub rendezvous_rc_per_second: u32,
2155
2156 /// Token-bucket depth for the MSC4388 rendezvous request throttle.
2157 ///
2158 /// This is the number of requests one client IP may make in a burst before
2159 /// `rendezvous_rc_per_second` governs. A value of zero is treated as one.
2160 ///
2161 /// reloadable: yes
2162 /// default: 20
2163 #[serde(default = "default_rendezvous_rc_burst_count")]
2164 pub rendezvous_rc_burst_count: u32,
2165
2166 /// Static TURN username to provide the client if not using a shared secret
2167 /// ("turn_secret"), It is recommended to use a shared secret over static
2168 /// credentials.
2169 /// reloadable: yes
2170 #[serde(default)]
2171 pub turn_username: String,
2172
2173 /// Static TURN password to provide the client if not using a shared secret
2174 /// ("turn_secret"). It is recommended to use a shared secret over static
2175 /// credentials.
2176 ///
2177 /// display: sensitive
2178 /// reloadable: yes
2179 #[serde(default)]
2180 pub turn_password: String,
2181
2182 #[expect(clippy::doc_link_with_quotes)]
2183 /// Vector list of TURN URIs/servers to use.
2184 ///
2185 /// Replace "example.turn.uri" with your TURN domain, such as the coturn
2186 /// "realm" config option. If using TURN over TLS, replace the URI prefix
2187 /// "turn:" with "turns:".
2188 ///
2189 /// reloadable: yes
2190 /// example: ["turn:example.turn.uri?transport=udp",
2191 /// "turn:example.turn.uri?transport=tcp"]
2192 ///
2193 /// default: []
2194 #[serde(default)]
2195 pub turn_uris: Vec<String>,
2196
2197 /// TURN secret to use for generating the HMAC-SHA1 hash apart of username
2198 /// and password generation.
2199 ///
2200 /// This is more secure, but if needed you can use traditional static
2201 /// username/password credentials.
2202 ///
2203 /// display: sensitive
2204 /// reloadable: yes
2205 #[serde(default)]
2206 pub turn_secret: Option<String>,
2207
2208 /// TURN secret to use that's read from the file path specified.
2209 ///
2210 /// This takes priority over "turn_secret", and falls back to it when the
2211 /// file cannot be opened. Surrounding whitespace is trimmed off, so a
2212 /// trailing newline does not become part of the secret. A file which is
2213 /// present but blank resolves to no secret rather than falling back.
2214 ///
2215 /// reloadable: yes
2216 /// example: "/etc/tuwunel/.turn_secret"
2217 pub turn_secret_file: Option<PathBuf>,
2218
2219 /// TURN TTL, in seconds.
2220 ///
2221 /// reloadable: yes
2222 /// default: 86400
2223 #[serde(default = "default_turn_ttl")]
2224 pub turn_ttl: u64,
2225
2226 #[expect(clippy::doc_link_with_quotes)]
2227 /// List/vector of room IDs or room aliases that tuwunel will make newly
2228 /// registered users join. The rooms specified must be rooms that you have
2229 /// joined at least once on the server, and must be public.
2230 ///
2231 /// reloadable: yes
2232 /// example: ["#tuwunel:grin.hu",
2233 /// "!l2xV0sd51lraysuRcsWVECge4NULaH3g-ou95vgDgiM"]
2234 ///
2235 /// default: []
2236 #[serde(default = "Vec::new")]
2237 pub auto_join_rooms: Vec<OwnedRoomOrAliasId>,
2238
2239 /// Config option to automatically deactivate the account of any user who
2240 /// attempts to join a:
2241 /// - banned room
2242 /// - forbidden room alias
2243 /// - room alias or ID with a forbidden server name
2244 ///
2245 /// This may be useful if all your banned lists consist of toxic rooms or
2246 /// servers that no good faith user would ever attempt to join, and
2247 /// to automatically remediate the problem without any admin user
2248 /// intervention.
2249 ///
2250 /// This will also make the user leave all rooms. Federation (e.g. remote
2251 /// room invites) are ignored here.
2252 ///
2253 /// Defaults to false as rooms can be banned for non-moderation-related
2254 /// reasons and this performs a full user deactivation.
2255 /// reloadable: yes
2256 #[serde(default)]
2257 pub auto_deactivate_banned_room_attempts: bool,
2258
2259 /// RocksDB log level. This is not the same as tuwunel's log level. This
2260 /// is the log level for the RocksDB engine/library which show up in your
2261 /// database folder/path as `LOG` files. tuwunel will log RocksDB errors
2262 /// as normal through tracing or panics if severe for safety.
2263 ///
2264 /// default: "error"
2265 #[serde(default = "default_rocksdb_log_level")]
2266 pub rocksdb_log_level: String,
2267
2268 /// Routes RocksDB log messages to standard error.
2269 ///
2270 /// `rocksdb_log_level` still filters the emitted records. When disabled,
2271 /// RocksDB uses the application's callback logger instead.
2272 #[serde(default)]
2273 pub rocksdb_log_stderr: bool,
2274
2275 /// Max RocksDB `LOG` file size before rotating. Accepts an integer byte
2276 /// count or a string with SI/IEC suffix such as "4 MiB".
2277 ///
2278 /// default: 4194304
2279 #[serde(
2280 default = "default_rocksdb_max_log_file_size",
2281 deserialize_with = "deserialize_bytesize_usize"
2282 )]
2283 pub rocksdb_max_log_file_size: usize,
2284
2285 /// Time in seconds before RocksDB will forcibly rotate logs.
2286 ///
2287 /// default: 0
2288 #[serde(default = "default_rocksdb_log_time_to_roll")]
2289 pub rocksdb_log_time_to_roll: usize,
2290
2291 /// Use RocksDB tunings tailored to spinning disks (HDDs). On NVMe or SSD
2292 /// storage, leave this disabled.
2293 ///
2294 /// When enabled, RocksDB skips compaction readahead and parallel file-open
2295 /// threads at startup. This option does not affect Direct IO; for that, see
2296 /// `rocksdb_direct_io`.
2297 #[serde(default)]
2298 pub rocksdb_optimize_for_spinning_disks: bool,
2299
2300 /// Enables direct-io to increase database performance via unbuffered I/O.
2301 ///
2302 /// For more details about direct I/O and RockDB, see:
2303 /// https://github.com/facebook/rocksdb/wiki/Direct-IO
2304 ///
2305 /// Set this option to false if the database resides on a filesystem which
2306 /// does not support direct-io like FUSE, or any form of complex filesystem
2307 /// setup such as possibly ZFS.
2308 #[serde(default = "true_fn")]
2309 pub rocksdb_direct_io: bool,
2310
2311 /// Amount of threads that RocksDB will use for parallelism on database
2312 /// operations such as cleanup, sync, flush and compaction. The stored value
2313 /// 0 selects the available logical thread count, with a minimum of two.
2314 ///
2315 /// default: 0
2316 #[serde(default = "default_rocksdb_parallelism_threads")]
2317 pub rocksdb_parallelism_threads: usize,
2318
2319 /// Maximum number of LOG files RocksDB will keep. This must *not* be set to
2320 /// 0. It must be at least 1. Defaults to 3 as these are not very useful
2321 /// unless troubleshooting/debugging a RocksDB bug.
2322 ///
2323 /// default: 3
2324 #[serde(default = "default_rocksdb_max_log_files")]
2325 pub rocksdb_max_log_files: usize,
2326
2327 /// Type of RocksDB database compression to use.
2328 ///
2329 /// Available options are "zstd", "bz2", "lz4", or "none".
2330 ///
2331 /// It is best to use ZSTD as an overall good balance between
2332 /// speed/performance, storage, IO amplification, and CPU usage. For more
2333 /// performance but less compression (more storage used) and less CPU usage,
2334 /// use LZ4.
2335 ///
2336 /// For more details, see:
2337 /// https://github.com/facebook/rocksdb/wiki/Compression
2338 ///
2339 /// "none" will disable compression.
2340 ///
2341 /// default: "zstd"
2342 #[serde(default = "default_rocksdb_compression_algo")]
2343 pub rocksdb_compression_algo: String,
2344
2345 /// Level of compression the specified compression algorithm for RocksDB to
2346 /// use.
2347 ///
2348 /// Default is 32767, which is internally read by RocksDB as the default
2349 /// magic number and translated to the library's default compression level
2350 /// as they all differ. See their `kDefaultCompressionLevel`.
2351 ///
2352 /// Note when using the default value we may override it with a setting
2353 /// tailored specifically tuwunel.
2354 ///
2355 /// default: 32767
2356 #[serde(default = "default_rocksdb_compression_level")]
2357 pub rocksdb_compression_level: i32,
2358
2359 /// Level of compression the specified compression algorithm for the
2360 /// bottommost level/data for RocksDB to use. Default is 32767, which is
2361 /// internally read by RocksDB as the default magic number and translated to
2362 /// the library's default compression level as they all differ. See their
2363 /// `kDefaultCompressionLevel`.
2364 ///
2365 /// Since this is the bottommost level (generally old and least used data),
2366 /// it may be desirable to have a very high compression level here as it's
2367 /// less likely for this data to be used. Research your chosen compression
2368 /// algorithm.
2369 ///
2370 /// Note when using the default value we may override it with a setting
2371 /// tailored specifically tuwunel.
2372 ///
2373 /// default: 32767
2374 #[serde(default = "default_rocksdb_bottommost_compression_level")]
2375 pub rocksdb_bottommost_compression_level: i32,
2376
2377 /// Whether to enable RocksDB's "bottommost_compression".
2378 ///
2379 /// At the expense of more CPU usage, this will further compress the
2380 /// database to reduce more storage. It is recommended to use ZSTD
2381 /// compression with this for best compression results. This may be useful
2382 /// if you're trying to reduce storage usage from the database.
2383 ///
2384 /// See https://github.com/facebook/rocksdb/wiki/Compression for more details.
2385 #[serde(default = "true_fn")]
2386 pub rocksdb_bottommost_compression: bool,
2387
2388 /// Database recovery mode (for RocksDB WAL corruption).
2389 ///
2390 /// Use this option when the server reports corruption and refuses to start.
2391 /// Set mode 2 (PointInTime) to cleanly recover from this corruption. The
2392 /// server will continue from the last good state, several seconds or
2393 /// minutes prior to the crash. Clients may have to run "clear-cache &
2394 /// reload" to account for the rollback. Upon success, you may reset the
2395 /// mode back to default and restart again. Please note in some cases the
2396 /// corruption error may not be cleared for at least 30 minutes of operation
2397 /// in PointInTime mode.
2398 ///
2399 /// As a very last ditch effort, if PointInTime does not fix or resolve
2400 /// anything, you can try mode 3 (SkipAnyCorruptedRecord) but this will
2401 /// leave the server in a potentially inconsistent state.
2402 ///
2403 /// The default mode 1 (TolerateCorruptedTailRecords) will automatically
2404 /// drop the last entry in the database if corrupted during shutdown, but
2405 /// nothing more. It is extraordinarily unlikely this will desynchronize
2406 /// clients. To disable any form of silent rollback set mode 0
2407 /// (AbsoluteConsistency).
2408 ///
2409 /// The options are:
2410 /// 0 = AbsoluteConsistency
2411 /// 1 = TolerateCorruptedTailRecords (default)
2412 /// 2 = PointInTime (use me if trying to recover)
2413 /// 3 = SkipAnyCorruptedRecord (you now voided your tuwunel warranty)
2414 ///
2415 /// For more information on these modes, see:
2416 /// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes
2417 ///
2418 /// For more details on recovering a corrupt database, see:
2419 /// https://tuwunel.chat/troubleshooting.html#database-corruption
2420 ///
2421 /// default: 1
2422 #[serde(default = "default_rocksdb_recovery_mode")]
2423 pub rocksdb_recovery_mode: u8,
2424
2425 /// Enables or disables paranoid SST file checks. This can improve RocksDB
2426 /// database consistency at a potential performance impact due to further
2427 /// safety checks ran.
2428 ///
2429 /// For more information, see:
2430 /// https://github.com/facebook/rocksdb/wiki/Online-Verification#columnfamilyoptionsparanoid_file_checks
2431 #[serde(default)]
2432 pub rocksdb_paranoid_file_checks: bool,
2433
2434 /// Enables or disables checksum verification in rocksdb at runtime.
2435 /// Checksums are usually hardware accelerated with low overhead; they are
2436 /// enabled in rocksdb by default. Older or slower platforms may see gains
2437 /// from disabling.
2438 ///
2439 /// default: true
2440 #[serde(default = "true_fn")]
2441 pub rocksdb_checksums: bool,
2442
2443 /// Enables the "atomic flush" mode in rocksdb. This option is not intended
2444 /// for users. It may be removed or ignored in future versions. Atomic flush
2445 /// may be enabled by the paranoid to possibly improve database integrity at
2446 /// the cost of performance.
2447 #[serde(default)]
2448 pub rocksdb_atomic_flush: bool,
2449
2450 /// Database repair mode (for RocksDB SST corruption).
2451 ///
2452 /// Use this option when the server reports corruption while running or
2453 /// panics. If the server refuses to start use the recovery mode options
2454 /// first. Corruption errors containing the acronym 'SST' which occur after
2455 /// startup will likely require this option.
2456 ///
2457 /// - Backing up your database directory is recommended prior to running the
2458 /// repair.
2459 ///
2460 /// - Disabling repair mode and restarting the server is recommended after
2461 /// running the repair.
2462 ///
2463 /// See https://tuwunel.chat/troubleshooting.html#database-corruption for more details on recovering a corrupt database.
2464 #[serde(default)]
2465 pub rocksdb_repair: bool,
2466
2467 /// Opens RocksDB in read-only mode.
2468 ///
2469 /// Writes are rejected and missing column families cannot be created. This
2470 /// mode is disabled by default.
2471 #[serde(default)]
2472 pub rocksdb_read_only: bool,
2473
2474 /// Opens RocksDB as a secondary follower of a primary instance.
2475 ///
2476 /// Writes are rejected while the primary's latest WAL can be replayed into
2477 /// this instance's view. Missing column families cannot be created.
2478 #[serde(default)]
2479 pub rocksdb_secondary: bool,
2480
2481 /// Enables idle CPU priority for compaction thread. This is not enabled by
2482 /// default to prevent compaction from falling too far behind on busy
2483 /// systems.
2484 #[serde(default)]
2485 pub rocksdb_compaction_prio_idle: bool,
2486
2487 /// Enables idle IO priority for compaction thread. This prevents any
2488 /// unexpected lag in the server's operation and is usually a good idea.
2489 /// Enabled by default.
2490 #[serde(default = "true_fn")]
2491 pub rocksdb_compaction_ioprio_idle: bool,
2492
2493 /// Enables RocksDB compaction. You should never ever have to set this
2494 /// option to false. If you for some reason find yourself needing to use
2495 /// this option as part of troubleshooting or a bug, please reach out to us
2496 /// in the tuwunel Matrix room with information and details.
2497 ///
2498 /// Disabling compaction will lead to a significantly bloated and
2499 /// explosively large database, gradually poor performance, unnecessarily
2500 /// excessive disk read/writes, and slower shutdowns and startups.
2501 #[serde(default = "true_fn")]
2502 pub rocksdb_compaction: bool,
2503
2504 /// Level of statistics collection. Some admin commands to display database
2505 /// statistics may require this option to be set. Database performance may
2506 /// be impacted by higher settings.
2507 ///
2508 /// Option is a number ranging from 0 to 6:
2509 /// 0 = No statistics.
2510 /// 1 = No statistics in release mode (default).
2511 /// 2 to 3 = Statistics with no performance impact.
2512 /// 3 to 5 = Statistics with possible performance impact.
2513 /// 6 = All statistics.
2514 ///
2515 /// default: 1
2516 #[serde(default = "default_rocksdb_stats_level")]
2517 pub rocksdb_stats_level: u8,
2518
2519 /// Ignores the list of dropped columns set by developers.
2520 ///
2521 /// This should be set to true when knowingly moving between versions in
2522 /// ways which are not recommended or otherwise forbidden, or for
2523 /// diagnostic and development purposes; requiring preservation across such
2524 /// movements.
2525 ///
2526 /// The developer's list of dropped columns is meant to safely reduce space
2527 /// by erasing data no longer in use. If this is set to true that storage
2528 /// will not be reclaimed as intended.
2529 ///
2530 /// default: false
2531 #[serde(default)]
2532 pub rocksdb_never_drop_columns: bool,
2533
2534 /// Configures RocksDB to not preallocate WAL logs.
2535 ///
2536 /// Normally, RocksDB allocates certain types of files by calling
2537 /// fallocate, writing the file contents, then truncating the logs to the
2538 /// proper size. This causes pathological disk space usage on btrfs due to
2539 /// how it interacts with its Copy-on-Write implementation. On ZFS,
2540 /// fallocate(2) for preallocation is unsupported and returns EOPNOTSUPP;
2541 /// only `FALLOC_FL_PUNCH_HOLE` and `FALLOC_FL_ZERO_RANGE` are implemented.
2542 ///
2543 /// Set this to false if you run the server on btrfs or ZFS, and do not
2544 /// touch it otherwise.
2545 ///
2546 /// default: true
2547 #[serde(default = "true_fn")]
2548 pub rocksdb_allow_fallocate: bool,
2549
2550 /// This is a password that can be configured that will let you login to the
2551 /// server bot account (currently `@conduit`) for emergency troubleshooting
2552 /// purposes such as recovering/recreating your admin room, or inviting
2553 /// yourself back.
2554 ///
2555 /// See https://tuwunel.chat/troubleshooting.html#lost-access-to-admin-room
2556 /// for other ways to get back into your admin room.
2557 ///
2558 /// Once this password is unset, all sessions will be logged out for
2559 /// security purposes.
2560 ///
2561 /// example: "F670$2CP@Hw8mG7RY1$%!#Ic7YA"
2562 ///
2563 /// display: sensitive
2564 pub emergency_password: Option<String>,
2565
2566 /// Suffix stripped from the gateway URL before constructing a push request.
2567 ///
2568 /// reloadable: yes
2569 /// default: "/_matrix/push/v1/notify"
2570 #[serde(default = "default_notification_push_path")]
2571 pub notification_push_path: String,
2572
2573 /// For compatibility and special purpose use only. Setting this option to
2574 /// true will not filter messages sent to pushers based on rules or actions.
2575 /// Everything will be sent to the pusher. This option is offered for
2576 /// several reasons, but should not be necessary:
2577 /// - Bypass to workaround bugs or outdated server-side ruleset support.
2578 /// - Allow clients to evaluate pushrules themselves (due to the above).
2579 /// - Hosting or companies which have custom pushers and internal needs.
2580 ///
2581 /// Note that setting this option to true will not affect the record of
2582 /// notifications found in the notifications pane.
2583 /// reloadable: yes
2584 #[serde(default)]
2585 pub push_everything: bool,
2586
2587 /// Evaluate the `im.nheko.msc3664.related_event_match` push rule condition,
2588 /// which matches on a property of the event that an incoming event relates
2589 /// to.
2590 ///
2591 /// A user can then write a push rule that notifies for replies or reactions
2592 /// to their own messages, which no other condition can express. Enabling
2593 /// this costs one extra event lookup for every event carrying a relation.
2594 ///
2595 /// The default `.im.nheko.msc3664.reply` push rule uses the condition.
2596 /// Disabling evaluation leaves the rule present but unable to match
2597 /// replies, while clients implementing MSC3664 may still evaluate it
2598 /// locally.
2599 ///
2600 /// reloadable: yes
2601 #[serde(default)]
2602 pub msc3664_related_event_match: bool,
2603
2604 /// Setting to false disables the heroes calculation made by sliding and
2605 /// legacy client sync. The heroes calculation is mandated by the Matrix
2606 /// specification and your client may not operate properly unless this
2607 /// option is set to true.
2608 ///
2609 /// This option is intended for custom software deployments seeking purely
2610 /// to minimize unused resources; the overall savings are otherwise
2611 /// negligible.
2612 /// reloadable: yes
2613 #[serde(default = "true_fn")]
2614 pub calculate_heroes: bool,
2615
2616 /// Allow local (your server only) presence updates/requests.
2617 ///
2618 /// Note that presence on tuwunel is very fast unlike Synapse's. If using
2619 /// outgoing presence, this MUST be enabled.
2620 /// reloadable: yes
2621 #[serde(default = "true_fn")]
2622 pub allow_local_presence: bool,
2623
2624 /// Allow incoming federated presence updates/requests.
2625 ///
2626 /// This option receives presence updates from other servers, but does not
2627 /// send any unless `allow_outgoing_presence` is true. Note that presence on
2628 /// tuwunel is very fast unlike Synapse's.
2629 /// reloadable: yes
2630 #[serde(default = "true_fn")]
2631 pub allow_incoming_presence: bool,
2632
2633 /// Allow outgoing presence updates/requests.
2634 ///
2635 /// This option sends presence updates to other servers, but does not
2636 /// receive any unless `allow_incoming_presence` is true. Note that presence
2637 /// on tuwunel is very fast unlike Synapse's. If using outgoing presence,
2638 /// you MUST enable `allow_local_presence` as well.
2639 /// reloadable: yes
2640 #[serde(default = "true_fn")]
2641 pub allow_outgoing_presence: bool,
2642
2643 /// How many seconds without presence updates before you become idle.
2644 /// Defaults to 5 minutes.
2645 ///
2646 /// default: 300
2647 #[serde(default = "default_presence_idle_timeout_s")]
2648 pub presence_idle_timeout_s: u64,
2649
2650 /// How many seconds without presence updates before you become offline.
2651 /// Defaults to 30 minutes.
2652 ///
2653 /// default: 1800
2654 #[serde(default = "default_presence_offline_timeout_s")]
2655 pub presence_offline_timeout_s: u64,
2656
2657 /// Enable the presence idle timer for remote users.
2658 ///
2659 /// Disabling is offered as an optimization for servers participating in
2660 /// many large rooms or when resources are limited. Disabling it may cause
2661 /// incorrect presence states (i.e. stuck online) to be seen for some remote
2662 /// users.
2663 #[serde(default = "true_fn")]
2664 pub presence_timeout_remote_users: bool,
2665
2666 /// Suppresses push notifications for users marked as active. (Experimental)
2667 ///
2668 /// When enabled, users with `Online` presence and recent activity
2669 /// (based on presence state and sync activity) won’t receive push
2670 /// notifications, reducing duplicate alerts while they're active
2671 /// on another client.
2672 ///
2673 /// Disabled by default to preserve legacy behavior.
2674 /// reloadable: yes
2675 #[serde(default)]
2676 pub suppress_push_when_active: bool,
2677
2678 /// Allow receiving incoming read receipts from remote servers.
2679 /// reloadable: yes
2680 #[serde(default = "true_fn")]
2681 pub allow_incoming_read_receipts: bool,
2682
2683 /// Allow sending read receipts to remote servers.
2684 /// reloadable: yes
2685 #[serde(default = "true_fn")]
2686 pub allow_outgoing_read_receipts: bool,
2687
2688 /// Allow outgoing typing updates to federation.
2689 /// reloadable: yes
2690 #[serde(default = "true_fn")]
2691 pub allow_outgoing_typing: bool,
2692
2693 /// Allow incoming typing updates from federation.
2694 /// reloadable: yes
2695 #[serde(default = "true_fn")]
2696 pub allow_incoming_typing: bool,
2697
2698 /// Maximum time federation user can indicate typing.
2699 ///
2700 /// reloadable: yes
2701 /// default: 30
2702 #[serde(default = "default_typing_federation_timeout_s")]
2703 pub typing_federation_timeout_s: u64,
2704
2705 /// Minimum time local client can indicate typing. This does not override a
2706 /// client's request to stop typing. It only enforces a minimum value in
2707 /// case of no stop request.
2708 ///
2709 /// reloadable: yes
2710 /// default: 15
2711 #[serde(default = "default_typing_client_timeout_min_s")]
2712 pub typing_client_timeout_min_s: u64,
2713
2714 /// Maximum time local client can indicate typing.
2715 ///
2716 /// reloadable: yes
2717 /// default: 45
2718 #[serde(default = "default_typing_client_timeout_max_s")]
2719 pub typing_client_timeout_max_s: u64,
2720
2721 /// Set this to true for tuwunel to compress HTTP response bodies using
2722 /// zstd. This option does nothing if tuwunel was not built with
2723 /// `zstd_compression` feature. Please be aware that enabling HTTP
2724 /// compression may weaken TLS. Most users should not need to enable this.
2725 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH
2726 /// before deciding to enable this.
2727 #[serde(default)]
2728 pub zstd_compression: bool,
2729
2730 /// Set this to true for tuwunel to compress HTTP response bodies using
2731 /// gzip. This option does nothing if tuwunel was not built with
2732 /// `gzip_compression` feature. Please be aware that enabling HTTP
2733 /// compression may weaken TLS. Most users should not need to enable this.
2734 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH before
2735 /// deciding to enable this.
2736 ///
2737 /// If you are in a large amount of rooms, you may find that enabling this
2738 /// is necessary to reduce the significantly large response bodies.
2739 #[serde(default)]
2740 pub gzip_compression: bool,
2741
2742 /// Set this to true for tuwunel to compress HTTP response bodies using
2743 /// brotli. This option does nothing if tuwunel was not built with
2744 /// `brotli_compression` feature. Please be aware that enabling HTTP
2745 /// compression may weaken TLS. Most users should not need to enable this.
2746 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH
2747 /// before deciding to enable this.
2748 #[serde(default)]
2749 pub brotli_compression: bool,
2750
2751 /// Set to true to allow user type "guest" registrations. Some clients like
2752 /// Element attempt to register guest users automatically.
2753 /// reloadable: yes
2754 #[serde(default)]
2755 pub allow_guest_registration: bool,
2756
2757 /// Set to true to log guest registrations in the admin room. Note that
2758 /// these may be noisy or unnecessary if you're a public homeserver.
2759 /// reloadable: yes
2760 #[serde(default)]
2761 pub log_guest_registrations: bool,
2762
2763 /// Set to true to allow guest registrations/users to auto join any rooms
2764 /// specified in `auto_join_rooms`.
2765 /// reloadable: yes
2766 #[serde(default)]
2767 pub allow_guests_auto_join_rooms: bool,
2768
2769 /// Prevent media from being embedded in frames, including same-origin frames.
2770 ///
2771 /// This is disabled by default for compatibility with attachment viewers.
2772 /// Enabling it adds `frame-ancestors 'none'` to the download and thumbnail
2773 /// Content-Security-Policy. Requires a restart, and reverse proxy headers may
2774 /// still prevent framing.
2775 #[serde(default)]
2776 pub media_deny_framing: bool,
2777
2778 /// Prevent inline styles in media documents.
2779 ///
2780 /// This is disabled by default so browser image viewers retain their
2781 /// presentation styles. Enabling it omits `style-src 'unsafe-inline'` from
2782 /// the download and thumbnail Content-Security-Policy. Requires a restart,
2783 /// and reverse proxy headers may still restrict styles.
2784 #[serde(default)]
2785 pub media_deny_inline_styles: bool,
2786
2787 /// Enable the legacy unauthenticated Matrix media repository endpoints.
2788 /// These endpoints consist of:
2789 /// - /_matrix/media/*/config
2790 /// - /_matrix/media/*/upload
2791 /// - /_matrix/media/*/preview_url
2792 /// - /_matrix/media/*/download/*
2793 /// - /_matrix/media/*/thumbnail/*
2794 ///
2795 /// The authenticated equivalent endpoints are always enabled.
2796 ///
2797 /// Defaults to false.
2798 #[serde(default)]
2799 pub allow_legacy_media: bool,
2800
2801 /// Fallback to requesting legacy unauthenticated media from remote servers.
2802 /// Unauthenticated media was removed in ~2024Q3; enabling this adds
2803 /// considerable federation requests which are unlikely to succeed.
2804 /// reloadable: yes
2805 #[serde(default)]
2806 pub request_legacy_media: bool,
2807
2808 /// When true, remote legacy content and thumbnail fetching is permitted.
2809 /// When false, those remote legacy requests are rejected.
2810 ///
2811 /// reloadable: yes
2812 #[serde(default = "true_fn")]
2813 pub freeze_legacy_media: bool,
2814
2815 /// Check consistency of the media directory at startup:
2816 /// 1. When `media_compat_file_link` is enabled, this check will upgrade
2817 /// media when switching back and forth between Conduit and tuwunel. Both
2818 /// options must be enabled to handle this.
2819 /// 2. When media is deleted from the directory, this check will also delete
2820 /// its database entry.
2821 ///
2822 /// If none of these checks apply to your use cases, and your media
2823 /// directory is significantly large setting this to false may reduce
2824 /// startup time.
2825 #[serde(default = "true_fn")]
2826 pub media_startup_check: bool,
2827
2828 /// Enable backward-compatibility with Conduit's media directory by creating
2829 /// symlinks of media.
2830 ///
2831 /// This option is only necessary if you plan on using Conduit again.
2832 /// Otherwise setting this to false reduces filesystem clutter and overhead
2833 /// for managing these symlinks in the directory. This is now disabled by
2834 /// default. You may still return to upstream Conduit but you have to run
2835 /// tuwunel at least once with this set to true and allow the
2836 /// media_startup_check to take place before shutting down to return to
2837 /// Conduit.
2838 #[serde(default)]
2839 pub media_compat_file_link: bool,
2840
2841 /// Prune missing media from the database as part of the media startup
2842 /// checks.
2843 ///
2844 /// This means if you delete files from the media directory the
2845 /// corresponding entries will be removed from the database. This is
2846 /// disabled by default because if the media directory is accidentally moved
2847 /// or inaccessible, the metadata entries in the database will be lost with
2848 /// sadness.
2849 #[serde(default)]
2850 pub prune_missing_media: bool,
2851
2852 /// Largest picture, in pixels, the thumbnailer will decode. Dimensions
2853 /// cost memory whatever the encoded file weighs, so a picture declaring
2854 /// more than this is left without a thumbnail instead of decoded. A video
2855 /// frame inherits the resolution of the video it came from and is bounded
2856 /// here too.
2857 ///
2858 /// 50 megapixels is roughly four 8K frames and more than any ordinary
2859 /// camera produces. Each pixel is budgeted at four bytes, so the default
2860 /// admits a decode of about 200 MiB. The budget is per in-flight request,
2861 /// which is what to size it against rather than one decode: thumbnail
2862 /// requests are not otherwise limited in number.
2863 ///
2864 /// reloadable: yes
2865 /// default: 50000000
2866 #[serde(default = "default_media_thumbnail_max_pixels")]
2867 pub media_thumbnail_max_pixels: u64,
2868
2869 /// Answer a request for an animated thumbnail with one, per MSC2705.
2870 ///
2871 /// A client that asks is served an animated GIF when the source carries a
2872 /// frame sequence and a still when it does not, and a client that does not
2873 /// ask is served a still either way. Encoding quantizes every frame on its
2874 /// own palette, which costs far more than a still, so the frame count and
2875 /// pixel budget below bound the work too. Turning this off leaves every
2876 /// thumbnail a still PNG.
2877 ///
2878 /// reloadable: yes
2879 /// default: true
2880 #[serde(default = "true_fn")]
2881 pub media_thumbnail_animated: bool,
2882
2883 /// Frames an animated thumbnail carries at most.
2884 ///
2885 /// A source with more of them is truncated to this and loops short, which
2886 /// the client is not told about, rather than being refused a thumbnail.
2887 /// Frames are also budgeted against `media_thumbnail_max_pixels` in total
2888 /// rather than one at a time, so a long animation stops at whichever limit
2889 /// it meets first. Both bound a request any remote server can make.
2890 ///
2891 /// reloadable: yes
2892 /// default: 50
2893 #[serde(default = "default_media_thumbnail_max_frames")]
2894 pub media_thumbnail_max_frames: usize,
2895
2896 /// Source jobs admitted for animated thumbnail generation.
2897 ///
2898 /// A job acquires a slot before reading the complete source. On a live
2899 /// request, it remains occupied through source I/O, animated storage, and
2900 /// subsequent still or video work until generation returns. A running
2901 /// blocking worker retains it after request cancellation. Static images and
2902 /// videos are admitted too because their bytes determine whether animation
2903 /// work is needed. A restart is required to apply a change.
2904 ///
2905 /// default: 4
2906 #[serde(default = "default_media_thumbnail_animated_concurrency")]
2907 pub media_thumbnail_animated_concurrency: usize,
2908
2909 /// Program invoked to extract a still frame from a video, giving videos
2910 /// uploaded without a thumbnail one anyway. Tuwunel decodes no video
2911 /// itself; the frame is scaled and cropped like any other image and the
2912 /// result is cached as an ordinary thumbnail.
2913 ///
2914 /// The list is an argument vector whose first entry is the program and
2915 /// whose remaining entries are its arguments. It is executed directly,
2916 /// never through a shell. Every argument has these tokens substituted
2917 /// before each call:
2918 ///
2919 /// - `{input}` path of a temporary file holding the source video.
2920 /// - `{width}` and `{height}` the requested thumbnail dimensions.
2921 ///
2922 /// The program writes one frame to standard output in any format the
2923 /// thumbnailer decodes: PNG, JPEG, WebP or GIF. Videos are served without
2924 /// a thumbnail while the list is empty.
2925 ///
2926 /// reloadable: yes
2927 /// example: [
2928 /// "ffmpeg", "-loglevel", "error", "-i", "{input}", "-vf", "thumbnail",
2929 /// "-frames:v", "1", "-f", "image2pipe", "-c:v", "mjpeg", "pipe:1",
2930 /// ]
2931 ///
2932 /// default: []
2933 #[serde(default)]
2934 pub media_video_thumbnail_command: Vec<String>,
2935
2936 /// Seconds a video thumbnail request may spend on frame extraction. One
2937 /// deadline spans the wait for a free slot, staging the video and the
2938 /// program itself, so a queue cannot compound it into a multiple. On
2939 /// expiry the program and anything it spawned are killed and the video is
2940 /// served without a thumbnail. Extraction requires a nonempty
2941 /// `media_video_thumbnail_command`.
2942 ///
2943 /// reloadable: yes
2944 /// default: 30
2945 #[serde(default = "default_media_video_thumbnail_timeout")]
2946 pub media_video_thumbnail_timeout: u64,
2947
2948 /// Video thumbnail extractions permitted to run at once. Decoding video
2949 /// costs far more than scaling an image, so requests past this limit wait
2950 /// for a slot instead of piling load onto the host. A slot is held from
2951 /// staging the video through to the program exiting, so this also bounds
2952 /// how many staged videos occupy the staging directory at once. Raise it
2953 /// where cores are spare; a restart is required to apply a change. This
2954 /// setting applies only when `media_video_thumbnail_command` is nonempty.
2955 ///
2956 /// default: 1
2957 #[serde(default = "default_media_video_thumbnail_concurrency")]
2958 pub media_video_thumbnail_concurrency: usize,
2959
2960 /// Largest video, in bytes, staged for the thumbnail program, and largest
2961 /// frame read back from it. A video past this is served without a
2962 /// thumbnail rather than written out, and a frame past it is refused
2963 /// rather than decoded from a truncation. Accepts an integer byte count or
2964 /// a string with SI/IEC suffix such as "128 MiB".
2965 /// Extraction requires a nonempty `media_video_thumbnail_command`.
2966 ///
2967 /// reloadable: yes
2968 /// default: 128 MiB
2969 #[serde(
2970 default = "default_media_video_thumbnail_max_size",
2971 deserialize_with = "deserialize_bytesize_usize"
2972 )]
2973 pub media_video_thumbnail_max_size: usize,
2974
2975 /// Directory a video is staged in for the thumbnail program to read, one
2976 /// file per running program, removed as soon as it exits. Leave unset to
2977 /// use a `tmp` subdirectory of the database path, which keeps large videos
2978 /// off the memory-backed `/tmp` a service manager commonly provides. Files
2979 /// left behind by a killed server are reclaimed at startup, including when
2980 /// `media_video_thumbnail_command` is empty. Extraction itself requires a
2981 /// nonempty command.
2982 ///
2983 /// reloadable: yes
2984 /// example: "/var/tmp/tuwunel"
2985 pub media_video_thumbnail_path: Option<PathBuf>,
2986
2987 /// List of storage providers to use for media. Providers can be configured
2988 /// below in respective sections designated by
2989 /// `global.storage_provider.<NAME>.<brand>` where `NAME` can be listed
2990 /// here.
2991 ///
2992 /// For advanced features and future extensions involving multiple providers
2993 /// the list may contain multiple entries. You MUST take note of other
2994 /// configuration options when listing multiple providers or resource
2995 /// duplication costs and poor performance can result.
2996 ///
2997 /// The list defaults to `["media"]` which is an implicit storage provider
2998 /// representing the media directory on the local filesystem. It can be
2999 /// altered by configuring `global.storage_provider.media.local` explicitly
3000 /// or disabled by omitting it from this list entirely. Users with existing
3001 /// deployments are advised to continue listing "media" as a fallback along
3002 /// with their new provider.
3003 ///
3004 /// reloadable: yes
3005 /// default: ["media"]
3006 #[serde(default = "default_media_storage_providers")]
3007 pub media_storage_providers: BTreeSet<String>,
3008
3009 /// List of configured storage providers where new media will be sent. When
3010 /// this list is not explicitly configured all entries in
3011 /// `media_storage_providers` are used as default.
3012 ///
3013 /// This list is important for users passively migrating to a new media
3014 /// storage provider by only writing to one while querying the other as a
3015 /// fallback.
3016 ///
3017 /// For example:
3018 ///
3019 /// `media_storage_providers = ["media", "media_on_s3"]`
3020 /// `store_media_on_providers = ["media_on_s3"]`
3021 ///
3022 /// Entries in this list must also be listed in `media_storage_providers`.
3023 ///
3024 /// reloadable: yes
3025 /// default: []
3026 #[serde(default)]
3027 pub store_media_on_providers: BTreeSet<String>,
3028
3029 /// Redirect local media downloads to a presigned object-store URL when the
3030 /// client sends `allow_redirect=true` (MSC3860). When a configured storage
3031 /// provider can presign the object (S3), the download responds with a 307
3032 /// to a short-lived URL instead of proxying the bytes. Media held only on
3033 /// the local filesystem is always served directly.
3034 ///
3035 /// reloadable: yes
3036 /// default: false
3037 #[serde(default)]
3038 pub media_allow_redirect: bool,
3039
3040 /// Vector list of regex patterns of server names that tuwunel will refuse
3041 /// to download remote media from.
3042 ///
3043 /// reloadable: yes
3044 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
3045 ///
3046 /// default: []
3047 #[serde(default, with = "serde_regex")]
3048 pub prevent_media_downloads_from: RegexSet,
3049
3050 /// List of forbidden server names via regex patterns that we will block
3051 /// incoming AND outgoing federation with, and block client room joins /
3052 /// remote user invites.
3053 ///
3054 /// This check is applied on the room ID, room alias, sender server name,
3055 /// sender user's server name, inbound federation X-Matrix origin, and
3056 /// outbound federation handler.
3057 ///
3058 /// Basically "global" ACLs.
3059 ///
3060 /// The server's own name is always permitted and is never subject to this
3061 /// list.
3062 ///
3063 /// reloadable: yes
3064 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
3065 ///
3066 /// default: []
3067 #[serde(default, with = "serde_regex")]
3068 pub forbidden_remote_server_names: RegexSet,
3069
3070 /// (EXPERIMENTAL) The behavior of this option will change; the
3071 /// _experimental suffix will be removed for that change in an upcoming
3072 /// release.
3073 ///
3074 /// List of allowed server names via regex patterns. This is an allow-list
3075 /// rather than a deny-list with all the same details as its counterpart in
3076 /// `forbidden_remote_server_names`.
3077 ///
3078 /// This feature becomes active when this list has one or more entries;
3079 /// everything not matching is denied. By default it is empty and inactive.
3080 ///
3081 /// The server's own name is always permitted and is never subject to this
3082 /// list.
3083 ///
3084 /// Entries in `forbidden_remote_server_names` are still applied after
3085 /// this is applied. This allows you to match e.g. "*\.example\.com" here
3086 /// while still singling out "bad\.example\.com" for exclusion.
3087 ///
3088 /// reloadable: yes
3089 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
3090 ///
3091 /// default: []
3092 #[serde(default, with = "serde_regex")]
3093 pub allowed_remote_server_names_experimental: RegexSet,
3094
3095 /// List of forbidden server names via regex patterns that we will block all
3096 /// outgoing federated room directory requests for. Useful for preventing
3097 /// our users from wandering into bad servers or spaces.
3098 ///
3099 /// reloadable: yes
3100 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
3101 ///
3102 /// default: []
3103 #[serde(default, with = "serde_regex")]
3104 pub forbidden_remote_room_directory_server_names: RegexSet,
3105
3106 #[expect(clippy::doc_link_with_quotes)]
3107 /// Vector list of IPv4 and IPv6 CIDR ranges / subnets *in quotes* that you
3108 /// do not want tuwunel to send outbound requests to. Defaults to
3109 /// RFC1918, unroutable, loopback, multicast, and testnet addresses for
3110 /// security.
3111 ///
3112 /// Please be aware that this is *not* a guarantee. You should be using a
3113 /// firewall with zones as doing this on the application layer may have
3114 /// bypasses.
3115 ///
3116 /// Proxy endpoints selected by configuration or environment variables are
3117 /// exempt so a private forward proxy can be reached. Destination addresses
3118 /// remain filtered for direct requests and for locally resolving `socks4`
3119 /// and `socks5` proxies. HTTP(S) forward proxies and `socks4a` or
3120 /// `socks5h` resolve the destination remotely, so their egress policy must
3121 /// enforce the destination network boundary instead.
3122 ///
3123 /// To disable, set this to be an empty vector (`[]`).
3124 ///
3125 /// Defaults to:
3126 /// ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
3127 /// "192.168.0.0/16", "100.64.0.0/10", "192.0.0.0/24", "169.254.0.0/16",
3128 /// "192.88.99.0/24", "198.18.0.0/15", "192.0.2.0/24", "198.51.100.0/24",
3129 /// "203.0.113.0/24", "224.0.0.0/4", "::1/128", "fe80::/10", "fc00::/7",
3130 /// "2001:db8::/32", "ff00::/8", "fec0::/10"]
3131 #[serde(default = "default_ip_range_denylist")]
3132 pub ip_range_denylist: Vec<String>,
3133
3134 /// Optional IP address or network interface-name to bind as the source of
3135 /// URL preview requests. If not set, it will not bind to a specific
3136 /// address or interface.
3137 ///
3138 /// Interface names only supported on Linux, Android, and Fuchsia platforms;
3139 /// all other platforms can specify the IP address. To list the interfaces
3140 /// on your system, use the command `ip link show`.
3141 ///
3142 /// example: `"eth0"` or `"1.2.3.4"`
3143 ///
3144 /// default:
3145 #[serde(default, with = "either::serde_untagged_optional")]
3146 pub url_preview_bound_interface: Option<Either<IpAddr, String>>,
3147
3148 /// Vector list of domains allowed to send requests to for URL previews.
3149 ///
3150 /// This is a *contains* match, not an explicit match. Putting "google.com"
3151 /// will match "https://google.com" and
3152 /// "http://mymaliciousdomainexamplegoogle.com" Setting this to "*" will
3153 /// allow all URL previews. Please note that this opens up significant
3154 /// attack surface to your server, you are expected to be aware of the risks
3155 /// by doing so.
3156 ///
3157 /// reloadable: yes
3158 /// default: []
3159 #[serde(default)]
3160 pub url_preview_domain_contains_allowlist: Vec<String>,
3161
3162 /// Vector list of explicit domains allowed to send requests to for URL
3163 /// previews.
3164 ///
3165 /// This is an *explicit* match, not a contains match. Putting "google.com"
3166 /// will match "https://google.com", "http://google.com", but not
3167 /// "https://mymaliciousdomainexamplegoogle.com". Setting this to "*" will
3168 /// allow all URL previews. Please note that this opens up significant
3169 /// attack surface to your server, you are expected to be aware of the risks
3170 /// by doing so.
3171 ///
3172 /// reloadable: yes
3173 /// default: []
3174 #[serde(default)]
3175 pub url_preview_domain_explicit_allowlist: Vec<String>,
3176
3177 /// Vector list of explicit domains not allowed to send requests to for URL
3178 /// previews.
3179 ///
3180 /// This is an *explicit* match, not a contains match. Putting "google.com"
3181 /// will match "https://google.com", "http://google.com", but not
3182 /// "https://mymaliciousdomainexamplegoogle.com". The denylist is checked
3183 /// first before allowlist. Setting this to "*" will not do anything.
3184 ///
3185 /// reloadable: yes
3186 /// default: []
3187 #[serde(default)]
3188 pub url_preview_domain_explicit_denylist: Vec<String>,
3189
3190 /// Vector list of URLs allowed to send requests to for URL previews.
3191 ///
3192 /// Note that this is a *contains* match, not an explicit match. Putting
3193 /// "google.com" will match "https://google.com/",
3194 /// "https://google.com/url?q=https://mymaliciousdomainexample.com", and
3195 /// "https://mymaliciousdomainexample.com/hi/google.com" Setting this to "*"
3196 /// will allow all URL previews. Please note that this opens up significant
3197 /// attack surface to your server, you are expected to be aware of the risks
3198 /// by doing so.
3199 ///
3200 /// reloadable: yes
3201 /// default: []
3202 #[serde(default)]
3203 pub url_preview_url_contains_allowlist: Vec<String>,
3204
3205 /// Maximum body size allowed when spidering a URL for previews.
3206 ///
3207 /// Accepts an integer byte count or a string with SI/IEC suffix such as
3208 /// "768 KiB". A page whose OpenGraph tags sit past this point yields an
3209 /// empty preview, so a site that front-loads a large script block needs a
3210 /// larger budget than one that does not.
3211 ///
3212 /// reloadable: yes
3213 /// default: 786432
3214 #[serde(
3215 default = "default_url_preview_max_spider_size",
3216 deserialize_with = "deserialize_bytesize_usize"
3217 )]
3218 pub url_preview_max_spider_size: usize,
3219
3220 /// Maximum size of a single media item fetched or relayed for a URL
3221 /// preview: the og:image measurement fetch and the lazy-media relay.
3222 /// Media whose advertised length exceeds this is not registered, and a
3223 /// relay that would exceed it is refused. Accepts an integer byte count
3224 /// or a string with SI/IEC suffix such as "50 MiB".
3225 ///
3226 /// reloadable: yes
3227 /// default: 50 MiB
3228 #[serde(
3229 default = "default_url_preview_max_media_size",
3230 deserialize_with = "deserialize_bytesize_usize"
3231 )]
3232 pub url_preview_max_media_size: usize,
3233
3234 /// Lifetime in seconds of a cached URL preview, including an empty one.
3235 ///
3236 /// Raising it spares origins in a room whose history is read often, at the
3237 /// cost of serving OpenGraph metadata a page has since changed. A value of
3238 /// 0 fetches on every request. Raising it beyond seven days takes effect at
3239 /// the next restart, since the database applies its retention floor when it
3240 /// opens.
3241 ///
3242 /// reloadable: yes
3243 /// default: 86400
3244 #[serde(default = "default_url_preview_cache_ttl")]
3245 pub url_preview_cache_ttl: u64,
3246
3247 /// Option to decide whether you would like to run the domain allowlist
3248 /// checks (contains and explicit) on the root domain or not. Does not apply
3249 /// to URL contains allowlist. Defaults to false.
3250 ///
3251 /// Example usecase: If this is enabled and you have "wikipedia.org" allowed
3252 /// in the explicit and/or contains domain allowlist, it will allow all
3253 /// subdomains under "wikipedia.org" such as "en.m.wikipedia.org" as the
3254 /// root domain is checked and matched. Useful if the domain contains
3255 /// allowlist is still too broad for you but you still want to allow all the
3256 /// subdomains under a root domain.
3257 /// reloadable: yes
3258 #[serde(default)]
3259 pub url_preview_check_root_domain: bool,
3260
3261 /// User-Agent header the URL preview client sends when fetching pages
3262 /// to extract their OpenGraph tags.
3263 ///
3264 /// When unset, the versioned server User-Agent is used followed by
3265 /// "preview", e.g. "Tuwunel/1.8.1 preview". Some origins serve their
3266 /// OpenGraph tags only to an agent they recognise as a link-preview
3267 /// crawler, and serve everyone else a page whose tags sit past
3268 /// `url_preview_max_spider_size`.
3269 ///
3270 /// reloadable: yes
3271 /// default:
3272 #[serde(default)]
3273 pub url_preview_user_agent: Option<String>,
3274
3275 /// User-Agent header sent when fetching and relaying URL preview media
3276 /// files themselves (og:image, og:video, og:audio, and direct links),
3277 /// as opposed to the pages they appear on. When unset, falls back to
3278 /// `url_preview_user_agent`, then to the versioned server User-Agent.
3279 ///
3280 /// reloadable: yes
3281 /// default:
3282 #[serde(default)]
3283 pub url_preview_media_user_agent: Option<String>,
3284
3285 /// Accept-Language header sent when fetching URL preview pages and media.
3286 ///
3287 /// For example, "en-US,en;q=0.9" requests English from sites that support
3288 /// language negotiation. Sites that select a language solely by IP address
3289 /// may ignore this header.
3290 ///
3291 /// When unset, no Accept-Language header is sent. Changes affect new
3292 /// requests; existing cached previews retain their language until expiry.
3293 ///
3294 /// reloadable: yes
3295 /// default:
3296 #[serde(default)]
3297 pub url_preview_accept_language: Option<String>,
3298
3299 /// List of forbidden room aliases and room IDs as strings of regex
3300 /// patterns.
3301 ///
3302 /// Regex can be used or explicit contains matches can be done by just
3303 /// specifying the words (see example).
3304 ///
3305 /// This is checked upon room alias creation, custom room ID creation if
3306 /// used, and startup as warnings if any room aliases in your database have
3307 /// a forbidden room alias/ID.
3308 ///
3309 /// reloadable: yes
3310 /// example: ["19dollarfortnitecards", "b[4a]droom", "badphrase"]
3311 ///
3312 /// default: []
3313 #[serde(default, with = "serde_regex")]
3314 pub forbidden_alias_names: RegexSet,
3315
3316 /// List of forbidden username patterns/strings.
3317 ///
3318 /// Regex can be used or explicit contains matches can be done by just
3319 /// specifying the words (see example).
3320 ///
3321 /// This is checked upon username availability check, registration, and
3322 /// startup as warnings if any local users in your database have a forbidden
3323 /// username.
3324 ///
3325 /// reloadable: yes
3326 /// example: ["administrator", "b[a4]dusernam[3e]", "badphrase"]
3327 ///
3328 /// default: []
3329 #[serde(default, with = "serde_regex")]
3330 pub forbidden_usernames: RegexSet,
3331
3332 /// List of server names to deprioritize joining through.
3333 ///
3334 /// If a client requests a join through one of these servers,
3335 /// they will be tried last.
3336 ///
3337 /// Useful for preventing failed joins due to timeouts
3338 /// from a certain homeserver.
3339 ///
3340 /// reloadable: yes
3341 /// default: ["matrix\.org"]
3342 /// config-example: ["matrix\\.org"]
3343 #[serde(
3344 default = "default_deprioritize_joins_through_servers",
3345 with = "serde_regex"
3346 )]
3347 pub deprioritize_joins_through_servers: RegexSet,
3348
3349 /// Maximum make_join requests to attempt within each join attempt. Each
3350 /// attempt tries a different server, as each server is only tried once;
3351 /// though retries can occur when the join request as a whole is retried.
3352 ///
3353 /// reloadable: yes
3354 /// default: 48
3355 #[serde(default = "default_max_make_join_attempts_per_join_attempt")]
3356 pub max_make_join_attempts_per_join_attempt: usize,
3357
3358 /// Maximum join attempts to conduct per client join request. Each join
3359 /// attempt consists of one or more make_join requests limited above, and a
3360 /// single send_join request. This value allows for additional servers to
3361 /// act as the join-server prior to reporting the last error back to the
3362 /// client, which can be frustrating for users. Therefor the default value
3363 /// is greater than one, but less than excessively exceeding the client's
3364 /// request timeout, though that may not be avoidable in some cases.
3365 ///
3366 /// reloadable: yes
3367 /// default: 3
3368 #[serde(default = "default_max_join_attempts_per_join_request")]
3369 pub max_join_attempts_per_join_request: usize,
3370
3371 /// Retry failed and incomplete messages to remote servers immediately upon
3372 /// startup.
3373 ///
3374 /// This is called bursting. If this is disabled, queued messages are retried
3375 /// on a paced timer after startup instead of all at once. Do not
3376 /// change this option unless server resources are extremely limited or the
3377 /// scale of the server's deployment is huge. Do not disable this unless you
3378 /// know what you are doing.
3379 #[serde(default = "true_fn")]
3380 pub startup_netburst: bool,
3381
3382 /// Messages are dropped and not reattempted. The `startup_netburst` option
3383 /// must be enabled for this value to have any effect. Do not change this
3384 /// value unless you know what you are doing. Set this value to -1 to
3385 /// reattempt every message without trimming the queues; this may consume
3386 /// significant disk. Set this value to 0 to drop all messages without any
3387 /// attempt at redelivery.
3388 ///
3389 /// default: 50
3390 #[serde(default = "default_startup_netburst_keep")]
3391 pub startup_netburst_keep: i64,
3392
3393 /// Block non-admin local users from sending room invites (local and
3394 /// remote), and block non-admin users from receiving remote room invites.
3395 ///
3396 /// Admins are always allowed to send and receive all room invites.
3397 /// reloadable: yes
3398 #[serde(default)]
3399 pub block_non_admin_invites: bool,
3400
3401 /// Automatically join local users to any room they are invited to.
3402 ///
3403 /// The join runs in the background once the invite is recorded, and is
3404 /// retried for a short while when a federated invite has not yet settled
3405 /// on the inviting server. Users who are deactivated, suspended, or locked
3406 /// are never joined.
3407 ///
3408 /// reloadable: yes
3409 #[serde(default)]
3410 pub auto_accept_invites: bool,
3411
3412 /// Restrict `auto_accept_invites` to direct-message invites.
3413 ///
3414 /// The invite must carry `is_direct` in its membership content, which
3415 /// clients set when starting a direct chat. Invites to any other room are
3416 /// left for the user to answer.
3417 ///
3418 /// reloadable: yes
3419 #[serde(default)]
3420 pub auto_accept_invites_direct_only: bool,
3421
3422 /// Restrict `auto_accept_invites` to invites sent by local users.
3423 ///
3424 /// Invites arriving from another server are left for the user to answer.
3425 ///
3426 /// reloadable: yes
3427 #[serde(default)]
3428 pub auto_accept_invites_local_only: bool,
3429
3430 /// Enforce MSC4311 validation of the create event in federated invite and
3431 /// knock stripped state. When enabled, an invite whose m.room.create event
3432 /// is missing, not a full PDU, bound to a different room, or fails
3433 /// signature checks is rejected, and such events are dropped from knock
3434 /// stripped state. When disabled (the default), failures are logged but
3435 /// tolerated to preserve interoperability during ecosystem migration; a
3436 /// create event that is present as a full PDU but cryptographically bound
3437 /// to a different room is always rejected for room version 12 and above
3438 /// regardless of this setting.
3439 ///
3440 /// reloadable: yes
3441 #[serde(default)]
3442 pub enforce_stripped_state_pdu_validation: bool,
3443
3444 /// Allow admins to run commands outside the admin room ("#admins") by
3445 /// prefixing a message with "\!admin" or "\\!admin" and a normal command.
3446 ///
3447 /// The reply is publicly visible to the room, originating from the sender.
3448 /// Disabled by default: the server cannot tell a command an admin typed
3449 /// from one in a message the admin forwarded or a bot on their account
3450 /// reposted. Message formatting can also hide the command from the admin.
3451 ///
3452 /// reloadable: yes
3453 /// example: \\!admin debug ping puppygock.gay
3454 #[serde(default)]
3455 pub admin_escape_commands: bool,
3456
3457 /// Automatically activate the tuwunel admin room console / CLI on
3458 /// startup. This option can also be enabled with `--console` tuwunel
3459 /// argument. Activation requires standard input to be a terminal.
3460 #[serde(default)]
3461 pub admin_console_automatic: bool,
3462
3463 #[expect(clippy::doc_link_with_quotes)]
3464 /// List of admin commands to execute on startup.
3465 ///
3466 /// This option can also be configured with the `--execute` tuwunel
3467 /// argument and can take standard shell commands and environment variables
3468 ///
3469 /// For example: `./tuwunel --execute "server admin-notice tuwunel has
3470 /// started up at $(date)"`
3471 ///
3472 /// example: admin_execute = ["debug ping puppygock.gay", "debug echo hi"]`
3473 ///
3474 /// default: []
3475 #[serde(default)]
3476 pub admin_execute: Vec<String>,
3477
3478 /// Ignore errors in startup commands.
3479 ///
3480 /// If false, tuwunel will error and fail to start if an admin execute
3481 /// command (`--execute` / `admin_execute`) fails.
3482 /// reloadable: yes
3483 #[serde(default)]
3484 pub admin_execute_errors_ignore: bool,
3485
3486 /// List of admin commands to execute on SIGUSR2.
3487 ///
3488 /// Similar to admin_execute, but these commands are executed when the
3489 /// server receives SIGUSR2 on supporting platforms.
3490 ///
3491 /// reloadable: yes
3492 /// default: []
3493 #[serde(default)]
3494 pub admin_signal_execute: Vec<String>,
3495
3496 /// Controls the max log level for admin command log captures (logs
3497 /// generated from running admin commands). Defaults to "info" on release
3498 /// builds, else "debug" on debug builds.
3499 ///
3500 /// reloadable: yes
3501 /// default: "info"
3502 #[serde(default = "default_admin_log_capture")]
3503 pub admin_log_capture: String,
3504
3505 /// The default room tag to apply on the admin room.
3506 ///
3507 /// On some clients like Element, the room tag "m.server_notice" is a
3508 /// special pinned room at the very bottom of your room list. The tuwunel
3509 /// admin room can be pinned here so you always have an easy-to-access
3510 /// shortcut dedicated to your admin room.
3511 ///
3512 /// reloadable: yes
3513 /// default: "m.server_notice"
3514 #[serde(default = "default_admin_room_tag")]
3515 pub admin_room_tag: String,
3516
3517 /// The localpart of the server's administrative user.
3518 ///
3519 /// This identity is durable after first boot. Changing it on an existing
3520 /// database is unsupported and prevents startup.
3521 ///
3522 /// default: "conduit"
3523 #[serde(default = "default_server_user_localpart")]
3524 pub server_user_localpart: ServerUserLocalpart,
3525
3526 /// The room that user, room, and event reports are posted to, instead of
3527 /// the admin room. Accepts a room ID or room alias; the server user must be
3528 /// joined with permission to post there. Reports fall back to the admin
3529 /// room when this is unset, cannot be resolved, or the server user is not a
3530 /// member.
3531 ///
3532 /// reloadable: yes
3533 /// default: (none)
3534 #[serde(default)]
3535 pub report_room: Option<OwnedRoomOrAliasId>,
3536
3537 /// Whether to grant the first user to register admin privileges by joining
3538 /// them to the admin room. Note that technically the next user to register
3539 /// when the admin room is empty (or only contains the server-user) is
3540 /// granted, and only when the admin room is enabled.
3541 ///
3542 /// reloadable: yes
3543 /// default: true
3544 #[serde(default = "true_fn")]
3545 pub grant_admin_to_first_user: bool,
3546
3547 /// Whether the admin room is created on first startup. Users should not set
3548 /// this to false. Developers can set this to false during integration tests
3549 /// to reduce activity and output.
3550 ///
3551 /// default: true
3552 #[serde(default = "true_fn")]
3553 pub create_admin_room: bool,
3554
3555 /// Whether to enable federation on the admin room. This cannot be changed
3556 /// after the admin room is created.
3557 ///
3558 /// default: true
3559 #[serde(default = "true_fn")]
3560 pub federate_admin_room: bool,
3561
3562 /// Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
3563 /// This is NOT enabled by default. tuwunel's default Sentry reporting
3564 /// endpoint domain is `o4509498990067712.ingest.us.sentry.io`.
3565 #[serde(default)]
3566 pub sentry: bool,
3567
3568 /// Sentry reporting URL, if a custom one is desired.
3569 ///
3570 /// display: sensitive
3571 /// default: ""
3572 #[serde(default = "default_sentry_endpoint")]
3573 pub sentry_endpoint: Option<Url>,
3574
3575 /// Report your tuwunel server_name in Sentry.io crash reports and
3576 /// metrics.
3577 #[serde(default)]
3578 pub sentry_send_server_name: bool,
3579
3580 /// Performance monitoring/tracing sample rate for Sentry.io.
3581 ///
3582 /// Sentry reads this as a fraction, so it must lie between 0.0 and 1.0
3583 /// inclusive when Sentry is enabled. High values may impact performance;
3584 /// 0.0 disables sampling.
3585 ///
3586 /// default: 0.15
3587 #[serde(default = "default_sentry_traces_sample_rate")]
3588 pub sentry_traces_sample_rate: f32,
3589
3590 /// Whether to attach a stacktrace to Sentry reports.
3591 #[serde(default)]
3592 pub sentry_attach_stacktrace: bool,
3593
3594 /// Send panics to Sentry. This is true by default, but Sentry has to be
3595 /// enabled. The global `sentry` config option must be enabled to send any
3596 /// data.
3597 #[serde(default = "true_fn")]
3598 pub sentry_send_panic: bool,
3599
3600 /// Send errors to sentry. This is true by default, but sentry has to be
3601 /// enabled. This option is only effective in release-mode; forced to false
3602 /// in debug-mode.
3603 #[serde(default = "true_fn")]
3604 pub sentry_send_error: bool,
3605
3606 /// Controls the tracing log level for Sentry to send things like
3607 /// breadcrumbs and transactions
3608 ///
3609 /// default: "info"
3610 #[serde(default = "default_sentry_filter")]
3611 pub sentry_filter: String,
3612
3613 /// Enable the tokio-console. This option is only relevant to developers.
3614 ///
3615 /// For more information, see:
3616 /// https://tuwunel.chat/development.html#debugging-with-tokio-console
3617 #[serde(default)]
3618 pub tokio_console: bool,
3619
3620 /// Arbitrary argument vector for integration testing. Functionality in the
3621 /// server is altered or informed for the requirements of integration tests.
3622 /// - "smoke" performs a shutdown after startup admin commands rather than
3623 /// hanging on client handling.
3624 ///
3625 /// display: hidden
3626 #[serde(default)]
3627 pub test: BTreeSet<String>,
3628
3629 /// Indicates the server has started in maintenance mode. Historically
3630 /// maintenance mode has been enabled by the command line argument
3631 /// `--maintenance` which then sets various configuration items such as
3632 /// `listening=false` among others. That is still the case. This option was
3633 /// only added as a single source of truth that `--maintenance` mode is
3634 /// active.
3635 ///
3636 /// This option must never be set manually.
3637 ///
3638 /// display: hidden
3639 #[serde(default)]
3640 pub maintenance: bool,
3641
3642 /// Controls whether admin room notices like account registrations, password
3643 /// changes, account deactivations, room directory publications, etc will be
3644 /// sent to the admin room. Update notices and normal admin command
3645 /// responses will still be sent.
3646 /// reloadable: yes
3647 #[serde(default = "true_fn")]
3648 pub admin_room_notices: bool,
3649
3650 /// Maximum number of message events an admin command's output may be split
3651 /// across as replies in the admin room. Output needing more events than
3652 /// this is uploaded to the media repository instead and returned as a text
3653 /// file attachment replying to the command. When 1, output which fits in a
3654 /// single event is posted as a single reply and anything larger becomes an
3655 /// attachment. When 0, output is always posted as an attachment regardless
3656 /// of size.
3657 ///
3658 /// reloadable: yes
3659 /// default: 1
3660 #[serde(default = "default_admin_output_max_events")]
3661 pub admin_output_max_events: usize,
3662
3663 /// Post admin command output into a thread on the command event rather than
3664 /// as replies. Output split across multiple events per
3665 /// `admin_output_max_events` is contained in a single thread; attachment
3666 /// outputs are posted into the thread as well.
3667 ///
3668 /// reloadable: yes
3669 #[serde(default)]
3670 pub admin_output_threads: bool,
3671
3672 /// Save original events before applying redaction to them.
3673 ///
3674 /// They can be retrieved with `admin debug get-retained-pdu` or MSC2815.
3675 ///
3676 /// reloadable: yes
3677 /// default: true
3678 #[serde(default = "true_fn")]
3679 pub save_unredacted_events: bool,
3680
3681 /// Redaction retention period in seconds.
3682 ///
3683 /// By default the unredacted events are stored for 60 days.
3684 ///
3685 /// reloadable: yes
3686 /// default: 5184000
3687 #[serde(default = "default_redaction_retention_seconds")]
3688 pub redaction_retention_seconds: u64,
3689
3690 /// Allows users with `redact` power level to request unredacted events with
3691 /// MSC2815.
3692 ///
3693 /// Server admins can request unredacted events regardless of the value of
3694 /// this option.
3695 ///
3696 /// reloadable: yes
3697 /// default: true
3698 #[serde(default = "true_fn")]
3699 pub allow_room_admins_to_request_unredacted_events: bool,
3700
3701 /// Prevents local users from sending redactions.
3702 ///
3703 /// This check does not apply to server admins.
3704 /// reloadable: yes
3705 #[serde(default)]
3706 pub disable_local_redactions: bool,
3707
3708 /// Serve erased senders' events as pruned copies over federation
3709 /// (MSC4025). A requesting server retains the unredacted view only when
3710 /// one of its users was joined in the room state at the event; join
3711 /// handshakes are not gated.
3712 ///
3713 /// reloadable: yes
3714 /// default: true
3715 #[serde(default = "true_fn")]
3716 pub enforce_erasure_over_federation: bool,
3717
3718 /// Enable database pool affinity support. On supporting systems, block
3719 /// device queue topologies are detected and the request pool is optimized
3720 /// for the hardware; db_pool_workers is determined automatically.
3721 ///
3722 /// default: true
3723 #[serde(default = "true_fn")]
3724 pub db_pool_affinity: bool,
3725
3726 /// Sets the number of worker threads in the frontend-pool of the database.
3727 /// This number should reflect the I/O capabilities of the system,
3728 /// such as the queue-depth or the number of simultaneous requests in
3729 /// flight. The default is four times the available CPU thread count,
3730 /// clamped from 32 through 1024.
3731 ///
3732 /// Note: This value is only used if db_pool_affinity is disabled or not
3733 /// detected on the system, otherwise it is determined automatically.
3734 ///
3735 /// default: varies by system
3736 #[serde(default = "default_db_pool_workers")]
3737 pub db_pool_workers: usize,
3738
3739 /// When db_pool_affinity is enabled and detected, the size of any worker
3740 /// group will not exceed the determined value. This is necessary when
3741 /// thread-pooling approach does not scale to the full capabilities of
3742 /// high-end hardware; using detected values without limitation could
3743 /// degrade performance.
3744 ///
3745 /// The value is multiplied by the number of cores which share a device
3746 /// queue, since group workers can be scheduled on any of those cores.
3747 ///
3748 /// default: 32
3749 #[serde(default = "default_db_pool_workers_limit")]
3750 pub db_pool_workers_limit: usize,
3751
3752 /// Limits the total number of workers across all worker groups. When the
3753 /// sum of all groups exceeds this value the worker counts are reduced until
3754 /// this constraint is satisfied.
3755 ///
3756 /// Each populated worker group retains at least one worker, so the number
3757 /// of populated groups is the effective floor of this value.
3758 ///
3759 /// By default this value is only effective on larger systems (e.g. 16+
3760 /// cores) where it will tamper the overall thread-count. The thread-pool
3761 /// model will never achieve hardware capacity but this value can be raised
3762 /// on huge systems if the scheduling overhead is determined to not
3763 /// bottleneck and the worker groups are divided too small.
3764 ///
3765 /// default: 2048
3766 #[serde(default = "default_db_pool_max_workers")]
3767 pub db_pool_max_workers: usize,
3768
3769 /// Determines the size of the queues feeding the database's frontend-pool.
3770 /// The size of the queue is determined by multiplying this value with the
3771 /// number of pool workers. When this queue is full, tokio tasks conducting
3772 /// requests will yield until space is available; this is good for
3773 /// flow-control by avoiding buffer-bloat, but can inhibit throughput if
3774 /// too low.
3775 ///
3776 /// default: 4
3777 #[serde(default = "default_db_pool_queue_mult")]
3778 pub db_pool_queue_mult: usize,
3779
3780 /// Sets the initial value for the concurrency of streams. This value simply
3781 /// allows overriding the default in the code. The default is 32, which is
3782 /// the same as the default in the code. Note this value is itself
3783 /// overridden by the computed stream_width_scale, unless that is disabled;
3784 /// this value can serve as a fixed-width instead.
3785 ///
3786 /// default: 32
3787 #[serde(default = "default_stream_width_default")]
3788 pub stream_width_default: usize,
3789
3790 /// Scales the stream width starting from a base value detected for the
3791 /// specific system. The base value is the database pool worker count
3792 /// determined from the hardware queue size (e.g. 32 for SSD or 64 or 128+
3793 /// for NVMe). This float allows scaling the width up or down by multiplying
3794 /// it (e.g. 1.5, 2.0, etc). The maximum result can be the size of the pool
3795 /// queue (see: db_pool_queue_mult) as any larger value will stall the tokio
3796 /// task. The value can also be scaled down (e.g. 0.5) to improve
3797 /// responsiveness for many users at the cost of throughput for each.
3798 ///
3799 /// Setting this value to 0.0 causes the stream width to be fixed at the
3800 /// value of stream_width_default. The default scale is 1.0 to match the
3801 /// capabilities detected for the system.
3802 ///
3803 /// default: 1.0
3804 #[serde(default = "default_stream_width_scale")]
3805 pub stream_width_scale: f32,
3806
3807 /// Sets the initial amplification factor. This controls batch sizes of
3808 /// requests made by each pool worker, multiplying the throughput of each
3809 /// stream. This value is somewhat abstract from specific hardware
3810 /// characteristics and can be significantly larger than any thread count or
3811 /// queue size. This is because each database query may require several
3812 /// index lookups, thus many database queries in a batch may make progress
3813 /// independently while also sharing index and data blocks which may or may
3814 /// not be cached. It is worthwhile to submit huge batches to reduce
3815 /// complexity. The maximum value is 32768, though sufficient hardware is
3816 /// still advised for that.
3817 ///
3818 /// default: 1024
3819 #[serde(default = "default_stream_amplification")]
3820 pub stream_amplification: usize,
3821
3822 /// Number of sender task workers; determines sender parallelism. Default is
3823 /// '0' which means the value is determined internally, likely matching the
3824 /// number of tokio worker-threads or number of cores, etc. Override by
3825 /// setting a non-zero value.
3826 ///
3827 /// default: 0
3828 #[serde(default)]
3829 pub sender_workers: usize,
3830
3831 /// Enables listener sockets; can be set to false to disable listening. This
3832 /// option is intended for developer/diagnostic purposes only.
3833 #[serde(default = "true_fn")]
3834 pub listening: bool,
3835
3836 /// Enables configuration reload when the server receives SIGUSR1 on
3837 /// supporting platforms.
3838 ///
3839 /// reloadable: yes
3840 /// default: true
3841 #[serde(default = "true_fn")]
3842 pub config_reload_signal: bool,
3843
3844 /// Toggles ignore checking/validating TLS certificates
3845 ///
3846 /// This applies to everything, including URL previews, federation requests,
3847 /// etc. This is a hidden argument that should NOT be used in production as
3848 /// it is highly insecure and I will personally yell at you if I catch you
3849 /// using this.
3850 #[serde(default)]
3851 pub allow_invalid_tls_certificates: bool,
3852
3853 /// Sets the `Access-Control-Allow-Origin` header included by this server in
3854 /// all responses. A list of multiple values can be specified. The default
3855 /// is an empty list. The actual header defaults to `*` upon an empty list.
3856 ///
3857 /// There is no reason to configure this without specific intent. Incorrect
3858 /// values may degrade or disrupt clients.
3859 ///
3860 /// default: []
3861 #[serde(default)]
3862 pub access_control_allow_origin: BTreeSet<String>,
3863
3864 /// Backport state-reset security fixes to all room versions.
3865 ///
3866 /// This option applies the State Resolution 2.1 mitigation developed during
3867 /// project Hydra for room version 12 to all prior State Resolution 2.0 room
3868 /// versions (all room versions supported by this server). These mitigations
3869 /// increase resilience to state-resets without any new definition of
3870 /// correctness; therefor it is safe to set this to true for existing rooms.
3871 ///
3872 /// Furthermore, state-reset attacks are not consistent as they result in
3873 /// rooms without any single consensus, therefor it is unnecessary to set
3874 /// this to false to match other servers which set this to false or simply
3875 /// lack support; even if replicating the post-reset state suffered by other
3876 /// servers is somehow desired.
3877 ///
3878 /// This option exists for developer and debug use, and as a failsafe in
3879 /// lieu of hardcoding it.
3880 /// reloadable: yes
3881 #[serde(default = "true_fn")]
3882 pub hydra_backports: bool,
3883
3884 /// Delete rooms when the last user from this server leaves. This feature is
3885 /// experimental and for the purpose of least-surprise is not enabled by
3886 /// default but can be enabled for deployments interested in conserving
3887 /// space. It may eventually default to true in a future release.
3888 ///
3889 /// Note that not all pathways which can remove the last local user
3890 /// currently invoke this operation, so in some cases you may find the room
3891 /// still exists.
3892 ///
3893 /// reloadable: yes
3894 /// default: false
3895 #[serde(default)]
3896 pub delete_rooms_after_leave: bool,
3897
3898 /// Limits the number of One Time Keys per device (not per-algorithm). The
3899 /// reference implementation maintains 50 OTK's at any given time, therefor
3900 /// our default is at least five times that. There is no known reason for an
3901 /// administrator to adjust this value; it is provided here rather than
3902 /// hardcoding it.
3903 ///
3904 /// reloadable: yes
3905 /// default: 256
3906 #[serde(default = "default_one_time_key_limit")]
3907 pub one_time_key_limit: usize,
3908
3909 /// (EXPERIMENTAL) Setting this option to true replaces the list of identity
3910 /// providers displayed on a client's login page with a single button "Sign
3911 /// in with single sign-on" linking to the URL
3912 /// `/_matrix/client/v3/login/sso/redirect`. All configured providers are
3913 /// attempted for authorization. All authorizations associate with the same
3914 /// Matrix user. NOTE: All authorizations must succeed, as there is no
3915 /// reliable way to skip a provider.
3916 ///
3917 /// This option is disabled by default, allowing the client to list
3918 /// configured providers and permitting privacy-conscious users to authorize
3919 /// only their choice.
3920 ///
3921 /// Note that fluffychat always displays a single button anyway. You do not
3922 /// need to enable this to use fluffychat; instead we offer a
3923 /// default-provider option, see `default` in the provider config section.
3924 /// reloadable: yes
3925 #[serde(default)]
3926 pub single_sso: bool,
3927
3928 /// Setting this option to true replaces the list of identity providers on
3929 /// the client's login screen with a single button "Sign in with single
3930 /// sign-on" linking to the URL `/_matrix/client/v3/login/sso/redirect`. The
3931 /// deployment is expected to intercept this URL with their reverse-proxy to
3932 /// provide a custom webpage listing providers; each entry linking or
3933 /// redirecting back to one of the configured identity providers at
3934 /// /_matrix/client/v3/login/sso/redirect/<client_id>`.
3935 ///
3936 /// This option defaults to false, allowing the client to generate the list
3937 /// of providers or hide all SSO-related options when none configured.
3938 /// reloadable: yes
3939 #[serde(default)]
3940 pub sso_custom_providers_page: bool,
3941
3942 /// From MSC3824:
3943 /// > If the client finds oauth_aware_preferred to be true then, assuming it
3944 /// > supports that auth type, it should present this as the only
3945 /// > login/registration method available to the user.
3946 /// reloadable: yes
3947 #[serde(default, alias = "sso_aware_preferred")]
3948 pub oidc_aware_preferred: bool,
3949
3950 /// Directory containing appservice yaml registration files.
3951 ///
3952 /// default: ""
3953 #[serde(default)]
3954 pub appservice_dir: Option<PathBuf>,
3955
3956 /// Apply pending database migrations on startup. This option is intended for
3957 /// developer debugging and testing only. Never set this option to false
3958 /// unless you have been instructed to do so. Setting this option to false
3959 /// may cause permanent damage and permanent loss of data.
3960 ///
3961 /// Any new database migrations will not be applied on startup, and the
3962 /// database schema version will not be adjusted. These migrations and
3963 /// schema changes may be expected by the current codebase but may not be
3964 /// available when this option is set to false.
3965 ///
3966 /// Setting this option to false is accepted only after the required local
3967 /// state memo invalidation has run once. Start once with this option true if
3968 /// startup reports that prerequisite missing. With the marker present,
3969 /// false continues to skip migrations as configured.
3970 #[serde(default = "true_fn")]
3971 pub database_migrations: bool,
3972
3973 /// Open a database whose schema version is newer than this build supports.
3974 ///
3975 /// A database reporting a higher schema version than this build is normally
3976 /// refused, since opening it stamps the schema down to this build's version
3977 /// and may permanently lose data written by the newer build. Setting this
3978 /// to true overrides that refusal: the database opens, one-time migrations
3979 /// run, and the schema is stamped down to this build's version.
3980 ///
3981 /// It has no effect when the discovered version is at or below this build's
3982 /// version, where migrations apply normally either way. It is also not
3983 /// needed to import a Conduit database or a fork of conduwuit; those are
3984 /// recognized by lineage and open without it.
3985 ///
3986 /// This option is extremely dangerous and intended for developer debugging
3987 /// and testing only. Never set it unless you have been instructed to do so;
3988 /// it may cause permanent damage and permanent loss of data.
3989 #[serde(default)]
3990 pub force_migration: bool,
3991
3992 /// When importing a Conduit database in place, the filesystem path to
3993 /// Conduit's media directory. Leave unset to use `<database_path>/media`,
3994 /// which is Conduit's own default location.
3995 ///
3996 /// example: "/var/lib/matrix-conduit/media"
3997 pub conduit_source_media_path: Option<PathBuf>,
3998
3999 /// When importing a Conduit database, the sharding depth of Conduit's media
4000 /// directory (0 for a flat directory). Must match the importing Conduit's
4001 /// `media.directory_structure`; the default matches Conduit's own default
4002 /// of `Deep { length = 2, depth = 2 }`.
4003 ///
4004 /// default: 2
4005 #[serde(default = "default_conduit_media_directory_depth")]
4006 pub conduit_media_directory_depth: u8,
4007
4008 /// When importing a Conduit database, the shard-segment length of Conduit's
4009 /// media directory. Paired with `conduit_media_directory_depth`.
4010 ///
4011 /// default: 2
4012 #[serde(default = "default_conduit_media_directory_length")]
4013 pub conduit_media_directory_length: u8,
4014
4015 /// When importing a Conduit database whose media lived in an S3 bucket
4016 /// rather than on disk, the name of a `[global.storage_provider.<name>]`
4017 /// entry to read the source originals from. Leave unset to read from the
4018 /// filesystem at `conduit_source_media_path`. Define the named provider
4019 /// with Conduit's own S3 credentials and set its `base_path` to Conduit's
4020 /// `media.path` prefix; the importer reads each content-addressed object
4021 /// using `conduit_media_directory_depth`/`length` for the key sharding.
4022 ///
4023 /// Scope `media_storage_providers` to your destination provider only (e.g.
4024 /// `["media"]`) so the import writes solely there; otherwise media is also
4025 /// copied back into the read-only source bucket.
4026 ///
4027 /// example: "conduit_source"
4028 pub conduit_source_media_provider: Option<String>,
4029
4030 /// Set this to true for excluding unencrypted rooms from the common-rooms
4031 /// calculation deciding the receivers of device list updates.
4032 ///
4033 /// Setting this to true can help performance on very large homeservers,
4034 /// but it may not be spec compliant and risky for client expectations.
4035 /// reloadable: yes
4036 #[serde(default)]
4037 pub device_key_update_encrypted_rooms_only: bool,
4038
4039 /// Defines named media storage providers.
4040 ///
4041 /// Each map key names a provider, and each value selects a local or
4042 /// S3-compatible backend or disables the entry. Provider-specific settings
4043 /// live in separate sections.
4044 // external structure; separate section
4045 #[serde(default)]
4046 pub storage_provider: BTreeMap<String, StorageProvider>,
4047
4048 /// Defines policy documents users must accept during registration.
4049 ///
4050 /// Each map key is the policy identifier exposed in the `m.login.terms` UIA
4051 /// stage. An empty map leaves the terms stage disabled.
4052 // external structure; separate section
4053 #[serde(default)]
4054 pub registration_terms: BTreeMap<String, TermsPolicy>,
4055
4056 /// Configures LDAP login integration.
4057 ///
4058 /// Connection, bind, search, and attribute settings live in the separate
4059 /// `[global.ldap]` section. LDAP authentication is disabled by default.
4060 // external structure; separate section
4061 #[serde(default)]
4062 pub ldap: LdapConfig,
4063
4064 /// Configures JSON Web Token login integration.
4065 ///
4066 /// Key format, algorithm, claim validation, and user provisioning settings
4067 /// live in `[global.jwt]`. Token login is disabled by default.
4068 // external structure; separate section
4069 #[serde(default)]
4070 pub jwt: JwtConfig,
4071
4072 /// Configures outbound SMTP email delivery.
4073 ///
4074 /// Providing a connection URI enables the email subsystem. Registration
4075 /// flags determine when a verified address is required.
4076 // external structure; separate section
4077 #[serde(default)]
4078 pub smtp: SmtpConfig,
4079
4080 /// Defines inline application service registrations.
4081 ///
4082 /// Each map key names one registration and supplies its default identifier.
4083 /// The contained settings are converted to Matrix application service data.
4084 // external structure; separate section
4085 #[serde(default)]
4086 pub appservice: BTreeMap<String, AppService>,
4087
4088 /// Defines OpenID Connect identity provider registrations.
4089 ///
4090 /// Each entry configures client credentials, discovery, and account
4091 /// mapping. Its stable `client_id` identifies the provider while `brand`
4092 /// selects provider-specific defaults and workarounds.
4093 // external structure; separate sections
4094 #[serde(default, with = "identity_provider_serde")]
4095 pub identity_provider: BTreeMap<String, IdentityProvider>,
4096
4097 #[serde(flatten)]
4098 #[expect(clippy::zero_sized_map_values)]
4099 // this is a catchall, the map shouldn't be zero at runtime
4100 catchall: BTreeMap<String, IgnoredAny>,
4101}
4102
4103/// Configures direct TLS listener behavior.
4104///
4105/// Certificate and key paths must be supplied together. Optional dual-protocol
4106/// mode accepts encrypted and plain connections on the same listeners.
4107#[derive(Clone, Debug, Deserialize, Default)]
4108#[config_example_generator(filename = "tuwunel-example.toml", section = "global.tls")]
4109pub struct TlsConfig {
4110 /// Path to a valid TLS certificate file; this section requires a build with
4111 /// the `direct_tls` Cargo feature.
4112 ///
4113 /// example: "/path/to/my/certificate.crt"
4114 pub certs: Option<String>,
4115
4116 /// Path to a valid TLS certificate private key.
4117 ///
4118 /// example: "/path/to/my/certificate.key"
4119 pub key: Option<String>,
4120
4121 /// Controls whether listeners accept both HTTP and HTTPS.
4122 ///
4123 /// Plain requests are served without redirecting them to HTTPS. This
4124 /// weakens transport security and is disabled by default.
4125 #[serde(default)]
4126 pub dual_protocol: bool,
4127}
4128
4129/// Configures Matrix discovery documents and related response data.
4130///
4131/// Client and server fields drive the standard well-known responses. Support
4132/// contacts, policies, and MatrixRTC transports populate their corresponding
4133/// discovery data.
4134#[expect(rustdoc::bare_urls)]
4135#[derive(Clone, Debug, Deserialize, Default)]
4136#[config_example_generator(
4137 filename = "tuwunel-example.toml",
4138 section = "global.well_known",
4139 ignore = "support_contact support_policy",
4140 hidden = "support_role support_email support_mxid support_page support_pgp_key"
4141)]
4142pub struct WellKnownConfig {
4143 /// The server URL that the client well-known file will serve.
4144 ///
4145 /// This should not contain a port, and should just be a valid HTTPS URL.
4146 /// While this is unset, `/.well-known/matrix/client` answers 404 and
4147 /// auto-discovery from the server name yields nothing, so the base URL has
4148 /// to reach clients some other way. Leave it unset only when a reverse
4149 /// proxy or another host publishes that file for this domain.
4150 ///
4151 /// example: "https://matrix.example.com"
4152 pub client: Option<Url>,
4153
4154 /// The server base domain of the URL with a specific port that the server
4155 /// well-known file will serve. This should contain a port at the end, and
4156 /// should not be a URL.
4157 ///
4158 /// reloadable: yes
4159 /// example: "matrix.example.com:443"
4160 pub server: Option<OwnedServerName>,
4161
4162 /// Defines contacts published by the support discovery endpoint.
4163 ///
4164 /// Each map value becomes one contact while its key is only a config
4165 /// identifier. Legacy scalar support fields are appended separately.
4166 // external structure; separate section
4167 #[serde(default)]
4168 pub support_contact: BTreeMap<String, SupportContact>,
4169
4170 /// Defines policies published by the support discovery endpoint.
4171 ///
4172 /// Each map key becomes a policy identifier. The value supplies its version
4173 /// and localized documents.
4174 // external structure; separate section
4175 #[serde(default)]
4176 pub support_policy: BTreeMap<String, SupportPolicy>,
4177
4178 /// The URL of the support web page. This and the below generate the content
4179 /// of `/.well-known/matrix/support`.
4180 ///
4181 /// example: "https://example.com/support"
4182 pub support_page: Option<Url>,
4183
4184 /// The name of the support role.
4185 ///
4186 ///
4187 /// display: hidden
4188 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
4189 // used instead. However for compatibility purposes the config option will still function and
4190 // be prioritised first.
4191 pub support_role: Option<ContactRole>,
4192
4193 /// The email address for the above support role.
4194 ///
4195 ///
4196 /// display: hidden
4197 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
4198 // used instead. However for compatibility purposes the config option will still function and
4199 // be prioritised first.
4200 pub support_email: Option<String>,
4201
4202 /// The Matrix User ID for the above support role.
4203 ///
4204 /// display: hidden
4205 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
4206 // used instead. However for compatibility purposes the config option will still function and
4207 // be prioritised first.
4208 pub support_mxid: Option<OwnedUserId>,
4209
4210 /// The PGP key (i.e. OpenPGP) that one may use for encrypted communications
4211 /// for the above support role. The value must be a URI. Use a web URL
4212 /// pointing to the key (for example "https://example.com/key.asc"), an
4213 /// OPENPGPKEY DNS record ("dns:..."), or a fingerprint carried with the
4214 /// "openpgp4fpr:" scheme. A bare fingerprint without a scheme, or raw
4215 /// inlined key material, is rejected at startup.
4216 ///
4217 /// As this is a spec proposal (MSC4439), the identifier/prefix for this
4218 /// field is currently "dev.zirco.msc4439.pgp_key"
4219 ///
4220 /// display: hidden
4221 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
4222 // used instead. However for compatibility purposes the config option will still function and
4223 // be prioritised first.
4224 pub support_pgp_key: Option<String>,
4225
4226 /// LiveKit JWT endpoint.
4227 /// Required for Element Call / MatrixRTC (MSC4143).
4228 ///
4229 /// Note: You must also set `client` above to your homeserver URL.
4230 ///
4231 /// reloadable: yes
4232 /// default: ""
4233 #[serde(default)]
4234 pub livekit_url: Option<String>,
4235
4236 /// Custom MatrixRTC transports.
4237 ///
4238 /// If you're looking to setup Element Call / MatrixRTC with Livekit,
4239 /// you should not use this option and instead set `livekit_url`.
4240 /// This is only required if you want to configure a non-livekit MatrixRTC
4241 /// transport. There are no known client implementations that support any
4242 /// other transport types.
4243 ///
4244 /// This option was previously the only way to configure a Livekit
4245 /// transport. It has been superseded by `livekit_url`.
4246 ///
4247 /// Example:
4248 /// ```toml
4249 /// [global.well_known]
4250 /// client = "https://matrix.yourdomain.com"
4251 ///
4252 /// [[global.well_known.rtc_transports]]
4253 /// type = "livekit"
4254 /// livekit_service_url = "https://livekit.yourdomain.com"
4255 /// ```
4256 ///
4257 /// reloadable: yes
4258 /// default: []
4259 #[serde(default)]
4260 pub rtc_transports: Vec<serde_json::Value>,
4261}
4262
4263/// Defines one policy published by the support discovery endpoint.
4264///
4265/// The enclosing map key supplies the policy identifier. Its version and
4266/// localized translations are emitted in the discovery response.
4267#[derive(Clone, Debug, Deserialize)]
4268#[config_example_generator(
4269 filename = "tuwunel-example.toml",
4270 section = "global.well_known.support_policy.<ID>",
4271 ignore = "policy_translation"
4272)]
4273pub struct SupportPolicy {
4274 /// Version string of the policy document.
4275 ///
4276 /// example: "v6.7"
4277 /// reloadable: yes
4278 pub version: String,
4279
4280 /// Maps language identifiers to localized policy documents.
4281 ///
4282 /// Each value supplies the display name and URL for its language. The map
4283 /// is converted to the response's localized policy entries.
4284 // external structure; separate section
4285 pub policy_translation: BTreeMap<String, SupportPolicyTranslation>,
4286}
4287
4288/// Defines one localized support policy document.
4289///
4290/// `name` is the user-facing title for this language. `url` points clients to
4291/// the corresponding policy text.
4292#[derive(Clone, Debug, Deserialize)]
4293#[config_example_generator(
4294 filename = "tuwunel-example.toml",
4295 section = "global.well_known.support_policy.<ID>.policy_translation.<LANG>"
4296)]
4297pub struct SupportPolicyTranslation {
4298 /// User friendly name of the policy document.
4299 ///
4300 /// example: "Privacy Policy"
4301 /// reloadable: yes
4302 pub name: String,
4303
4304 /// Link to the test of the policy document. A valid URL must be specified.
4305 ///
4306 /// example: "https://website.local/privacy-policy"
4307 /// reloadable: yes
4308 pub url: Url,
4309}
4310
4311/// Defines a policy document required during registration.
4312///
4313/// The enclosing map key becomes the policy identifier presented to clients.
4314/// Its version and translations form the `m.login.terms` stage parameters.
4315#[derive(Clone, Debug, Deserialize)]
4316#[config_example_generator(
4317 filename = "tuwunel-example.toml",
4318 section = "global.registration_terms.<ID>",
4319 ignore = "translations"
4320)]
4321pub struct TermsPolicy {
4322 /// Version of this policy document, presented to the client. Configuring
4323 /// any `[global.registration_terms.<ID>]` block makes registration
4324 /// require an `m.login.terms` stage listing every such document; the
4325 /// `<ID>` is the policy id sent to clients.
4326 ///
4327 /// example: "1.2"
4328 /// reloadable: yes
4329 pub version: String,
4330
4331 /// Maps language identifiers to localized registration policy documents.
4332 ///
4333 /// Each value supplies the display name and HTTP or HTTPS URL for its
4334 /// language. These translations are presented in the terms stage.
4335 // external structure; separate section
4336 pub translations: BTreeMap<String, TermsPolicyTranslation>,
4337}
4338
4339/// Defines one localized registration policy document.
4340///
4341/// `name` is the user-facing title for this language. `url` points clients to
4342/// the policy text whose acceptance is recorded.
4343#[derive(Clone, Debug, Deserialize)]
4344#[config_example_generator(
4345 filename = "tuwunel-example.toml",
4346 section = "global.registration_terms.<ID>.translations.<LANG>"
4347)]
4348pub struct TermsPolicyTranslation {
4349 /// User friendly name of the policy document in this language.
4350 ///
4351 /// example: "Terms of Service"
4352 /// reloadable: yes
4353 pub name: String,
4354
4355 /// Link to the text of the policy document. Must be a valid http(s) URL.
4356 ///
4357 /// example: "https://example.org/terms-1.2-en.html"
4358 /// reloadable: yes
4359 pub url: Url,
4360}
4361
4362impl From<SupportPolicyTranslation>
4363 for ruma::api::identity_service::tos::get_terms_of_service::v2::LocalizedPolicy
4364{
4365 fn from(conf: SupportPolicyTranslation) -> Self {
4366 Self {
4367 name: conf.name,
4368 url: conf.url.to_string(),
4369 }
4370 }
4371}
4372
4373/// Defines a contact published by the support discovery endpoint.
4374///
4375/// Every contact has a Matrix support role. Email, Matrix ID, and OpenPGP key
4376/// fields provide optional communication channels.
4377#[derive(Clone, Debug, Deserialize)]
4378#[config_example_generator(
4379 filename = "tuwunel-example.toml",
4380 section = "global.well_known.support_contact.<ID>"
4381)]
4382pub struct SupportContact {
4383 /// The name of the support role.
4384 ///
4385 /// example: "m.role.admin"
4386 pub role: ContactRole,
4387
4388 /// The email address for the above support role.
4389 ///
4390 /// example: "admin@example.com"
4391 pub email_address: Option<String>,
4392
4393 /// The Matrix User ID for the above support role.
4394 ///
4395 /// example "@admin:example.com"
4396 pub matrix_id: Option<OwnedUserId>,
4397
4398 /// The PGP key (i.e. OpenPGP) that one may use for encrypted communications
4399 /// for the above support role. The value must be a URI. Use a web URL
4400 /// pointing to the key (for example "https://example.com/key.asc"), an
4401 /// OPENPGPKEY DNS record ("dns:..."), or a fingerprint carried with the
4402 /// "openpgp4fpr:" scheme. A bare fingerprint without a scheme, or raw
4403 /// inlined key material, is rejected at startup.
4404 ///
4405 /// As this is a spec proposal (MSC4439), the identifier/prefix for this
4406 /// field is currently "dev.zirco.msc4439.pgp_key"
4407 ///
4408 /// example: "openpgp4fpr:8B77919975EAFA5E2456EE03665FE73077489DB0"
4409 pub pgp_key: Option<String>,
4410}
4411
4412impl From<SupportContact> for ruma::api::client::discovery::discover_support::Contact {
4413 fn from(conf: SupportContact) -> Self {
4414 Self {
4415 role: conf.role,
4416 matrix_id: conf.matrix_id,
4417 email_address: conf.email_address,
4418 pgp_key: conf.pgp_key,
4419 }
4420 }
4421}
4422
4423/// Configures LDAP authentication and directory-backed administration.
4424///
4425/// Connection, bind, search, and attribute settings determine how users are
4426/// located and authenticated. Optional admin search settings identify directory
4427/// entries treated as server administrators.
4428#[derive(Clone, Debug, Default, Deserialize)]
4429#[config_example_generator(filename = "tuwunel-example.toml", section = "global.ldap")]
4430pub struct LdapConfig {
4431 /// Whether to enable LDAP login.
4432 ///
4433 /// reloadable: yes
4434 /// example: "true"
4435 #[serde(default)]
4436 pub enable: bool,
4437
4438 /// URI of the LDAP server.
4439 ///
4440 /// reloadable: yes
4441 /// example: "ldap://ldap.example.com:389"
4442 pub uri: Option<Url>,
4443
4444 /// Root of the searches.
4445 ///
4446 /// reloadable: yes
4447 /// example: "ou=users,dc=example,dc=org"
4448 ///
4449 /// default:
4450 #[serde(default)]
4451 pub base_dn: String,
4452
4453 /// Bind DN if anonymous search is not enabled.
4454 ///
4455 /// You can use the variable `{username}` that will be replaced by the
4456 /// entered username. In such case, the password used to bind will be the
4457 /// one provided for the login and not the one given by
4458 /// `bind_password_file`. Beware: automatically granting admin rights will
4459 /// not work if you use this direct bind instead of a LDAP search.
4460 ///
4461 /// reloadable: yes
4462 /// example: "cn=ldap-reader,dc=example,dc=org" or
4463 /// "cn={username},ou=users,dc=example,dc=org"
4464 ///
4465 /// default: ""
4466 #[serde(default)]
4467 pub bind_dn: Option<String>,
4468
4469 /// Path to a file on the system that contains the password for the
4470 /// `bind_dn`.
4471 ///
4472 /// The server must be able to access the file, and it must not be empty.
4473 ///
4474 /// reloadable: yes
4475 /// default: ""
4476 #[serde(default)]
4477 pub bind_password_file: Option<PathBuf>,
4478
4479 /// Search filter to limit user searches.
4480 ///
4481 /// You can use the variable `{username}` that will be replaced by the
4482 /// entered username for more complex filters.
4483 ///
4484 /// reloadable: yes
4485 /// example: "(&(objectClass=person)(memberOf=matrix))"
4486 ///
4487 /// default: "(objectClass=*)"
4488 #[serde(default = "default_ldap_search_filter")]
4489 pub filter: String,
4490
4491 /// Attribute to use to uniquely identify the user.
4492 ///
4493 /// reloadable: yes
4494 /// example: "uid" or "cn"
4495 ///
4496 /// default: "uid"
4497 #[serde(default = "default_ldap_uid_attribute")]
4498 pub uid_attribute: String,
4499
4500 /// Root of the searches for admin users.
4501 ///
4502 /// Defaults to `base_dn` if empty.
4503 ///
4504 /// reloadable: yes
4505 /// example: "ou=admins,dc=example,dc=org"
4506 ///
4507 /// default:
4508 #[serde(default)]
4509 pub admin_base_dn: String,
4510
4511 /// The LDAP search filter to find administrative users for tuwunel.
4512 ///
4513 /// If left blank, administrative state must be configured manually for each
4514 /// user.
4515 ///
4516 /// You can use the variable `{username}` that will be replaced by the
4517 /// entered username for more complex filters.
4518 ///
4519 /// reloadable: yes
4520 /// example: "(objectClass=tuwunelAdmin)" or "(uid={username})"
4521 ///
4522 /// default:
4523 #[serde(default)]
4524 pub admin_filter: String,
4525}
4526
4527/// Configures authentication using JSON Web Tokens.
4528///
4529/// Key format, signature algorithm, and claim rules determine token validity.
4530/// Optional provisioning creates a local account for an otherwise valid token.
4531#[derive(Clone, Debug, Default, Deserialize)]
4532#[config_example_generator(filename = "tuwunel-example.toml", section = "global.jwt")]
4533pub struct JwtConfig {
4534 /// Enable JWT logins
4535 ///
4536 /// reloadable: yes
4537 /// default: false
4538 #[serde(default)]
4539 pub enable: bool,
4540
4541 /// Validation key, also called 'secret' in Synapse config. The type of key
4542 /// can be configured in 'format', but defaults to the common HMAC which
4543 /// is a plaintext shared-secret, so you should keep this value private.
4544 ///
4545 /// display: sensitive
4546 /// reloadable: yes
4547 /// default:
4548 #[serde(default, alias = "secret")]
4549 pub key: String,
4550
4551 /// Format of the 'key': HMAC, B64HMAC, ECDSA, or EDDSA, case-insensitive.
4552 ///
4553 /// HMAC is a plaintext shared secret, and B64HMAC (also spelled HMACB64)
4554 /// is the same secret base64-encoded in the standard alphabet with '='
4555 /// padding, for random binary secrets that cannot be pasted as text.
4556 /// ECDSA and EDDSA are PEM-encoded public keys, EDDSA for Ed25519.
4557 /// Startup and reload both refuse an unknown format while JWT is enabled.
4558 ///
4559 /// reloadable: yes
4560 /// default: "HMAC"
4561 #[serde(default = "default_jwt_format")]
4562 pub format: String,
4563
4564 /// Automatically create new user from a valid claim, otherwise access is
4565 /// denied for an unknown even with an authentic token.
4566 ///
4567 /// reloadable: yes
4568 /// default: true
4569 #[serde(default = "true_fn")]
4570 pub register_user: bool,
4571
4572 /// Signature algorithm for validating tokens.
4573 ///
4574 /// The name must suit the key format: HS256, HS384, or HS512 for HMAC
4575 /// and B64HMAC, ES256 or ES384 for ECDSA, and EdDSA for EDDSA.
4576 ///
4577 /// reloadable: yes
4578 /// default: "HS256"
4579 #[serde(default = "default_jwt_algorithm")]
4580 pub algorithm: String,
4581
4582 /// Optional audience claim list. The token must claim one or more values
4583 /// from this list when set.
4584 ///
4585 /// reloadable: yes
4586 /// default: []
4587 #[serde(default)]
4588 pub audience: Vec<String>,
4589
4590 /// Optional issuer claim list. The token must claim one or more values
4591 /// from this list when set.
4592 ///
4593 /// reloadable: yes
4594 /// default: []
4595 #[serde(default)]
4596 pub issuer: Vec<String>,
4597
4598 /// Require expiration claim in the token. This defaults to false for
4599 /// synapse migration compatibility.
4600 ///
4601 /// reloadable: yes
4602 /// default: false
4603 #[serde(default)]
4604 pub require_exp: bool,
4605
4606 /// Require not-before claim in the token. This defaults to false for
4607 /// synapse migration compatibility.
4608 ///
4609 /// reloadable: yes
4610 /// default: false
4611 #[serde(default)]
4612 pub require_nbf: bool,
4613
4614 /// Validate expiration time of the token when present. Whether or not it is
4615 /// required depends on require_exp, but when present this ensures the token
4616 /// is not used after a time.
4617 ///
4618 /// reloadable: yes
4619 /// default: true
4620 #[serde(default = "true_fn")]
4621 pub validate_exp: bool,
4622
4623 /// Validate not-before time of the token when present. Whether or not it is
4624 /// required depends on require_nbf, but when present this ensures the token
4625 /// is not used before a time.
4626 ///
4627 /// reloadable: yes
4628 /// default: true
4629 #[serde(default = "true_fn")]
4630 pub validate_nbf: bool,
4631
4632 /// Bypass validation for diagnostic/debug use only.
4633 ///
4634 /// reloadable: yes
4635 /// default: true
4636 #[serde(default = "true_fn")]
4637 pub validate_signature: bool,
4638}
4639
4640/// Configures request rate limits.
4641///
4642/// Each subsection limits one kind of request, such as password sign-in.
4643#[derive(Clone, Copy, Debug, Default, Deserialize)]
4644#[config_example_generator(
4645 filename = "tuwunel-example.toml",
4646 section = "global.rate_limiting",
4647 ignore = "login"
4648)]
4649pub struct RateLimits {
4650 /// Limits password sign-in per account, in the `failed` and `account`
4651 /// subsections.
4652 // external structure; separate section
4653 #[serde(default)]
4654 pub login: LoginRateLimits,
4655}
4656
4657/// Limits password sign-in per account on two axes.
4658///
4659/// Both mirror Synapse's `rc_login` limits of the same purpose and are keyed on
4660/// the account rather than the client address.
4661#[derive(Clone, Copy, Debug, Default, Deserialize)]
4662#[config_example_generator(
4663 filename = "tuwunel-example.toml",
4664 section = "global.rate_limiting.login",
4665 ignore = "failed account"
4666)]
4667pub struct LoginRateLimits {
4668 /// Limits wrong passwords against one account.
4669 // external structure; separate section
4670 #[serde(default)]
4671 pub failed: LoginFailedRateLimit,
4672
4673 /// Limits successful sign-ins to one account.
4674 // external structure; separate section
4675 #[serde(default)]
4676 pub account: LoginAccountRateLimit,
4677}
4678
4679/// Limits wrong passwords against one account.
4680///
4681/// A name that matches no account counts as a wrong password, so the limit
4682/// does not reveal which accounts exist.
4683#[derive(Clone, Copy, Debug, Deserialize)]
4684#[config_example_generator(
4685 filename = "tuwunel-example.toml",
4686 section = "global.rate_limiting.login.failed"
4687)]
4688pub struct LoginFailedRateLimit {
4689 /// Token-bucket refill rate (failures per second) for wrong passwords
4690 /// against one account.
4691 ///
4692 /// Every password attempt takes a token before the password is checked,
4693 /// and only a wrong password keeps it; once the bucket is empty, even the
4694 /// owner's correct password is refused with `M_LIMIT_EXCEEDED`. Applies to
4695 /// `/login` (but not to the LDAP binds it makes), the built-in OIDC login
4696 /// page and password re-entry for sensitive actions (UIAA), keyed on the
4697 /// account. Mirrors Synapse's `rc_login.failed_attempts` and its default:
4698 /// `burst_count` failures, then about one attempt every six seconds, or
4699 /// some 14,700 a day.
4700 ///
4701 /// `0` disables this limit rather than making a bucket that never refills.
4702 ///
4703 /// reloadable: yes
4704 /// default: 0.17
4705 #[serde(default = "default_login_failed_per_second")]
4706 pub per_second: f64,
4707
4708 /// Token-bucket depth (burst size) for wrong passwords against one
4709 /// account.
4710 ///
4711 /// The number of failures allowed before `per_second` governs. `0`
4712 /// disables this limit, as does a `0` rate. The default is Synapse's
4713 /// `rc_login.failed_attempts.burst_count`.
4714 ///
4715 /// reloadable: yes
4716 /// default: 3
4717 #[serde(default = "default_login_failed_burst_count")]
4718 pub burst_count: u32,
4719}
4720
4721/// Limits successful sign-ins to one account.
4722///
4723/// Only a sign-in by a verified password takes a token, so wrong guesses never
4724/// lock the owner out on this axis.
4725#[derive(Clone, Copy, Debug, Deserialize)]
4726#[config_example_generator(
4727 filename = "tuwunel-example.toml",
4728 section = "global.rate_limiting.login.account"
4729)]
4730pub struct LoginAccountRateLimit {
4731 /// Token-bucket refill rate (sign-ins per second) for successful password
4732 /// logins to one account.
4733 ///
4734 /// Applies to `/login` with `m.login.password` (but not to the LDAP binds it
4735 /// makes) and to the built-in OIDC login page, keyed on the account rather
4736 /// than the client IP. Mirrors Synapse's `rc_login.account` and its default
4737 /// of `burst_count` sign-ins, then about one every five and a half minutes;
4738 /// once those are used up, a correct password is refused with
4739 /// `M_LIMIT_EXCEEDED`.
4740 ///
4741 /// `0` disables this limit rather than making a bucket that never refills.
4742 ///
4743 /// reloadable: yes
4744 /// default: 0.003
4745 #[serde(default = "default_login_account_per_second")]
4746 pub per_second: f64,
4747
4748 /// Token-bucket depth (burst size) for successful password logins to one
4749 /// account.
4750 ///
4751 /// The number of sign-ins allowed before `per_second` governs. `0` disables
4752 /// this limit, as does a `0` rate. The default is Synapse's
4753 /// `rc_login.account.burst_count`.
4754 ///
4755 /// reloadable: yes
4756 /// default: 5
4757 #[serde(default = "default_login_account_burst_count")]
4758 pub burst_count: u32,
4759}
4760
4761/// Configures outbound email verification through SMTP.
4762///
4763/// The connection URI and sender identify the relay and source mailbox.
4764/// Registration flags control which flows require a verified email address.
4765#[derive(Clone, Debug, Default, Deserialize)]
4766#[config_example_generator(filename = "tuwunel-example.toml", section = "global.smtp")]
4767pub struct SmtpConfig {
4768 /// Connection URL for the outbound SMTP relay used to send email
4769 /// verification messages. Setting this enables the email subsystem;
4770 /// without it no mail is sent.
4771 ///
4772 /// Use a `smtp://` URL for an unencrypted or STARTTLS connection and a
4773 /// `smtps://` URL for implicit TLS. Credentials and the host go inline:
4774 /// `smtps://user:pass@host:port`. The port defaults per scheme when
4775 /// omitted.
4776 ///
4777 /// The userinfo component is URL-encoded, so an `@` inside the username
4778 /// must be written as `%40` (for example a login of `bot@example.com`
4779 /// becomes `smtps://bot%40example.com:pass@host:465`). Other reserved
4780 /// characters in the username or password are percent-encoded the same
4781 /// way.
4782 ///
4783 /// example: "smtps://user:pass@mail.example.com:465"
4784 pub connection_uri: Option<String>,
4785
4786 /// The mailbox that outbound verification messages are sent from. Accepts
4787 /// either a bare address or a display-name form.
4788 ///
4789 /// example: "Example <noreply@example.com>"
4790 pub sender: Option<String>,
4791
4792 /// Require a verified email address to complete registration. When set,
4793 /// the registration flow does not finish until the user proves control of
4794 /// an email address.
4795 ///
4796 /// default: false
4797 #[serde(default)]
4798 pub require_email_for_registration: bool,
4799
4800 /// Require a verified email address when registering with a registration
4801 /// token. When set, token-based registration also demands a verified
4802 /// email address.
4803 ///
4804 /// default: false
4805 #[serde(default)]
4806 pub require_email_for_token_registration: bool,
4807}
4808
4809/// Configures one OpenID Connect identity provider.
4810///
4811/// Client credentials and endpoint discovery establish the upstream
4812/// authorization flow. Claim and trust settings control account mapping and
4813/// optional registration.
4814#[derive(Clone, Debug, Deserialize)]
4815#[config_example_generator(
4816 filename = "tuwunel-example.toml",
4817 section = "[global.identity_provider]"
4818)]
4819pub struct IdentityProvider {
4820 /// The brand-name of the service (e.g. Apple, Facebook, GitHub, GitLab,
4821 /// Google) or the software (e.g. keycloak, MAS) providing the identity.
4822 /// When a brand is recognized we apply certain defaults to this config
4823 /// for your convenience. For certain brands we apply essential internal
4824 /// workarounds specific to that provider; it is important to configure this
4825 /// field properly when a provider needs to be recognized (like GitHub for
4826 /// example).
4827 ///
4828 /// Several configured providers can share the same brand name. It is not
4829 /// case-sensitive. As a convenience for common simple deployments we can
4830 /// identify this provider by brand in addition to the unique `client_id` if
4831 /// and only if there is a single provider for the brand; see notes for
4832 /// `client_id`.
4833 #[serde(deserialize_with = "utils::string::de::to_lowercase")]
4834 pub brand: String,
4835
4836 /// The ID of your OAuth application which the provider generates upon
4837 /// registration. This ID then uniquely identifies this configuration
4838 /// instance itself, becoming the identity provider's ID and must be unique
4839 /// and remain unchanged.
4840 ///
4841 /// As a convenience we also identify this config by `brand` if and only if
4842 /// there is a single provider configured for a `brand`. Note carefully that
4843 /// multiple providers configured with the same `brand` is not an error and
4844 /// this provider will simply not be found when querying by `brand`.
4845 pub client_id: String,
4846
4847 /// Secret key the provider generated for you along with the `client_id`
4848 /// above. Unlike the `client_id`, the `client_secret` can be changed here
4849 /// whenever the provider regenerates one for you.
4850 ///
4851 /// display: sensitive
4852 pub client_secret: Option<String>,
4853
4854 /// Secret key to use, read from the file path specified.
4855 ///
4856 /// Alternative to `client_secret` for deployments that prefer to keep the
4857 /// secret outside the config file. When both are configured `client_secret`
4858 /// is used and this field is ignored. The file is read at startup and on
4859 /// each OAuth exchange, must exist and must be non-empty; leading and
4860 /// trailing whitespace is trimmed. Under systemd the path must be visible
4861 /// to the service after sandboxing (`ReadWritePaths` / `ProtectHome`),
4862 /// typically by placing the file under `/etc/tuwunel/`.
4863 ///
4864 /// example: "/etc/tuwunel/.client_secret"
4865 pub client_secret_file: Option<PathBuf>,
4866
4867 /// Issuer URL the provider publishes for you. We have pre-supplied default
4868 /// values for some of the canonical public providers, making this field
4869 /// optional based on the `brand` set above. Otherwise it is required to
4870 /// find self-hosted providers. It must be identical to what is configured
4871 /// and expected by the provider and must never change because we associate
4872 /// identities to it. If the `/.well-known/openid-configuration` is not
4873 /// found behind this URL see `base_path` below as a workaround.
4874 pub issuer_url: Option<Url>,
4875
4876 /// The callback URL configured when registering the OAuth application with
4877 /// the provider. Tuwunel's callback URL must be strictly formatted exactly
4878 /// as instructed. The URL host must point directly at the matrix server and
4879 /// use the following path:
4880 /// `/_matrix/client/unstable/login/sso/callback/<client_id>` where
4881 /// `<client_id>` is the same one configured for this provider above.
4882 pub callback_url: Option<Url>,
4883
4884 /// When more than one identity_provider has been configured and
4885 /// `single_sso` is false and `sso_custom_providers_page` is false this will
4886 /// determine the behavior of the `/_matrix/client/v3/login/sso/redirect`
4887 /// endpoint (note the url lacks a trailing `client_id`).
4888 ///
4889 /// When only one identity_provider is configured it will be interpreted
4890 /// as the default and this does not need to be set. Otherwise a default
4891 /// *must* be selected for some clients (e.g. fluffychat) to work properly
4892 /// when the above conditions require it. To operate out-of-the-box we
4893 /// default to one configured provider if none are explicitly default; a
4894 /// warning will be logged on startup for this condition.
4895 ///
4896 /// (EXPERIMENTAL) Multiple providers can be set to default. All providers
4897 /// configured with this option set to `true` will associate with the same
4898 /// Matrix account when a client flows through
4899 /// `/_matrix/client/v3/login/sso/redirect`.
4900 ///
4901 /// When a user authorizes any provider configured default, the flow will
4902 /// include all other providers configured default as well for association.
4903 /// NOTE: authorization must succeed for ALL default providers.
4904 #[serde(default)]
4905 pub default: bool,
4906
4907 /// Optional display-name for this provider instance seen on the login page
4908 /// by users. It defaults to `brand`. When configuring multiple providers
4909 /// using the same `brand` this can be set to distinguish them.
4910 pub name: Option<String>,
4911
4912 /// Optional icon for the provider. The canonical providers have a default
4913 /// icon based on the `brand` supplied above when this is not supplied. Note
4914 /// that it uses an MXC url which is curious in the auth-media era and may
4915 /// not be reliable.
4916 pub icon: Option<OwnedMxcUri>,
4917
4918 /// Optional list of scopes to authorize.
4919 ///
4920 /// An empty array sends `openid email profile`. The exception is
4921 /// `brand = "MAS"`, which sends only `openid`: MAS rejects `profile`, and
4922 /// its userinfo endpoint returns just `sub` and `username`. Set this to
4923 /// request a different subset. The user can further restrict scopes during
4924 /// their authorization.
4925 ///
4926 /// default: []
4927 #[serde(default)]
4928 pub scope: BTreeSet<String>,
4929
4930 /// Optional list of userinfo claims which shape and restrict the way we
4931 /// compute a Matrix UserId for new registrations. Reviewing Tuwunel's
4932 /// documentation will be necessary for a complete description in detail. An
4933 /// empty array imposes no restriction here, avoiding generated fallbacks as
4934 /// much as possible.
4935 ///
4936 /// For simplicity we reserve a claim called "unique" which can be listed
4937 /// alone to ensure *only* generated ID's are used for registrations.
4938 ///
4939 /// Note that listing the claim "sub" has special significance and will take
4940 /// precedence over all other claims, listed or unlisted. "sub" is not
4941 /// normally used to determine a UserId unless explicitly listed here.
4942 ///
4943 /// As of now arbitrary claims cannot be listed here, we only recognize
4944 /// specific hard-coded claims.
4945 ///
4946 /// default: []
4947 #[serde(default)]
4948 pub userid_claims: BTreeSet<String>,
4949
4950 /// Trusted providers can cause username conflicts (i.e. account hijacking)
4951 /// but this is precisely how an existing matrix account can be associated
4952 /// with a provider. When this option is set to true, the way we compute a
4953 /// Matrix UserId from userinfo claims is inverted: we find the first
4954 /// matching user and grant access to it. Whereas by default, when set to
4955 /// false, we skip matching users and register the first available username;
4956 /// falling-back to random characters to avoid conflicts.
4957 ///
4958 /// Only set this option to true for providers you self-host and control.
4959 /// Never set this option to true for the public providers such as GitHub,
4960 /// GitLab, etc.
4961 ///
4962 /// Note that associating an existing user with an untrusted provider is
4963 /// still possible but only with the command '!admin query oauth associate'.
4964 ///
4965 /// default: false
4966 #[serde(default)]
4967 pub trusted: bool,
4968
4969 /// Setting this option to false will inhibit unique ID's from being
4970 /// generated as a last-resort when determining a UserId from a provider's
4971 /// claims. In the case of untrusted providers, when all provided claims
4972 /// conflict with existing user accounts, a unique fallback ID needs
4973 /// to be generated for registration to not be denied with an error.
4974 ///
4975 /// Set this option to false if you operate a private server or a trusted
4976 /// identity provider where random UserId's are undesirable; the result of a
4977 /// misconfiguration or other issue where an error is warranted.
4978 ///
4979 /// This option should be set to true for public servers or some users may
4980 /// never be able to register.
4981 ///
4982 /// default: true
4983 #[serde(default = "true_fn")]
4984 pub unique_id_fallbacks: bool,
4985
4986 /// Controls whether new user registration is possible from this provider.
4987 /// When this option is set to false, authorizations from this provider
4988 /// only affect existing users and will never result in a new registration
4989 /// when the claims fail to match any existing user (in the case of trusted
4990 /// providers) or an available username is found (in the case of untrusted
4991 /// providers).
4992 ///
4993 /// When LDAP is enabled, a user found in the LDAP directory counts as an
4994 /// existing user and is still provisioned on first login, since the
4995 /// directory is the authoritative account store.
4996 ///
4997 /// Setting this option to false is generally not useful unless there is
4998 /// an explicit reason to do so.
4999 ///
5000 /// default: true
5001 #[serde(default = "true_fn")]
5002 pub registration: bool,
5003
5004 /// Optional extra path components after the issuer_url leading to the
5005 /// location of the `.well-known` directory used for discovery. If the path
5006 /// starts with a slash it will be treated as absolute, meaning overwriting
5007 /// any path in the issuer_url. The path needs to end with a slash. This
5008 /// will be empty for specification-compliant providers.
5009 pub base_path: Option<String>,
5010
5011 /// Overrides the `.well-known` location where the provider's openid
5012 /// configuration is found. It is very unlikely you will need to set this;
5013 /// available for developers or special purposes only.
5014 pub discovery_url: Option<Url>,
5015
5016 /// Overrides the authorize URL requested during the grant phase. This is
5017 /// generally discovered or derived automatically, but may be required as a
5018 /// workaround for any non-standard or undiscoverable provider.
5019 pub authorization_url: Option<Url>,
5020
5021 /// Overrides the access token URL; the same caveats apply as with the other
5022 /// URL overrides.
5023 pub token_url: Option<Url>,
5024
5025 /// Overrides the revocation URL; the same caveats apply as with the other
5026 /// URL overrides.
5027 pub revocation_url: Option<Url>,
5028
5029 /// Overrides the introspection URL; the same caveats apply as with the
5030 /// other URL overrides.
5031 pub introspection_url: Option<Url>,
5032
5033 /// Overrides the userinfo URL; the same caveats apply as with the other URL
5034 /// overrides.
5035 pub userinfo_url: Option<Url>,
5036
5037 /// Whether to perform discovery and adjust this provider's configuration
5038 /// accordingly. This defaults to true. When true, it is an error when
5039 /// discovery fails and authorizations will not be attempted to the
5040 /// provider.
5041 #[serde(default = "true_fn")]
5042 pub discovery: bool,
5043
5044 /// The duration in seconds before a grant authorization session expires.
5045 ///
5046 /// default: 300
5047 #[serde(default = "default_sso_grant_session_duration")]
5048 pub grant_session_duration: Option<u64>,
5049
5050 /// Whether to check the redirect cookie during the callback. This is a
5051 /// security feature and should remain enabled. This is available for
5052 /// developers or deployments which cannot tolerate cookies and are willing
5053 /// to tolerate the risks.
5054 ///
5055 /// default: true
5056 #[serde(default = "true_fn")]
5057 pub check_cookie: bool,
5058
5059 /// Extra query parameters appended to every authorization request sent to
5060 /// the identity provider.
5061 ///
5062 /// E.g. to force re-authentication even if IdP cookies are present:
5063 /// ```toml
5064 /// [[global.identity_provider]]
5065 /// extra_authorization_parameters = { prompt = "login" }
5066 /// ```
5067 ///
5068 /// default: {}
5069 #[serde(default)]
5070 pub extra_authorization_parameters: BTreeMap<String, String>,
5071
5072 /// Forward the MSC3824 `action` query parameter from the SSO redirect
5073 /// endpoints to this provider as an OpenID Connect `prompt` value.
5074 ///
5075 /// When a client appends `action=register` to a `/login/sso/redirect`
5076 /// request the upstream authorization request carries `prompt=create`
5077 /// (the OpenID Connect "Initiating User Registration" extension) so the
5078 /// provider can present its registration screen. `action=login` is left
5079 /// unforwarded to avoid forcing a re-authentication, and a `prompt` set in
5080 /// `extra_authorization_parameters` still applies in that case. An
5081 /// action-derived `prompt` takes precedence over one configured there.
5082 ///
5083 /// Leave this disabled unless the provider supports the `prompt=create`
5084 /// registration extension; a provider that does not may reject or ignore
5085 /// the request.
5086 ///
5087 /// default: false
5088 #[serde(default)]
5089 pub forward_action_prompt: bool,
5090}
5091
5092impl IdentityProvider {
5093 /// Returns the provider's stable identifier.
5094 ///
5095 /// The identifier is the OAuth application's client ID. It is borrowed from
5096 /// this configuration without allocation.
5097 #[inline]
5098 #[must_use]
5099 pub fn id(&self) -> &str { self.client_id.as_str() }
5100
5101 /// Loads the effective client secret.
5102 ///
5103 /// An inline secret takes precedence over a configured secret file. File
5104 /// contents are read asynchronously and trimmed before being returned.
5105 pub async fn get_client_secret(&self) -> Result<String> {
5106 if let Some(client_secret) = &self.client_secret {
5107 return Ok(client_secret.clone());
5108 }
5109
5110 let Some(client_secret_file) = &self.client_secret_file else {
5111 return Err!("No client secret or client secret file configured");
5112 };
5113
5114 let client_secret = tokio::fs::read_to_string(client_secret_file).await?;
5115
5116 Ok(client_secret.trim().to_owned())
5117 }
5118}
5119
5120/// Returns the name shown to a user choosing between providers.
5121///
5122/// The configured name wins, and the brand stands in when none is set.
5123#[implement(IdentityProvider)]
5124#[inline]
5125#[must_use]
5126pub fn display_name(&self) -> &str { self.name.as_deref().unwrap_or(&self.brand) }
5127
5128/// Selects the backend for a named media storage provider.
5129///
5130/// Local providers store objects beneath a filesystem path, while S3 providers
5131/// use a compatible object store. The default variant disables the entry.
5132#[derive(Clone, Debug, Default, Deserialize)]
5133pub enum StorageProvider {
5134 /// Selects a local filesystem backend.
5135 ///
5136 /// The contained settings root object paths beneath a configured directory.
5137 /// Startup checks can require that directory to be usable.
5138 #[expect(non_camel_case_types)]
5139 local(StorageProviderLocal),
5140
5141 /// Selects an S3-compatible object storage backend.
5142 ///
5143 /// The boxed settings configure endpoint, credentials, encryption, and
5144 /// multipart uploads. Custom endpoints permit compatible non-AWS services.
5145 #[expect(non_camel_case_types)]
5146 #[serde(rename = "s3", alias = "S3")]
5147 s3(Box<StorageProviderS3>),
5148
5149 /// Disables this storage provider entry.
5150 ///
5151 /// This is the default when no backend variant is selected. It carries no
5152 /// backend settings.
5153 #[default]
5154 None,
5155}
5156
5157/// Configures local filesystem object storage.
5158///
5159/// `base_path` prefixes every object path belonging to this provider. Remaining
5160/// options control directory creation, cleanup, and startup checks.
5161#[derive(Clone, Debug, Default, Deserialize)]
5162#[config_example_generator(
5163 filename = "tuwunel-example.toml",
5164 section = "global.storage_provider.<ID>.local"
5165)]
5166pub struct StorageProviderLocal {
5167 /// Absolute path to this local filesystem storage provider. Technically the
5168 /// provider exists at the filesystem root, and the base_path is prefixed to
5169 /// all objects.
5170 #[serde(alias = "path")]
5171 pub base_path: String,
5172
5173 /// Creates the directory on the local filesystem if missing. This is not
5174 /// recommended to prevent misconfigured environments and missing mounts
5175 /// from silently succeeding.
5176 #[serde(default)]
5177 pub create_if_missing: bool,
5178
5179 /// Toggles the preservation of a directory after its last file contents are
5180 /// removed.
5181 #[serde(default = "true_fn")]
5182 pub delete_empty_directories: bool,
5183
5184 /// Enables checks performed at startup determining the usability of the
5185 /// local directory. Failures will abort the server's startup.
5186 ///
5187 /// default: true
5188 #[serde(default = "true_fn")]
5189 pub startup_check: bool,
5190}
5191
5192/// Configures an S3-compatible object storage provider.
5193///
5194/// Bucket, endpoint, and credential fields identify the remote store.
5195/// Transport, encryption, multipart, and startup options tune how objects are
5196/// accessed.
5197#[derive(Clone, Debug, Default, Deserialize)]
5198#[config_example_generator(
5199 filename = "tuwunel-example.toml",
5200 section = "global.storage_provider.<ID>.s3",
5201 section_aliases = "S3"
5202)]
5203pub struct StorageProviderS3 {
5204 /// Supply an s3 URL e.g. "s3://bucket/path". These URLs may contain one
5205 /// or all of `bucket`, `region`, and `path` . When not supplied, such
5206 /// additional items can be supplied below individually.
5207 pub url: Option<String>,
5208
5209 /// The name of the S3 bucket. e.g. "bucketname-123456789-us-west-2-an".
5210 pub bucket: Option<String>,
5211
5212 /// The region of the S3 bucket. e.g. "us-west-2".
5213 ///
5214 /// default: "us-east-1"
5215 pub region: Option<String>,
5216
5217 /// Your amazon IAM Key ID with access granted to this bucket.
5218 /// e.g. "ABCDEFG1X1ZZYYXXWWVV"
5219 #[debug("{}", redacted_debug!(key))]
5220 pub key: Option<String>,
5221
5222 /// The secret key component which is approx 40 characters of base64.
5223 ///
5224 /// default:
5225 /// display: sensitive
5226 #[serde(skip_serializing)]
5227 #[debug("{}", redacted_debug!(secret))]
5228 pub secret: Option<String>,
5229
5230 /// Optional path prefix within the bucket where all our operations will
5231 /// take place.
5232 #[serde(alias = "path")]
5233 pub base_path: Option<String>,
5234
5235 /// (expert use) Override the location of s3 applied after components of the
5236 /// parsed `url` (or when none set).
5237 pub endpoint: Option<String>,
5238
5239 /// (expert use) Override this property useful for some self-hosted
5240 /// environments. By default it is derived when parsing the primary `url`.
5241 #[serde(default)]
5242 pub use_vhost_request: Option<bool>,
5243
5244 /// (expert use) Alternative session-token authentication method.
5245 ///
5246 /// display: sensitive
5247 /// default:
5248 #[serde(skip_serializing)]
5249 #[debug("{}", redacted_debug!(token))]
5250 pub token: Option<String>,
5251
5252 /// (expert use) Associated SSE-KMS key material.
5253 ///
5254 /// display: sensitive
5255 #[debug("{}", redacted_debug!(kms))]
5256 pub kms: Option<String>,
5257
5258 /// (expert use) When configured for the bucket it should be reflected here.
5259 pub use_bucket_key: Option<bool>,
5260
5261 /// (expert use) Threshold size for switching to Multi-part uploads. This is
5262 /// a quirk of the S3 protocol which requires us to use a different approach
5263 /// for "large" uploads. This value determines what a "large" upload is. The
5264 /// default value should be sufficient for most providers. The value is a
5265 /// parsed string allowing SI or IEC units for convenience.
5266 ///
5267 /// default: 100 MiB
5268 #[serde(default = "default_multipart_threshold")]
5269 pub multipart_threshold: ByteSize,
5270
5271 /// (expert use) Size of each individual part within a Multi-part upload.
5272 /// Once an upload exceeds `multipart_threshold` the payload is split into
5273 /// parts of this size, each sent as a separate HTTP PUT. Smaller values
5274 /// keep individual requests under per-request timeouts on slow uplinks at
5275 /// the cost of more round-trips. S3 requires every part except the last
5276 /// to be at least 5 MiB. The value is a parsed string allowing SI or IEC
5277 /// units for convenience.
5278 ///
5279 /// default: 10 MiB
5280 #[serde(default = "default_multipart_part_size")]
5281 pub multipart_part_size: ByteSize,
5282
5283 /// (developer use) Allows relaxing default requirement forcing HTTPS.
5284 ///
5285 /// default: true
5286 #[serde(default = "some_true_fn")]
5287 pub use_https: Option<bool>,
5288
5289 /// (developer_use) Allows skipping request header signatures (will be
5290 /// reejected by AWS).
5291 ///
5292 /// default: true
5293 #[serde(default = "some_true_fn")]
5294 pub use_signatures: Option<bool>,
5295
5296 /// (developer_use) Allows disabling request payload signatures.
5297 ///
5298 /// default: true
5299 #[serde(default = "some_true_fn")]
5300 pub use_payload_signatures: Option<bool>,
5301
5302 /// (developer use) Enables checks performed at startup such as pinging the
5303 /// provider. Failures are considered critical startup errors which abort
5304 /// startup. When set to false, faulty providers are only discovered with
5305 /// first use and will not be fatal errors.
5306 ///
5307 /// Only set this to false if you expect a provider to be down at startup or
5308 /// for development/testing purposes; checks are disabled when the server
5309 /// is started in '--maintenance' mode.
5310 ///
5311 /// default: true
5312 #[serde(default = "true_fn")]
5313 pub startup_check: bool,
5314}
5315
5316/// Defines one inline Matrix application service registration.
5317///
5318/// Tokens, namespaces, and protocol flags are converted to the Matrix
5319/// registration model. The enclosing config map supplies the registration ID
5320/// when `id` is empty.
5321#[derive(Clone, Debug, Default, Deserialize)]
5322#[config_example_generator(
5323 filename = "tuwunel-example.toml",
5324 section = "global.appservice.<ID>",
5325 ignore = "users aliases rooms",
5326 hidden = "id"
5327)]
5328pub struct AppService {
5329 /// Identifies the application service registration.
5330 ///
5331 /// An empty value is replaced with the enclosing config map key. An
5332 /// explicit value must match that key.
5333 #[serde(default)]
5334 pub id: String,
5335
5336 /// The URL for the application service.
5337 ///
5338 /// Optionally set to `null` if no traffic is required.
5339 pub url: Option<String>,
5340
5341 /// A unique token for application services to use to authenticate requests
5342 /// to Homeservers.
5343 ///
5344 /// default:
5345 /// display: sensitive
5346 pub as_token: String,
5347
5348 /// A unique token for Homeservers to use to authenticate requests to
5349 /// application services.
5350 ///
5351 /// default:
5352 /// display: sensitive
5353 pub hs_token: String,
5354
5355 /// The localpart of the user associated with the application service.
5356 pub sender_localpart: Option<String>,
5357
5358 /// Events which are sent from certain users.
5359 #[serde(default)]
5360 pub users: Vec<AppServiceNamespace>,
5361
5362 /// Events which are sent in rooms with certain room aliases.
5363 #[serde(default)]
5364 pub aliases: Vec<AppServiceNamespace>,
5365
5366 /// Events which are sent in rooms with certain room IDs.
5367 #[serde(default)]
5368 pub rooms: Vec<AppServiceNamespace>,
5369
5370 /// Whether requests from masqueraded users are rate-limited.
5371 ///
5372 /// The sender is excluded.
5373 #[serde(default)]
5374 pub rate_limited: bool,
5375
5376 /// The external protocols which the application service provides (e.g.
5377 /// IRC).
5378 ///
5379 /// default: []
5380 #[serde(default)]
5381 pub protocols: Vec<String>,
5382
5383 /// Whether the application service wants to receive ephemeral data.
5384 ///
5385 /// default: false
5386 #[serde(default)]
5387 pub receive_ephemeral: bool,
5388
5389 /// Whether the application service wants to do device management, as part
5390 /// of MSC4190.
5391 ///
5392 /// default: false
5393 #[serde(default)]
5394 pub device_management: bool,
5395
5396 /// Whether the application service wants MSC3202 transaction extensions
5397 /// (device lists, one-time-key counts, and unused fallback key types).
5398 ///
5399 /// The registration-file key is `org.matrix.msc3202`; this inline-config
5400 /// key is `msc3202_transaction_extensions`.
5401 ///
5402 /// default: false
5403 #[serde(default)]
5404 pub msc3202_transaction_extensions: bool,
5405}
5406
5407impl From<AppService> for ruma::api::appservice::Registration {
5408 fn from(conf: AppService) -> Self {
5409 use ruma::api::appservice::Namespaces;
5410
5411 let sender_localpart = conf
5412 .sender_localpart
5413 .unwrap_or_else(|| conf.id.clone());
5414
5415 Self {
5416 id: conf.id,
5417 url: conf.url,
5418 as_token: conf.as_token,
5419 hs_token: conf.hs_token,
5420 receive_ephemeral: conf.receive_ephemeral,
5421 device_management: conf.device_management,
5422 msc3202_transaction_extensions: conf.msc3202_transaction_extensions,
5423 protocols: conf.protocols.into(),
5424 rate_limited: conf.rate_limited.into(),
5425 sender_localpart,
5426 namespaces: Namespaces {
5427 users: conf.users.into_iter().map(Into::into).collect(),
5428 aliases: conf.aliases.into_iter().map(Into::into).collect(),
5429 rooms: conf.rooms.into_iter().map(Into::into).collect(),
5430 },
5431 keys_claims: false,
5432 }
5433 }
5434}
5435
5436/// Defines one namespace claimed by an application service.
5437///
5438/// The regular expression selects users, aliases, or rooms according to the
5439/// list containing this value. `exclusive` controls whether the service owns
5440/// every matching identifier.
5441#[derive(Clone, Debug, Default, Deserialize)]
5442#[config_example_generator(
5443 filename = "tuwunel-example.toml",
5444 section = "[global.appservice.<ID>.<users|rooms|aliases>]"
5445)]
5446pub struct AppServiceNamespace {
5447 /// Whether this application service has exclusive access to events within
5448 /// this namespace.
5449 #[serde(default)]
5450 pub exclusive: bool,
5451
5452 /// A regular expression defining which values this namespace includes.
5453 pub regex: String,
5454}
5455
5456impl From<AppServiceNamespace> for ruma::api::appservice::Namespace {
5457 fn from(conf: AppServiceNamespace) -> Self {
5458 Self {
5459 exclusive: conf.exclusive,
5460 regex: conf.regex,
5461 }
5462 }
5463}
5464
5465/// Items matched here will not generate an "unknown to tuwunel" warning when
5466/// configured. This is important for environment variables which share the
5467/// `TUWUNEL_` prefix namespace but aren't config items; match them here in
5468/// their split+lowercased format.
5469static KNOWN_KEYS: &[&str; 3] = &["^config$", "^runtime_[a-z0-9_]+$", "^appservice_keys_claims$"];
5470
5471/// Items listed here generate a deprecation warning when configured.
5472static DEPRECATED_KEYS: &[&str; 11] = &[
5473 "appservice_keys_claims",
5474 "cache_capacity",
5475 "conduit_cache_capacity_modifier",
5476 "ldap.name_attribute",
5477 "max_concurrent_requests",
5478 "well_known_client",
5479 "well_known_server",
5480 "well_known_support_page",
5481 "well_known_support_role",
5482 "well_known_support_email",
5483 "well_known_support_mxid",
5484];
5485
5486impl Config {
5487 /// Loads raw configuration from ordered file and environment sources.
5488 ///
5489 /// Explicit paths follow config files selected through the supported
5490 /// environment variables, and later files override earlier files. Config
5491 /// values from the three environment namespaces take precedence over files.
5492 pub fn load<'a, I>(paths: I) -> Result<Figment>
5493 where
5494 I: Iterator<Item = &'a Path>,
5495 {
5496 let paths = Self::file_paths(paths);
5497 let config = Self::load_files(paths)?;
5498
5499 Ok(Self::merge_environment(config))
5500 }
5501}
5502
5503#[implement(Config)]
5504pub(crate) fn file_paths<'a, I>(paths: I) -> impl Iterator<Item = PathBuf>
5505where
5506 I: Iterator<Item = &'a Path>,
5507{
5508 [
5509 Env::var("CONDUIT_CONFIG"),
5510 Env::var("CONDUWUIT_CONFIG"),
5511 Env::var("TUWUNEL_CONFIG"),
5512 ]
5513 .into_iter()
5514 .flatten()
5515 .map(PathBuf::from)
5516 .chain(paths.map(Path::to_path_buf))
5517}
5518
5519#[implement(Config)]
5520pub(crate) fn load_files<I, P>(paths: I) -> Result<Figment>
5521where
5522 I: Iterator<Item = P>,
5523 P: Into<PathBuf>,
5524{
5525 let toml_files = paths.map(Into::into).collect_vec();
5526
5527 let invalid_toml_files = toml_files
5528 .iter()
5529 .filter(|path| !path.exists())
5530 .map(|path| path.as_os_str())
5531 .collect_vec();
5532
5533 if !invalid_toml_files.is_empty() {
5534 return Err!(
5535 "The following config files do not exist or have broken symlinks: \
5536 {invalid_toml_files:?}"
5537 );
5538 }
5539
5540 toml_files
5541 .iter()
5542 .try_fold(Figment::new(), |config, path| {
5543 Self::load_file(path).map(|file| config.merge(file))
5544 })
5545}
5546
5547#[implement(Config)]
5548fn load_file(path: &Path) -> Result<Figment> {
5549 let provider = Toml::file(path);
5550 let profiles = Provider::data(&provider)?;
5551 let values = profiles.get(&Profile::Default);
5552 let headerless = values.is_some_and(|values| {
5553 values
5554 .values()
5555 .any(|value| value.as_dict().is_none())
5556 });
5557
5558 let has_global = values.is_some_and(|values| values.contains_key("global"));
5559
5560 if headerless && has_global {
5561 return Err!(
5562 "Configuration file mixes bare keys with a [global] profile: {}.",
5563 path.display()
5564 );
5565 }
5566
5567 let provider = match headerless {
5568 | true => provider.profile(Profile::Global),
5569 | false => provider.nested(),
5570 };
5571
5572 Ok(Figment::new().merge(provider))
5573}
5574
5575#[implement(Config)]
5576pub(crate) fn merge_environment(config: Figment) -> Figment {
5577 ENV_PREFIXES
5578 .into_iter()
5579 .fold(config, |config, prefix| {
5580 config.merge(Env::prefixed(prefix).global().split("__"))
5581 })
5582}
5583
5584impl Config {
5585 /// Finalize config
5586 pub fn new(raw_config: &Figment) -> Result<Self> {
5587 let config = raw_config
5588 .extract::<Self>()
5589 .map_err(|e| err!("There was a problem with your configuration file: {e}"))?;
5590
5591 Ok(config)
5592 }
5593
5594 /// Validates the complete configuration.
5595 ///
5596 /// The startup checks emit warnings for deprecated or risky settings and
5597 /// reject invalid combinations. Reload-specific comparisons are performed
5598 /// by the configuration manager separately.
5599 pub fn check(&self) -> Result { check(self) }
5600}
5601
5602/// Argon2id cost parameters for hashing a new password.
5603///
5604/// The three settings are interdependent, so they travel as a unit rather than
5605/// being read one at a time. Verification never consults them: the cost of
5606/// checking a password comes from the stored hash.
5607#[implement(Config)]
5608#[inline]
5609#[must_use]
5610pub fn password_hash_cost(&self) -> Cost {
5611 Cost {
5612 m_cost: self.argon2_m_cost,
5613 t_cost: self.argon2_t_cost,
5614 p_cost: self.argon2_p_cost,
5615 }
5616}
5617
5618/// Returns whether login screens list each identity provider by name.
5619///
5620/// `single_sso` and `sso_custom_providers_page` each replace that list with
5621/// one single sign-on entry.
5622#[implement(Config)]
5623#[inline]
5624#[must_use]
5625pub fn lists_identity_providers(&self) -> bool {
5626 !self.sso_custom_providers_page && !self.single_sso
5627}
5628
5629impl TlsConfig {
5630 /// Returns the configured TLS certificate and key paths together.
5631 ///
5632 /// A pair is returned only when both options are present. Startup
5633 /// validation rejects a configuration containing only one of the two
5634 /// paths.
5635 #[must_use]
5636 pub fn get_tls_cert_key(&self) -> Option<(&Path, &Path)> {
5637 let cert = self.certs.as_ref()?;
5638
5639 let cert = Path::new(cert);
5640
5641 let key = self.key.as_ref()?; // this cannot fail, aborts startup on cert.is_some ^ key.is_some
5642
5643 let key = Path::new(key);
5644
5645 Some((cert, key))
5646 }
5647}
5648
5649impl Default for LoginFailedRateLimit {
5650 fn default() -> Self {
5651 Self {
5652 per_second: default_login_failed_per_second(),
5653 burst_count: default_login_failed_burst_count(),
5654 }
5655 }
5656}
5657
5658impl Default for LoginAccountRateLimit {
5659 fn default() -> Self {
5660 Self {
5661 per_second: default_login_account_per_second(),
5662 burst_count: default_login_account_burst_count(),
5663 }
5664 }
5665}
5666
5667fn true_fn() -> bool { true }
5668
5669fn default_policy_server_request_timeout() -> u64 { 5 }
5670
5671fn default_rendezvous_session_max_bytes() -> usize { 4096 }
5672
5673fn default_rendezvous_session_ttl() -> u64 { 600 }
5674
5675fn default_rendezvous_max_sessions() -> usize { 100 }
5676
5677fn default_rendezvous_rc_per_second() -> u32 { 10 }
5678
5679fn default_rendezvous_rc_burst_count() -> u32 { 20 }
5680
5681// Synapse's `rc_login` defaults, taken unchanged:
5682// https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#rc_login
5683fn default_login_failed_per_second() -> f64 { 0.17 }
5684
5685fn default_login_failed_burst_count() -> u32 { 3 }
5686
5687fn default_login_account_per_second() -> f64 { 0.003 }
5688
5689fn default_login_account_burst_count() -> u32 { 5 }
5690
5691fn some_true_fn() -> Option<bool> { Some(true) }
5692
5693#[cfg(test)]
5694fn default_server_name() -> OwnedServerName { ruma::owned_server_name!("localhost") }
5695
5696fn default_database_path() -> PathBuf { "/var/lib/tuwunel".to_owned().into() }
5697
5698fn default_conduit_media_directory_depth() -> u8 { 2 }
5699
5700fn default_conduit_media_directory_length() -> u8 { 2 }
5701
5702fn default_port() -> ListeningPort { ListeningPort { ports: Left(8008) } }
5703
5704fn default_unix_socket_perms() -> u32 { 660 }
5705
5706fn default_database_backups_to_keep() -> i16 { 1 }
5707
5708fn default_db_write_buffer_capacity_mb() -> f64 { 48.0 + parallelism_scaled_f64(4.0) }
5709
5710fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
5711
5712fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
5713
5714fn default_cache_capacity_modifier() -> f64 { 1.0 }
5715
5716fn default_auth_chain_cache_capacity() -> u32 {
5717 parallelism_scaled_u32(250_000).saturating_add(750_000)
5718}
5719
5720fn default_shorteventid_cache_capacity() -> u32 {
5721 parallelism_scaled_u32(200_000).saturating_add(400_000)
5722}
5723
5724fn default_eventidshort_cache_capacity() -> u32 {
5725 parallelism_scaled_u32(100_000).saturating_add(400_000)
5726}
5727
5728fn default_eventid_pdu_cache_capacity() -> u32 {
5729 parallelism_scaled_u32(100_000).saturating_add(400_000)
5730}
5731
5732fn default_eventid_backoff_cache_capacity() -> u32 {
5733 parallelism_scaled_u32(4_000).saturating_add(200_000)
5734}
5735
5736fn default_shortstatekey_cache_capacity() -> u32 {
5737 parallelism_scaled_u32(4_000).saturating_add(40_000)
5738}
5739
5740fn default_statekeyshort_cache_capacity() -> u32 {
5741 parallelism_scaled_u32(4_000).saturating_add(40_000)
5742}
5743
5744fn default_servernameevent_data_cache_capacity() -> u32 {
5745 parallelism_scaled_u32(50_000).saturating_add(200_000)
5746}
5747
5748fn default_servername_status_cache_capacity() -> u32 {
5749 parallelism_scaled_u32(10_000).saturating_add(100_000)
5750}
5751
5752fn default_resolver_cache_capacity() -> u32 {
5753 parallelism_scaled_u32(10_000).saturating_add(100_000)
5754}
5755
5756fn default_mediaid_lazycontent_cache_capacity() -> u32 { 128 }
5757
5758fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(100) }
5759
5760fn default_spacehierarchy_cache_ttl_min() -> u64 { 60 * 60 * 3 }
5761
5762fn default_spacehierarchy_cache_ttl_max() -> u64 { 60 * 60 * 18 }
5763
5764fn default_dns_cache_entries() -> u32 { 32768 }
5765
5766fn default_dns_min_ttl() -> u64 { 60 * 180 }
5767
5768fn default_dns_min_ttl_nxdomain() -> u64 { 60 * 60 * 24 * 3 }
5769
5770fn default_dns_attempts() -> u16 { 10 }
5771
5772fn default_dns_timeout() -> u64 { 10 }
5773
5774fn default_ip_lookup_strategy() -> u8 { 5 }
5775
5776fn default_max_request_size() -> usize { 24 * 1024 * 1024 }
5777
5778fn default_max_response_size() -> usize { 256 * 1024 * 1024 }
5779
5780fn default_max_pending_media_uploads() -> usize { 5 }
5781
5782fn default_media_create_unused_expiration_time() -> u64 { 86400 }
5783
5784fn default_media_rc_create_per_second() -> u32 { 10 }
5785
5786fn default_media_rc_create_burst_count() -> u32 { 50 }
5787
5788fn default_media_thumbnail_max_pixels() -> u64 { 50_000_000 }
5789
5790fn default_media_thumbnail_max_frames() -> usize { 50 }
5791
5792fn default_media_thumbnail_animated_concurrency() -> usize { 4 }
5793
5794fn default_media_video_thumbnail_timeout() -> u64 { 30 }
5795
5796fn default_media_video_thumbnail_concurrency() -> usize { 1 }
5797
5798fn default_media_video_thumbnail_max_size() -> usize { 128 * 1024 * 1024 }
5799
5800fn default_request_conn_timeout() -> u64 { 10 }
5801
5802fn default_request_timeout() -> u64 { 35 }
5803
5804fn default_request_total_timeout() -> u64 { 320 }
5805
5806fn default_request_idle_timeout() -> u64 { 5 }
5807
5808fn default_request_idle_per_host() -> u16 { 1 }
5809
5810fn default_well_known_conn_timeout() -> u64 { 6 }
5811
5812fn default_well_known_timeout() -> u64 { 10 }
5813
5814fn default_federation_timeout() -> u64 { 25 }
5815
5816fn default_federation_keys_timeout() -> u64 { 8 }
5817
5818fn default_feds_timeout() -> u64 { 15 }
5819
5820fn default_feds_destination_limit() -> usize { 2048 }
5821
5822fn default_federation_idle_timeout() -> u64 { 25 }
5823
5824fn default_federation_idle_per_host() -> u16 { 1 }
5825
5826fn default_sender_timeout() -> u64 { 180 }
5827
5828fn default_sender_idle_timeout() -> u64 { 180 }
5829
5830fn default_sender_retry_backoff_limit() -> u64 { 86400 }
5831
5832fn default_sender_retry_grace() -> u64 { 15 }
5833
5834fn default_appservice_timeout() -> u64 { 35 }
5835
5836fn default_appservice_idle_timeout() -> u64 { 300 }
5837
5838fn default_pusher_idle_timeout() -> u64 { 15 }
5839
5840fn default_max_fetch_prev_events() -> u16 { 1024_u16 }
5841
5842fn default_prev_events_concurrency() -> u16 {
5843 u16::try_from(MAX_PREV_EVENTS).expect("MAX_PREV_EVENTS exceeds u16")
5844}
5845
5846fn default_fetch_prev_wait_ms() -> u64 { 750 }
5847
5848fn default_resolve_state_locally_max() -> usize { 256 }
5849
5850fn default_forward_extremities_max() -> usize { 60 }
5851
5852fn default_forward_extremities_emergency_max() -> usize { 256 }
5853
5854fn default_forward_extremities_prune_batch() -> usize { 32 }
5855
5856fn default_tracing_flame_filter() -> String {
5857 cfg!(debug_assertions)
5858 .then_some("trace,h2=off")
5859 .unwrap_or("info")
5860 .to_owned()
5861}
5862
5863fn default_jaeger_filter() -> String {
5864 cfg!(debug_assertions)
5865 .then_some("trace,h2=off")
5866 .unwrap_or("info")
5867 .to_owned()
5868}
5869
5870fn default_tracing_flame_output_path() -> String { "./tracing.folded".to_owned() }
5871
5872fn default_trusted_servers() -> Vec<OwnedServerName> {
5873 vec![OwnedServerName::try_from("matrix.org").expect("valid ServerName")]
5874}
5875
5876/// do debug logging by default for debug builds
5877#[must_use]
5878pub fn default_log() -> String {
5879 cfg!(debug_assertions)
5880 .then_some("debug")
5881 .unwrap_or("info")
5882 .to_owned()
5883}
5884
5885/// Returns the default tracing span-event mode.
5886///
5887/// The value is `none`, which disables span lifecycle event emission. It is
5888/// used when `log_span_events` is omitted.
5889#[must_use]
5890pub fn default_log_span_events() -> String { "none".into() }
5891
5892fn default_notification_push_path() -> String { "/_matrix/push/v1/notify".to_owned() }
5893
5894fn default_openid_token_ttl() -> u64 { 60 * 60 }
5895
5896fn default_login_token_ttl() -> u64 { 2 * 60 * 1000 }
5897
5898fn default_turn_ttl() -> u64 { 60 * 60 * 24 }
5899
5900fn default_presence_idle_timeout_s() -> u64 { 5 * 60 }
5901
5902fn default_presence_offline_timeout_s() -> u64 { 30 * 60 }
5903
5904fn default_typing_federation_timeout_s() -> u64 { 30 }
5905
5906fn default_typing_client_timeout_min_s() -> u64 { 15 }
5907
5908fn default_typing_client_timeout_max_s() -> u64 { 45 }
5909
5910fn default_rocksdb_recovery_mode() -> u8 { 1 }
5911
5912fn default_rocksdb_log_level() -> String { "error".to_owned() }
5913
5914fn default_rocksdb_log_time_to_roll() -> usize { 0 }
5915
5916fn default_rocksdb_max_log_files() -> usize { 3 }
5917
5918fn default_rocksdb_max_log_file_size() -> usize {
5919 // 4 megabytes
5920 4 * 1024 * 1024
5921}
5922
5923fn default_rocksdb_parallelism_threads() -> usize { 0 }
5924
5925fn default_rocksdb_compression_algo() -> String {
5926 cfg!(feature = "zstd_compression")
5927 .then_some("zstd")
5928 .unwrap_or("none")
5929 .to_owned()
5930}
5931
5932/// Default RocksDB compression level is 32767, which is internally read by
5933/// RocksDB as the default magic number and translated to the library's default
5934/// compression level as they all differ. See their `kDefaultCompressionLevel`.
5935#[expect(clippy::doc_markdown)]
5936fn default_rocksdb_compression_level() -> i32 { 32767 }
5937
5938/// Default RocksDB compression level is 32767, which is internally read by
5939/// RocksDB as the default magic number and translated to the library's default
5940/// compression level as they all differ. See their `kDefaultCompressionLevel`.
5941#[expect(clippy::doc_markdown)]
5942fn default_rocksdb_bottommost_compression_level() -> i32 { 32767 }
5943
5944fn default_rocksdb_stats_level() -> u8 { 1 }
5945
5946/// Returns the default Matrix room version.
5947///
5948/// Room version 12 is selected when `default_room_version` is omitted. The
5949/// value is returned without consulting runtime configuration.
5950// I know, it's a great name
5951#[must_use]
5952#[inline]
5953pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V12 }
5954
5955fn default_ip_range_denylist() -> Vec<String> {
5956 vec![
5957 "127.0.0.0/8".to_owned(),
5958 "10.0.0.0/8".to_owned(),
5959 "172.16.0.0/12".to_owned(),
5960 "192.168.0.0/16".to_owned(),
5961 "100.64.0.0/10".to_owned(),
5962 "192.0.0.0/24".to_owned(),
5963 "169.254.0.0/16".to_owned(),
5964 "192.88.99.0/24".to_owned(),
5965 "198.18.0.0/15".to_owned(),
5966 "192.0.2.0/24".to_owned(),
5967 "198.51.100.0/24".to_owned(),
5968 "203.0.113.0/24".to_owned(),
5969 "224.0.0.0/4".to_owned(),
5970 "::1/128".to_owned(),
5971 "fe80::/10".to_owned(),
5972 "fc00::/7".to_owned(),
5973 "2001:db8::/32".to_owned(),
5974 "ff00::/8".to_owned(),
5975 "fec0::/10".to_owned(),
5976 ]
5977}
5978
5979fn default_url_preview_max_spider_size() -> usize {
5980 768 * 1024 // 768 KiB
5981}
5982
5983fn default_url_preview_max_media_size() -> usize {
5984 50 * 1024 * 1024 // 50 MiB
5985}
5986
5987fn default_url_preview_cache_ttl() -> u64 { 60 * 60 * 24 }
5988
5989fn default_new_user_displayname_suffix() -> String { "💕".to_owned() }
5990
5991fn default_argon2_m_cost() -> u32 { 19 * 1024 }
5992
5993fn default_argon2_t_cost() -> u32 { 2 }
5994
5995fn default_argon2_p_cost() -> u32 { 1 }
5996
5997fn default_sentry_endpoint() -> Option<Url> {
5998 let url = "https://8994b1762a6a95af9502a7900edabc4c@o4509498990067712.ingest.us.sentry.io/4509498993213440"
5999 .try_into()
6000 .expect("default sentry url is invalid");
6001
6002 Some(url)
6003}
6004
6005fn default_sentry_traces_sample_rate() -> f32 { 0.15 }
6006
6007fn default_sentry_filter() -> String { "info".to_owned() }
6008
6009fn default_startup_netburst_keep() -> i64 { 50 }
6010
6011fn default_admin_log_capture() -> String {
6012 cfg!(debug_assertions)
6013 .then_some("debug")
6014 .unwrap_or("info")
6015 .to_owned()
6016}
6017
6018fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
6019
6020/// Preserves the administrative identity used before it was configurable.
6021///
6022/// Omitting the setting keeps existing installations on their original user.
6023fn default_server_user_localpart() -> ServerUserLocalpart { "conduit".into() }
6024
6025fn default_admin_output_max_events() -> usize { 1 }
6026
6027#[expect(clippy::as_conversions, clippy::cast_precision_loss)]
6028fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
6029
6030fn parallelism_scaled_u32(val: u32) -> u32 {
6031 let val = val
6032 .try_into()
6033 .expect("failed to cast u32 to usize");
6034 parallelism_scaled(val)
6035 .try_into()
6036 .unwrap_or(u32::MAX)
6037}
6038
6039fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
6040
6041fn default_trusted_server_batch_size() -> usize { 192 }
6042
6043fn default_trusted_server_batch_concurrency() -> usize { 2 }
6044
6045fn default_db_pool_workers() -> usize {
6046 sys::available_parallelism()
6047 .saturating_mul(4)
6048 .clamp(32, 1024)
6049}
6050
6051fn default_db_pool_workers_limit() -> usize { 32 }
6052
6053fn default_db_pool_max_workers() -> usize { 2048 }
6054
6055fn default_db_pool_queue_mult() -> usize { 4 }
6056
6057fn default_stream_width_default() -> usize { 32 }
6058
6059fn default_stream_width_scale() -> f32 { 1.0 }
6060
6061fn default_stream_amplification() -> usize { 1024 }
6062
6063fn default_client_receive_timeout() -> u64 { 75 }
6064
6065fn default_client_request_timeout() -> u64 { 240 }
6066
6067fn default_client_response_timeout() -> u64 { 120 }
6068
6069fn default_client_shutdown_timeout() -> u64 { 15 }
6070
6071fn default_sender_shutdown_timeout() -> u64 { 5 }
6072
6073fn default_ldap_search_filter() -> String { "(objectClass=*)".to_owned() }
6074
6075fn default_ldap_uid_attribute() -> String { String::from("uid") }
6076
6077fn default_jwt_algorithm() -> String { "HS256".to_owned() }
6078
6079fn default_jwt_format() -> String { "HMAC".to_owned() }
6080
6081fn default_client_sync_timeout_min() -> u64 { 5000 }
6082
6083fn default_client_sync_timeout_default() -> u64 { 30000 }
6084
6085fn default_client_sync_timeout_max() -> u64 { 90000 }
6086
6087fn default_access_token_ttl() -> u64 { 604_800 }
6088
6089fn default_refresh_token_reuse_grace() -> u64 { 15 }
6090
6091fn default_deprioritize_joins_through_servers() -> RegexSet {
6092 RegexSet::new([r"matrix\.org"]).expect("valid set of regular expressions")
6093}
6094
6095fn default_one_time_key_limit() -> usize { 256 }
6096
6097fn default_max_make_join_attempts_per_join_attempt() -> usize { 48 }
6098
6099fn default_max_join_attempts_per_join_request() -> usize { 3 }
6100
6101fn default_sso_grant_session_duration() -> Option<u64> { Some(300) }
6102
6103fn default_redaction_retention_seconds() -> u64 { 5_184_000 }
6104
6105fn default_media_storage_providers() -> BTreeSet<String> { ["media".to_owned()].into() }
6106
6107fn default_multipart_threshold() -> ByteSize { ByteSize::mib(100) }
6108
6109fn default_multipart_part_size() -> ByteSize { ByteSize::mib(10) }