tuwunel_core/config/mod.rs
1pub mod check;
2mod identity_provider_serde;
3pub mod ip_source;
4pub mod manager;
5mod net;
6pub mod proxy;
7pub mod room_version;
8#[cfg(test)]
9mod tests;
10pub mod well_known;
11
12use std::{
13 collections::{BTreeMap, BTreeSet},
14 net::IpAddr,
15 path::{Path, PathBuf},
16};
17
18use bytesize::ByteSize;
19use derive_more::Debug;
20use either::{Either, Either::Left};
21use figment::providers::{Data, Env, Format, Toml};
22pub use figment::{Figment, value::Value as FigmentValue};
23use ipnet::IpNet;
24use itertools::Itertools;
25use regex::RegexSet;
26use ruma::{
27 OwnedMxcUri, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
28 api::client::discovery::discover_support::ContactRole,
29};
30use serde::{Deserialize, de::IgnoredAny};
31use tuwunel_macros::config_example_generator;
32use url::Url;
33
34pub use self::{check::check, ip_source::IpSource, manager::Manager};
35use self::{
36 net::{ListeningAddr, ListeningPort},
37 proxy::ProxyConfig,
38};
39use crate::{
40 Err, Result, err, redacted_debug,
41 utils::{self, bytes::deserialize_bytesize_usize, sys},
42};
43
44/// All the config options for tuwunel.
45#[expect(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
46#[derive(Clone, Deserialize)]
47#[config_example_generator(
48 filename = "tuwunel-example.toml",
49 section = "global",
50 undocumented = "# This item is undocumented. Please contribute documentation for it.",
51 header = r#"### Tuwunel Configuration
52###
53### THIS FILE IS GENERATED. CHANGES/CONTRIBUTIONS IN THE REPO WILL BE
54### OVERWRITTEN!
55###
56### You should rename this file before configuring your server. Changes to
57### documentation and defaults can be contributed in source code at
58### src/core/config/mod.rs. This file is generated when building.
59###
60### Any values pre-populated are the default values for said config option.
61###
62### At the minimum, you MUST edit all the config options to your environment
63### that say "YOU NEED TO EDIT THIS".
64###
65### For more information, see:
66### https://tuwunel.chat/configuration.html
67"#,
68 ignore = "catchall well_known tls allow_invalid_tls_certificates ldap jwt appservice \
69 identity_provider storage_provider registration_terms smtp"
70)]
71pub struct Config {
72 /// The server_name is the pretty name of this server. It is used as a
73 /// suffix for user and room IDs/aliases.
74 ///
75 /// See the docs for reverse proxying and delegation:
76 /// https://tuwunel.chat/deploying/generic.html#setting-up-the-reverse-proxy
77 ///
78 /// Also see the `[global.well_known]` config section at the very bottom.
79 ///
80 /// Examples of delegation:
81 /// - https://matrix.org/.well-known/matrix/server
82 /// - https://matrix.org/.well-known/matrix/client
83 ///
84 /// YOU NEED TO EDIT THIS. THIS CANNOT BE CHANGED AFTER WITHOUT A DATABASE
85 /// WIPE.
86 ///
87 /// example: "girlboss.ceo"
88 #[cfg_attr(test, serde(default = "default_server_name"))]
89 pub server_name: OwnedServerName,
90
91 /// This is the only directory where tuwunel will save its data, including
92 /// media. Note: this was previously "/var/lib/matrix-conduit".
93 ///
94 /// default: "/var/lib/tuwunel"
95 #[serde(default = "default_database_path")]
96 pub database_path: PathBuf,
97
98 /// Text which will be added to the end of the user's displayname upon
99 /// registration with a space before the text. In Conduit, this was the
100 /// lightning bolt emoji.
101 ///
102 /// To disable, set this to "" (an empty string).
103 ///
104 /// reloadable: yes
105 /// default: "💕"
106 #[serde(default = "default_new_user_displayname_suffix")]
107 pub new_user_displayname_suffix: String,
108
109 #[expect(clippy::doc_link_with_quotes)]
110 /// The default address (IPv4 or IPv6) tuwunel will listen on.
111 ///
112 /// If you are using Docker or a container NAT networking setup, this must
113 /// be "0.0.0.0".
114 ///
115 /// To listen on multiple addresses, specify a vector e.g. ["127.0.0.1",
116 /// "::1"]
117 ///
118 /// default: ["127.0.0.1", "::1"]
119 #[serde(default)]
120 address: Option<ListeningAddr>,
121
122 /// The port(s) tuwunel will listen on.
123 ///
124 /// For reverse proxying, see:
125 /// https://tuwunel.chat/deploying/generic.html#setting-up-the-reverse-proxy
126 ///
127 /// If you are using Docker, don't change this, you'll need to map an
128 /// external port to this.
129 ///
130 /// To listen on multiple ports, specify a vector e.g. [8080, 8448]
131 ///
132 /// default: 8008
133 #[serde(default = "default_port")]
134 port: ListeningPort,
135
136 // external structure; separate section
137 #[serde(default)]
138 pub tls: TlsConfig,
139
140 /// The UNIX socket tuwunel will listen on.
141 ///
142 /// Remember to make sure that your reverse proxy has access to this socket
143 /// file, either by adding your reverse proxy to the 'tuwunel' group or
144 /// granting world R/W permissions with `unix_socket_perms` (666 minimum).
145 ///
146 /// example: "/run/tuwunel/tuwunel.sock"
147 pub unix_socket_path: Option<PathBuf>,
148
149 /// The default permissions (in octal) to create the UNIX socket with.
150 ///
151 /// default: 660
152 #[serde(default = "default_unix_socket_perms")]
153 pub unix_socket_perms: u32,
154
155 /// Error on startup if any config option specified is unknown to Tuwunel.
156 ///
157 /// This is false by default to allow easier deprecation or removal of
158 /// config options in the future without breaking existing deployments. The
159 /// default behaviour is to simply warn on startup.
160 /// reloadable: yes
161 #[serde(default)]
162 pub error_on_unknown_config_opts: bool,
163
164 /// tuwunel supports online database backups using RocksDB's Backup engine
165 /// API. To use this, set a database backup path that tuwunel can write
166 /// to.
167 ///
168 /// For more information, see:
169 /// https://tuwunel.chat/maintenance.html#backups
170 ///
171 /// reloadable: yes
172 /// example: "/opt/tuwunel-db-backups"
173 pub database_backup_path: Option<PathBuf>,
174
175 /// The amount of online RocksDB database backups to keep/retain, if using
176 /// "database_backup_path", before deleting the oldest one.
177 ///
178 /// reloadable: yes
179 /// default: 1
180 #[serde(default = "default_database_backups_to_keep")]
181 pub database_backups_to_keep: i16,
182
183 /// Set this to any float value to multiply tuwunel's in-memory LRU caches
184 /// with such as "auth_chain_cache_capacity".
185 ///
186 /// May be useful if you have significant memory to spare to increase
187 /// performance.
188 ///
189 /// If you have low memory, reducing this may be viable.
190 ///
191 /// By default, the individual caches such as "auth_chain_cache_capacity"
192 /// are scaled by your CPU core count.
193 ///
194 /// default: 1.0
195 #[serde(
196 default = "default_cache_capacity_modifier",
197 alias = "conduit_cache_capacity_modifier"
198 )]
199 pub cache_capacity_modifier: f64,
200
201 /// Set this to any float value in megabytes for tuwunel to tell the
202 /// database engine that this much memory is available for database read
203 /// caches.
204 ///
205 /// May be useful if you have significant memory to spare to increase
206 /// performance.
207 ///
208 /// Similar to the individual LRU caches, this is scaled up with your CPU
209 /// core count.
210 ///
211 /// This defaults to 128.0 + (64.0 * CPU core count).
212 ///
213 /// default: varies by system
214 #[serde(default = "default_db_cache_capacity_mb")]
215 pub db_cache_capacity_mb: f64,
216
217 /// Set this to any float value in megabytes for tuwunel to tell the
218 /// database engine that this much memory is available for database write
219 /// caches.
220 ///
221 /// May be useful if you have significant memory to spare to increase
222 /// performance.
223 ///
224 /// Similar to the individual LRU caches, this is scaled up with your CPU
225 /// core count.
226 ///
227 /// This defaults to 48.0 + (4.0 * CPU core count).
228 ///
229 /// default: varies by system
230 #[serde(default = "default_db_write_buffer_capacity_mb")]
231 pub db_write_buffer_capacity_mb: f64,
232
233 /// default: varies by system
234 #[serde(default = "default_pdu_cache_capacity")]
235 pub pdu_cache_capacity: u32,
236
237 /// default: varies by system
238 #[serde(default = "default_auth_chain_cache_capacity")]
239 pub auth_chain_cache_capacity: u32,
240
241 /// default: varies by system
242 #[serde(default = "default_shorteventid_cache_capacity")]
243 pub shorteventid_cache_capacity: u32,
244
245 /// default: varies by system
246 #[serde(default = "default_eventidshort_cache_capacity")]
247 pub eventidshort_cache_capacity: u32,
248
249 /// default: varies by system
250 #[serde(default = "default_eventid_pdu_cache_capacity")]
251 pub eventid_pdu_cache_capacity: u32,
252
253 /// default: varies by system
254 #[serde(default = "default_shortstatekey_cache_capacity")]
255 pub shortstatekey_cache_capacity: u32,
256
257 /// default: varies by system
258 #[serde(default = "default_statekeyshort_cache_capacity")]
259 pub statekeyshort_cache_capacity: u32,
260
261 /// default: varies by system
262 #[serde(default = "default_servernameevent_data_cache_capacity")]
263 pub servernameevent_data_cache_capacity: u32,
264
265 /// default: varies by system
266 #[serde(default = "default_stateinfo_cache_capacity")]
267 pub stateinfo_cache_capacity: u32,
268
269 /// Minimum time-to-live in seconds for room summary entries in the spaces
270 /// cache.
271 ///
272 /// reloadable: yes
273 /// default: 21600
274 #[serde(default = "default_spacehierarchy_cache_ttl_min")]
275 pub spacehierarchy_cache_ttl_min: u64,
276
277 /// Maximum time-to-live in seconds for room summary entries in the spaces
278 /// cache.
279 ///
280 /// reloadable: yes
281 /// default: 129600
282 #[serde(default = "default_spacehierarchy_cache_ttl_max")]
283 pub spacehierarchy_cache_ttl_max: u64,
284
285 /// Minimum timeout a client can request for long-polling sync. Requests
286 /// will be clamped up to this value if smaller.
287 ///
288 /// reloadable: yes
289 /// default: 5000
290 #[serde(default = "default_client_sync_timeout_min")]
291 pub client_sync_timeout_min: u64,
292
293 /// Default timeout for long-polling sync if a client does not request
294 /// another in their query-string.
295 ///
296 /// reloadable: yes
297 /// default: 30000
298 #[serde(default = "default_client_sync_timeout_default")]
299 pub client_sync_timeout_default: u64,
300
301 /// Maximum timeout a client can request for long-polling sync. Requests
302 /// will be clamped down to this value if larger.
303 ///
304 /// reloadable: yes
305 /// default: 90000
306 #[serde(default = "default_client_sync_timeout_max")]
307 pub client_sync_timeout_max: u64,
308
309 /// Maximum entries stored in DNS memory-cache. The size of an entry may
310 /// vary so please take care if raising this value excessively. Only
311 /// decrease this when using an external DNS cache. Please note that
312 /// systemd-resolved does *not* count as an external cache, even when
313 /// configured to do so.
314 ///
315 /// default: 32768
316 #[serde(default = "default_dns_cache_entries")]
317 pub dns_cache_entries: u32,
318
319 /// Minimum time-to-live in seconds for entries in the DNS cache. The
320 /// default may appear high to most administrators; this is by design as the
321 /// exotic loads of federating to many other servers require a higher TTL
322 /// than many domains have set. Even when using an external DNS cache the
323 /// problem is shifted to that cache which is ignorant of its role for
324 /// this application and can adhere to many low TTL's increasing its load.
325 ///
326 /// default: 10800
327 #[serde(default = "default_dns_min_ttl")]
328 pub dns_min_ttl: u64,
329
330 /// Minimum time-to-live in seconds for NXDOMAIN entries in the DNS cache.
331 /// This value is critical for the server to federate efficiently.
332 /// NXDOMAIN's are assumed to not be returning to the federation and
333 /// aggressively cached rather than constantly rechecked.
334 ///
335 /// Defaults to 3 days as these are *very rarely* false negatives.
336 ///
337 /// default: 259200
338 #[serde(default = "default_dns_min_ttl_nxdomain")]
339 pub dns_min_ttl_nxdomain: u64,
340
341 /// Number of DNS nameserver retries after a timeout or error.
342 ///
343 /// default: 10
344 #[serde(default = "default_dns_attempts")]
345 pub dns_attempts: u16,
346
347 /// The number of seconds to wait for a reply to a DNS query. Please note
348 /// that recursive queries can take up to several seconds for some domains,
349 /// so this value should not be too low, especially on slower hardware or
350 /// resolvers.
351 ///
352 /// default: 10
353 #[serde(default = "default_dns_timeout")]
354 pub dns_timeout: u64,
355
356 /// Fallback to TCP on DNS errors. Set this to false if unsupported by
357 /// nameserver.
358 #[serde(default = "true_fn")]
359 pub dns_tcp_fallback: bool,
360
361 /// Enable to query all nameservers until the domain is found. Referred to
362 /// as "trust_negative_responses" in hickory_resolver. This can avoid
363 /// useless DNS queries if the first nameserver responds with NXDOMAIN or
364 /// an empty NOERROR response.
365 #[serde(default = "true_fn")]
366 pub query_all_nameservers: bool,
367
368 /// Enable using *only* TCP for querying your specified nameservers instead
369 /// of UDP.
370 ///
371 /// If you are running tuwunel in a container environment, this config
372 /// option may need to be enabled. For more details, see:
373 /// https://tuwunel.chat/troubleshooting.html#potential-dns-issues-when-using-docker
374 #[serde(default)]
375 pub query_over_tcp_only: bool,
376
377 /// DNS A/AAAA record lookup strategy
378 ///
379 /// Takes a number of one of the following options:
380 /// 1 - Ipv4Only (Only query for A records, no AAAA/IPv6)
381 ///
382 /// 2 - Ipv6Only (Only query for AAAA records, no A/IPv4)
383 ///
384 /// 3 - Ipv4AndIpv6 (Query for A and AAAA records in parallel, uses whatever
385 /// returns a successful response first)
386 ///
387 /// 4 - Ipv6thenIpv4 (Query for AAAA record, if that fails then query the A
388 /// record)
389 ///
390 /// 5 - Ipv4thenIpv6 (Query for A record, if that fails then query the AAAA
391 /// record)
392 ///
393 /// If you don't have IPv6 networking, then for better DNS performance it
394 /// may be suitable to set this to Ipv4Only (1) as you will never ever use
395 /// the AAAA record contents even if the AAAA record is successful instead
396 /// of the A record.
397 ///
398 /// default: 5
399 #[serde(default = "default_ip_lookup_strategy")]
400 pub ip_lookup_strategy: u8,
401
402 /// List of domain patterns resolved via the alternative path without any
403 /// persistent cache, very small memory cache, and no enforced TTL. This
404 /// is intended for internal network and application services which require
405 /// these specific properties. This path does not support federation or
406 /// general purposes.
407 ///
408 /// reloadable: yes
409 /// example: ["*\.dns\.podman$"]
410 ///
411 /// default: []
412 #[serde(default, with = "serde_regex")]
413 pub dns_passthru_domains: RegexSet,
414
415 /// Whether to resolve appservices via the alternative path; setting this is
416 /// superior to providing domains in `dns_passthru_domains` if all
417 /// appservices intend to be matched anyway. The overhead of matching regex
418 /// and maintaining the list of domains can be avoided.
419 #[serde(default)]
420 pub dns_passthru_appservices: bool,
421
422 /// Enable or disable case randomization for DNS queries. This is a security
423 /// mitigation where answer spoofing is prevented by having to exactly match
424 /// the question. Occasional errors seen in logs which may have lead you
425 /// here tend to be from overloading DNS. Nevertheless for servers which
426 /// are truly incapable this can be set to false.
427 ///
428 /// This currently defaults to false due to user reports regarding some
429 /// popular DNS caches which may or may not be patched soon. It may again
430 /// default to true in an upcoming release.
431 #[serde(default)]
432 pub dns_case_randomization: bool,
433
434 /// Max request size for file uploads. Accepts an integer byte count or a
435 /// string with SI/IEC suffix such as "24 MiB".
436 ///
437 /// default: 24 MiB
438 #[serde(
439 default = "default_max_request_size",
440 deserialize_with = "deserialize_bytesize_usize"
441 )]
442 pub max_request_size: usize,
443
444 /// Maximum size of a response body buffered from a remote server. Applies
445 /// to federation requests, push gateway and appservice transactions, and
446 /// remote media fetched for URL previews. A peer cannot be trusted to honor
447 /// a requested limit, so this bounds the response held in memory
448 /// regardless, guarding against a remote driving the process out of
449 /// memory. Accepts an integer byte count or a string with SI/IEC suffix
450 /// such as "256 MiB".
451 ///
452 /// default: 256 MiB
453 #[serde(
454 default = "default_max_response_size",
455 deserialize_with = "deserialize_bytesize_usize"
456 )]
457 pub max_response_size: usize,
458
459 /// Maximum number of concurrently pending (asynchronous) media uploads a
460 /// user can have.
461 ///
462 /// reloadable: yes
463 /// default: 5
464 #[serde(default = "default_max_pending_media_uploads")]
465 pub max_pending_media_uploads: usize,
466
467 /// The time in seconds before an unused pending MXC URI expires and is
468 /// removed.
469 ///
470 /// reloadable: yes
471 /// default: 86400 (24 hours)
472 #[serde(default = "default_media_create_unused_expiration_time")]
473 pub media_create_unused_expiration_time: u64,
474
475 /// The maximum number of media create requests per second allowed from a
476 /// single user.
477 ///
478 /// reloadable: yes
479 /// default: 10
480 #[serde(default = "default_media_rc_create_per_second")]
481 pub media_rc_create_per_second: u32,
482
483 /// The maximum burst count for media create requests from a single user.
484 ///
485 /// reloadable: yes
486 /// default: 50
487 #[serde(default = "default_media_rc_create_burst_count")]
488 pub media_rc_create_burst_count: u32,
489
490 /// reloadable: yes
491 /// default: 192
492 #[serde(default = "default_max_fetch_prev_events")]
493 pub max_fetch_prev_events: u16,
494
495 /// Maximum time, in milliseconds, to wait for the missing prev_events of an
496 /// incoming timeline event to arrive on their own before fetching them over
497 /// federation. A gap that closes within this window skips the fetch. The
498 /// wait is event-driven and wakes the instant the events arrive, so this is
499 /// a ceiling on added latency, not a fixed cost. Set to 0 to fetch
500 /// immediately.
501 ///
502 /// reloadable: yes
503 /// default: 750
504 #[serde(default = "default_fetch_prev_wait_ms")]
505 pub fetch_prev_wait_ms: u64,
506
507 /// Default/base connection timeout (seconds). This is used only by URL
508 /// previews and update/news endpoint checks.
509 ///
510 /// default: 10
511 #[serde(default = "default_request_conn_timeout")]
512 pub request_conn_timeout: u64,
513
514 /// Default/base request timeout (seconds). The time waiting to receive more
515 /// data from another server. This is used only by URL previews,
516 /// update/news, and misc endpoint checks.
517 ///
518 /// default: 35
519 #[serde(default = "default_request_timeout")]
520 pub request_timeout: u64,
521
522 /// Default/base request total timeout (seconds). The time limit for a whole
523 /// request. This is set very high to not cancel healthy requests while
524 /// serving as a backstop. This is used only by URL previews and update/news
525 /// endpoint checks.
526 ///
527 /// default: 320
528 #[serde(default = "default_request_total_timeout")]
529 pub request_total_timeout: u64,
530
531 /// Default/base idle connection pool timeout (seconds). This is used only
532 /// by URL previews and update/news endpoint checks.
533 ///
534 /// default: 5
535 #[serde(default = "default_request_idle_timeout")]
536 pub request_idle_timeout: u64,
537
538 /// Default/base max idle connections per host. This is used only by URL
539 /// previews and update/news endpoint checks. Defaults to 1 as generally the
540 /// same open connection can be re-used.
541 ///
542 /// default: 1
543 #[serde(default = "default_request_idle_per_host")]
544 pub request_idle_per_host: u16,
545
546 /// Allow the outbound HTTP client to negotiate gzip with other servers:
547 /// advertise it in Accept-Encoding and transparently decompress responses.
548 /// This covers federation, media, and URL preview traffic, and is separate
549 /// from `gzip_compression`, which compresses tuwunel's own responses.
550 ///
551 /// Enabled by default. Set to false to force the client to neither request
552 /// nor decompress gzip. Does nothing unless tuwunel was built with the
553 /// `gzip_compression` feature.
554 ///
555 /// default: true
556 #[serde(default = "true_fn")]
557 pub request_gzip: bool,
558
559 /// Allow the outbound HTTP client to negotiate brotli with other servers:
560 /// advertise it in Accept-Encoding and transparently decompress responses.
561 /// This covers federation, media, and URL preview traffic, and is separate
562 /// from `brotli_compression`, which compresses tuwunel's own responses.
563 ///
564 /// Enabled by default. Set to false to force the client to neither request
565 /// nor decompress brotli. Does nothing unless tuwunel was built with the
566 /// `brotli_compression` feature.
567 ///
568 /// default: true
569 #[serde(default = "true_fn")]
570 pub request_brotli: bool,
571
572 /// Allow the outbound HTTP client to negotiate zstd with other servers:
573 /// advertise it in Accept-Encoding and transparently decompress responses.
574 /// This covers federation, media, and URL preview traffic, and is separate
575 /// from `zstd_compression`, which compresses tuwunel's own responses.
576 ///
577 /// Enabled by default. Set to false to force the client to neither request
578 /// nor decompress zstd. Does nothing unless tuwunel was built with the
579 /// `zstd_compression` feature.
580 ///
581 /// default: true
582 #[serde(default = "true_fn")]
583 pub request_zstd: bool,
584
585 /// Federation well-known resolution connection timeout (seconds).
586 ///
587 /// default: 6
588 #[serde(default = "default_well_known_conn_timeout")]
589 pub well_known_conn_timeout: u64,
590
591 /// Federation HTTP well-known resolution request timeout (seconds).
592 ///
593 /// default: 10
594 #[serde(default = "default_well_known_timeout")]
595 pub well_known_timeout: u64,
596
597 /// Federation client request timeout (seconds). You most definitely want
598 /// this to be high to account for extremely large room joins, slow
599 /// homeservers, your own resources etc.
600 ///
601 /// default: 300
602 #[serde(default = "default_federation_timeout")]
603 pub federation_timeout: u64,
604
605 /// Timeout (seconds) for client-initiated federation key lookups, namely
606 /// /keys/query and /keys/claim against remote servers. Should be well
607 /// below `federation_timeout` so an interactive request to an unresponsive
608 /// server does not outlast the requesting client's own send deadline. A
609 /// lookup that exceeds this bound records a transient federation failure
610 /// for that server, so subsequent lookups back off instead of blocking
611 /// again.
612 ///
613 /// default: 8
614 #[serde(default = "default_federation_keys_timeout")]
615 pub federation_keys_timeout: u64,
616
617 /// Federation client idle connection pool timeout (seconds).
618 ///
619 /// default: 25
620 #[serde(default = "default_federation_idle_timeout")]
621 pub federation_idle_timeout: u64,
622
623 /// Federation client max idle connections per host. Defaults to 1 as
624 /// generally the same open connection can be re-used.
625 ///
626 /// default: 1
627 #[serde(default = "default_federation_idle_per_host")]
628 pub federation_idle_per_host: u16,
629
630 /// Federation sender request timeout (seconds). The time it takes for the
631 /// remote server to process sent transactions can take a while.
632 ///
633 /// default: 180
634 #[serde(default = "default_sender_timeout")]
635 pub sender_timeout: u64,
636
637 /// Federation sender idle connection pool timeout (seconds).
638 ///
639 /// default: 180
640 #[serde(default = "default_sender_idle_timeout")]
641 pub sender_idle_timeout: u64,
642
643 /// Federation sender transaction retry backoff limit (seconds).
644 ///
645 /// reloadable: yes
646 /// default: 86400
647 #[serde(default = "default_sender_retry_backoff_limit")]
648 pub sender_retry_backoff_limit: u64,
649
650 /// Appservice URL request connection timeout. Defaults to 35 seconds as
651 /// generally appservices are hosted within the same network.
652 ///
653 /// default: 35
654 #[serde(default = "default_appservice_timeout")]
655 pub appservice_timeout: u64,
656
657 /// Appservice URL idle connection pool timeout (seconds).
658 ///
659 /// default: 300
660 #[serde(default = "default_appservice_idle_timeout")]
661 pub appservice_idle_timeout: u64,
662
663 /// Notification gateway pusher idle connection pool timeout.
664 ///
665 /// default: 15
666 #[serde(default = "default_pusher_idle_timeout")]
667 pub pusher_idle_timeout: u64,
668
669 /// Maximum time to receive a request from a client (seconds).
670 ///
671 /// default: 75
672 #[serde(default = "default_client_receive_timeout")]
673 pub client_receive_timeout: u64,
674
675 /// Maximum time to process a request received from a client (seconds).
676 ///
677 /// default: 240
678 #[serde(default = "default_client_request_timeout")]
679 pub client_request_timeout: u64,
680
681 /// Maximum time to transmit a response to a client (seconds)
682 ///
683 /// default: 120
684 #[serde(default = "default_client_response_timeout")]
685 pub client_response_timeout: u64,
686
687 /// Grace period for clean shutdown of client requests (seconds).
688 ///
689 /// reloadable: yes
690 /// default: 10
691 #[serde(default = "default_client_shutdown_timeout")]
692 pub client_shutdown_timeout: u64,
693
694 /// Source of the client IP address for rate limiting, logging, and
695 /// security tooling.
696 ///
697 /// When unset (the default), the `ClientIp` extractor scans common
698 /// proxy headers in leftmost-IP mode (`X-Forwarded-For`, RFC 7239
699 /// `Forwarded`, `X-Real-IP`, `Fly-Client-IP`, `True-Client-IP`,
700 /// `CF-Connecting-IP`, `CloudFront-Viewer-Address`) and falls back
701 /// to the TCP peer address; clients can spoof their address via
702 /// request headers in that mode.
703 ///
704 /// When set, `ClientIp` resolves exclusively from the selected
705 /// source. The rightmost value is used for multi-valued headers;
706 /// only the proxy can append to the right, so this is resistant to
707 /// client spoofing.
708 ///
709 /// Supported values:
710 /// - "connect_info" - TCP peer address only (direct connections)
711 /// - "rightmost_x_forwarded_for" - nginx, Caddy
712 /// - "rightmost_forwarded" - RFC 7239 proxies
713 /// - "x_real_ip" - nginx `X-Real-IP`
714 /// - "cf_connecting_ip" - Cloudflare / cloudflared
715 /// - "true_client_ip" - Akamai, Cloudflare Enterprise
716 /// - "fly_client_ip" - Fly.io
717 /// - "cloudfront_viewer_address" - AWS CloudFront
718 ///
719 /// On Unix-socket deployments, leave this unset rather than setting
720 /// "connect_info"; that source requires a TCP peer address.
721 ///
722 /// WARNING: A header-based value without a trusted reverse proxy in
723 /// front of tuwunel allows clients to forge their IP. Changing this
724 /// value requires a server restart.
725 ///
726 /// default: unset
727 /// config-example: "connect_info"
728 #[serde(default)]
729 pub ip_source: Option<IpSource>,
730
731 /// Subnets whose TCP peers are treated as trusted and bypass the
732 /// `ip_source`-based extraction, falling through to the same
733 /// insecure header-scan + `ConnectInfo` fallback used when
734 /// `ip_source` is unset. Each entry is CIDR notation, including
735 /// the prefix length (use `/32` or `/128` to trust a single host).
736 ///
737 /// Loopback (`127.0.0.0/8`, `::1/128`) is always bypassed and
738 /// need not be listed.
739 ///
740 /// Use this when locally attached bridges or other server-side
741 /// clients connect from a private container or VPN subnet that
742 /// cannot carry the configured proxy header (e.g. a user-defined
743 /// Docker bridge network without `network_mode: host`).
744 ///
745 /// NOTE: If you configure an entire subnet here, be sure that it
746 /// does not include the address Tuwunel receives external traffic
747 /// from, i.e. that of your proxy. This would, for example, happen
748 /// if you deployed the proxy in a common bridge network with your
749 /// other components (e.g. in a Compose deployment) and specified
750 /// said network's subnet here. Traffic from the proxy would then
751 /// also have the bypass applied, rendering the `ip_source` option
752 /// effectively useless.
753 ///
754 /// WARNING: Any peer in these subnets can forge the client IP via
755 /// request headers. Only include subnets you control end-to-end.
756 /// Changing this value requires a server restart.
757 ///
758 /// default: []
759 /// config-example: ["172.18.0.0/16", "fd00::/8"]
760 #[expect(
761 clippy::doc_link_with_quotes,
762 reason = "config-example directive emits literal quoted strings, not an intra-doc link"
763 )]
764 #[serde(default)]
765 pub ip_source_trusted_subnets: Vec<IpNet>,
766
767 /// Grace period for clean shutdown of federation requests (seconds).
768 ///
769 /// reloadable: yes
770 /// default: 5
771 #[serde(default = "default_sender_shutdown_timeout")]
772 pub sender_shutdown_timeout: u64,
773
774 /// Enables registration. If set to false, no users can register on this
775 /// server.
776 ///
777 /// If set to true without a token configured, users can register with no
778 /// form of 2nd-step only if you set the following option to true:
779 /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
780 ///
781 /// If you would like registration only via token reg, please configure
782 /// `registration_token` or `registration_token_file`.
783 /// reloadable: yes
784 #[serde(default)]
785 pub allow_registration: bool,
786
787 /// Enabling this setting opens registration to anyone without restrictions.
788 /// This makes your server vulnerable to abuse
789 /// reloadable: yes
790 #[serde(default)]
791 pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
792
793 /// A static registration token that new users will have to provide when
794 /// creating an account. If unset and `allow_registration` is true,
795 /// you must set
796 /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
797 /// to true to allow open registration without any conditions.
798 ///
799 /// YOU NEED TO EDIT THIS OR USE registration_token_file.
800 ///
801 /// reloadable: yes
802 /// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
803 ///
804 /// display: sensitive
805 pub registration_token: Option<String>,
806
807 /// Path to a file on the system that gets read for additional registration
808 /// tokens. Multiple tokens can be added if you separate them with
809 /// whitespace
810 ///
811 /// tuwunel must be able to access the file, and it must not be empty
812 ///
813 /// reloadable: yes
814 /// example: "/etc/tuwunel/.reg_token"
815 pub registration_token_file: Option<PathBuf>,
816
817 /// A pre-shared secret enabling out-of-band account creation via the
818 /// Synapse-style `/_synapse/admin/v1/register` endpoint. The endpoint is
819 /// only available when this is set. Requests authenticate by HMAC-SHA1
820 /// keyed on this value; UIAA is bypassed.
821 ///
822 /// Use a high-entropy value (at least 32 bytes) and treat it as a
823 /// secret of equivalent power to a server admin's access token.
824 ///
825 /// reloadable: yes
826 /// example: "kZ2hN5pQ8wXyL4mR7tBfCgJxV3aD6sE1u"
827 ///
828 /// display: sensitive
829 pub registration_shared_secret: Option<String>,
830
831 /// Path to a file containing the registration shared secret. Trimmed of
832 /// surrounding whitespace on read. Takes precedence over
833 /// `registration_shared_secret` when both are set.
834 ///
835 /// reloadable: yes
836 /// example: "/etc/tuwunel/.reg_shared_secret"
837 pub registration_shared_secret_file: Option<PathBuf>,
838
839 /// Shared secret the Matrix Authentication Service (MAS) authenticates its
840 /// provisioning calls with. When set, the `/_synapse/mas/*` endpoints
841 /// accept only requests bearing this exact secret as their bearer token,
842 /// rejecting all others; when unset, those endpoints reject every request.
843 ///
844 /// Use a high-entropy value and keep it identical to the secret configured
845 /// on the MAS side.
846 ///
847 /// reloadable: yes
848 /// example: "kZ2hN5pQ8wXyL4mR7tBfCgJxV3aD6sE1u"
849 ///
850 /// display: sensitive
851 pub mas_secret: Option<String>,
852
853 /// Controls whether encrypted rooms and events are allowed.
854 /// reloadable: yes
855 #[serde(default = "true_fn")]
856 pub allow_encryption: bool,
857
858 /// Controls whether locally-created rooms should be end-to-end encrypted by
859 /// default. This option is equivalent to the one found in Synapse.
860 ///
861 /// Options:
862 /// - "all": All created rooms are encrypted.
863 /// - "invite": Any room created with `private_chat` or
864 /// `trusted_private_chat` presets.
865 /// - "none": Explicit value for no effect.
866 /// - Other values default to no effect.
867 ///
868 /// reloadable: yes
869 /// default: "none"
870 #[serde(default)]
871 pub encryption_enabled_by_default_for_room_type: Option<String>,
872
873 /// Controls whether federation is allowed or not. It is not recommended to
874 /// disable this after installation due to potential federation breakage but
875 /// this is technically not a permanent setting.
876 #[serde(default = "true_fn")]
877 pub allow_federation: bool,
878
879 /// (EXPERIMENTAL) Resolve the base event of a room context request by
880 /// fetching it from federation when the server never received it.
881 ///
882 /// When a client requests
883 /// `/_matrix/client/v3/rooms/{roomId}/context/{eventId}` for an event
884 /// the server does not hold locally, the server fetches it from a room
885 /// peer and persists it before responding, rather than returning a
886 /// 404. This is gated on `allow_federation`; with federation disabled
887 /// it has no effect. Other on-demand federation fetch sites are gated
888 /// separately.
889 ///
890 /// reloadable: yes
891 /// default: false
892 #[serde(default)]
893 pub fetch_unreceived_contexts_over_federation: bool,
894
895 /// Per-round ceiling on how many servers a federation event fetch contacts
896 /// concurrently. Tightens the built-in fan-out profile of every fetch kind;
897 /// it never widens one. 0 leaves the profiles unchanged.
898 ///
899 /// reloadable: yes
900 /// default: 0
901 #[serde(default)]
902 pub fetch_fanout_max_width: usize,
903
904 /// Ceiling on how many staged rounds a federation event fetch runs before
905 /// giving up. Tightens the built-in round count of every fetch kind; it
906 /// never raises one. 0 leaves the profiles unchanged.
907 ///
908 /// reloadable: yes
909 /// default: 0
910 #[serde(default)]
911 pub fetch_fanout_rounds: usize,
912
913 /// Sets the default `m.federate` property for newly created rooms when the
914 /// client does not request one. If `allow_federation` is set to false at
915 /// the same this value is set to false it then always overrides the client
916 /// requested `m.federate` value to false.
917 ///
918 /// Rooms are fixed to the setting at the time of their creation and can
919 /// never be changed; changing this value only affects new rooms.
920 /// reloadable: yes
921 #[serde(default = "true_fn")]
922 pub federate_created_rooms: bool,
923
924 /// Allows federation requests to be made to itself
925 ///
926 /// This isn't intended and is very likely a bug if federation requests are
927 /// being sent to yourself. This currently mainly exists for development
928 /// purposes.
929 /// reloadable: yes
930 #[serde(default)]
931 pub federation_loopback: bool,
932
933 /// Always calls /forget on behalf of the user if leaving a room. This is a
934 /// part of MSC4267 "Automatically forgetting rooms on leave"
935 /// reloadable: yes
936 #[serde(default)]
937 pub forget_forced_upon_leave: bool,
938
939 /// Set this to true to require authentication on the normally
940 /// unauthenticated profile retrieval endpoints (GET)
941 /// "/_matrix/client/v3/profile/{userId}".
942 ///
943 /// This can prevent profile scraping.
944 /// reloadable: yes
945 #[serde(default)]
946 pub require_auth_for_profile_requests: bool,
947
948 /// Preserve per-room profile overrides during a global profile update.
949 ///
950 /// When `true` (default), a profile change (displayname or avatar_url)
951 /// arriving via the profile endpoints skips rooms whose current
952 /// `m.room.member` already differs from the user's prior global
953 /// profile. This is the natural behavior users expect after setting a
954 /// per-room nickname or avatar with a client's `/myroomnick`-style
955 /// command: a subsequent global change does not clobber the override.
956 ///
957 /// Set to `false` to always rewrite every joined room's member event
958 /// to match the new global profile. That matches the literal spec
959 /// reading.
960 ///
961 /// MSC4466 lets clients pick this per request via the
962 /// `org.matrix.msc4466.propagate_to` query parameter
963 /// (`all` / `unchanged` / `none`); an explicit value overrides this
964 /// default in either direction.
965 ///
966 /// reloadable: yes
967 /// default: true
968 #[serde(default = "true_fn")]
969 pub preserve_room_profile_overrides: bool,
970
971 /// Set this to true to allow your server's public room directory to be
972 /// federated. Set this to false to protect against /publicRooms spiders,
973 /// but will forbid external users from viewing your server's public room
974 /// directory. If federation is disabled entirely (`allow_federation`), this
975 /// is inherently false.
976 /// reloadable: yes
977 #[serde(default)]
978 pub allow_public_room_directory_over_federation: bool,
979
980 /// Set this to true to allow your server's public room directory to be
981 /// queried without client authentication (access token) through the Client
982 /// APIs. Set this to false to protect against /publicRooms spiders.
983 /// reloadable: yes
984 #[serde(default)]
985 pub allow_public_room_directory_without_auth: bool,
986
987 /// Allows room directory searches to match on partial room_id's when the
988 /// search term starts with '!'.
989 ///
990 /// reloadable: yes
991 /// default: true
992 #[serde(default = "true_fn")]
993 pub allow_public_room_search_by_id: bool,
994
995 /// Set this to false to limit results of rooms when searching by ID to
996 /// those that would be found by an alias or other query; specifically
997 /// those listed in the public rooms directory. By default this is set to
998 /// true allowing any joinable room to match. This satisfies the Principle
999 /// of Least Expectation when pasting a room_id into a search box with
1000 /// intent to join; many rooms simply opt-out of public listings. Therefor
1001 /// to prevent this feature from abuse, knowledge of several characters of
1002 /// the room_id is required before any results are returned.
1003 ///
1004 /// reloadable: yes
1005 /// default: true
1006 #[serde(default = "true_fn")]
1007 pub allow_unlisted_room_search_by_id: bool,
1008
1009 /// Show all local users in user directory. With this set to false, only
1010 /// users in public rooms or those that share a room with the user making
1011 /// the search will be shown.
1012 ///
1013 /// reloadable: yes
1014 /// default: false
1015 #[serde(default)]
1016 pub show_all_local_users_in_user_directory: bool,
1017
1018 /// Allow guest users to access TURN credentials.
1019 ///
1020 /// This is the equivalent of Synapse's `turn_allow_guests` config option.
1021 /// Setting this to true allows guest users to call the endpoint
1022 /// `/_matrix/client/v3/voip/turnServer`.
1023 /// reloadable: yes
1024 #[serde(default)]
1025 pub turn_allow_guests: bool,
1026
1027 /// Set this to true to lock down your server's public room directory and
1028 /// only allow admins to publish rooms to the room directory. Unpublishing
1029 /// is still allowed by all users with this enabled.
1030 /// reloadable: yes
1031 #[serde(default)]
1032 pub lockdown_public_room_directory: bool,
1033
1034 /// Set this to true to allow federating device display names / allow
1035 /// external users to see your device display name. If federation is
1036 /// disabled entirely (`allow_federation`), this is inherently false. For
1037 /// privacy reasons, this is best left disabled.
1038 /// reloadable: yes
1039 #[serde(default)]
1040 pub allow_device_name_federation: bool,
1041
1042 /// Config option to allow or disallow incoming federation requests that
1043 /// obtain the profiles of our local users from
1044 /// `/_matrix/federation/v1/query/profile`
1045 ///
1046 /// Increases privacy of your local user's such as display names, but some
1047 /// remote users may get a false "this user does not exist" error when they
1048 /// try to invite you to a DM or room. Also can protect against profile
1049 /// spiders.
1050 ///
1051 /// This is inherently false if `allow_federation` is disabled
1052 /// reloadable: yes
1053 #[serde(
1054 default = "true_fn",
1055 alias = "allow_profile_lookup_federation_requests"
1056 )]
1057 pub allow_inbound_profile_lookup_federation_requests: bool,
1058
1059 /// Allow standard users to create rooms. Appservices and admins are always
1060 /// allowed to create rooms
1061 /// reloadable: yes
1062 #[serde(default = "true_fn")]
1063 pub allow_room_creation: bool,
1064
1065 /// Set to false to disable users from joining or creating room versions
1066 /// that aren't officially supported by tuwunel. Unstable room versions may
1067 /// have flawed specifications or our implementation may be non-conforming.
1068 /// Correct operation may not be guaranteed, but incorrect operation may be
1069 /// tolerable and unnoticed.
1070 ///
1071 /// tuwunel officially supports room versions 6+. tuwunel has slightly
1072 /// experimental (though works fine in practice) support for versions 3 - 5.
1073 ///
1074 /// reloadable: yes
1075 /// default: true
1076 #[serde(default = "true_fn")]
1077 pub allow_unstable_room_versions: bool,
1078
1079 /// Set to true to enable experimental room versions.
1080 ///
1081 /// Unlike unstable room versions these versions are either under
1082 /// development, protype spec-changes, or somehow present a serious risk to
1083 /// the server's operation or database corruption. This is for developer use
1084 /// only.
1085 /// reloadable: yes
1086 #[serde(default)]
1087 pub allow_experimental_room_versions: bool,
1088
1089 /// MSC4284: ask the room's policy server to sign outgoing events. When a
1090 /// room has a valid `m.room.policy` state event, the homeserver requests a
1091 /// signature from that policy server's federation `/sign` endpoint before
1092 /// federating each event. Refusal aborts the local request; network or
1093 /// timeout failures fail open with a warn log so a transient policy-server
1094 /// outage does not silently take the room offline.
1095 ///
1096 /// reloadable: yes
1097 /// default: false
1098 #[serde(default)]
1099 pub enable_policy_servers: bool,
1100
1101 /// MSC4284: timeout (seconds) for requests to a room's policy server.
1102 /// Applies to both outbound `/sign` calls and inbound signature-fetches.
1103 ///
1104 /// reloadable: yes
1105 /// default: 5
1106 #[serde(default = "default_policy_server_request_timeout")]
1107 pub policy_server_request_timeout: u64,
1108
1109 /// MSC3925: fold the most recent message edit (an `m.replace` relation)
1110 /// into `unsigned.m.relations` on a served event as the full replacement
1111 /// event, on the client read endpoints. Off by default: it adds a typed
1112 /// index seek per served event and a server-authoritative edit summary that
1113 /// most clients reconstruct locally anyway, so it is opt-in.
1114 ///
1115 /// reloadable: yes
1116 /// default: false
1117 #[serde(default)]
1118 pub bundle_edit_relations: bool,
1119
1120 /// MSC2675/MSC3267: fold reference relations (`m.reference`) into
1121 /// `unsigned.m.relations` on a served event as `{ chunk: [{ event_id },
1122 /// ...] }`, on the client read endpoints. Off by default: no surveyed
1123 /// client renders reference bundles (references are plumbing for polls,
1124 /// beacons, and verification, which clients resolve directly), so most
1125 /// deployments gain nothing from the added read-time cost.
1126 ///
1127 /// reloadable: yes
1128 /// default: false
1129 #[serde(default)]
1130 pub bundle_reference_relations: bool,
1131
1132 /// Default room version tuwunel will create rooms with.
1133 ///
1134 /// The default is prescribed by the spec, but may be selected by developer
1135 /// recommendation. To prevent stale documentation we no longer list it
1136 /// here. It is only advised to override this if you know what you are
1137 /// doing, and by doing so, updates with new versions are precluded.
1138 /// reloadable: yes
1139 #[serde(default = "default_default_room_version")]
1140 pub default_room_version: RoomVersionId,
1141
1142 // external structure; separate section
1143 #[serde(default)]
1144 pub well_known: WellKnownConfig,
1145
1146 #[serde(default)]
1147 pub allow_jaeger: bool,
1148
1149 /// default: "info"
1150 #[serde(default = "default_jaeger_filter")]
1151 pub jaeger_filter: String,
1152
1153 /// If the 'perf_measurements' compile-time feature is enabled, enables
1154 /// collecting folded stack trace profile of tracing spans using
1155 /// tracing_flame. The resulting profile can be visualized with inferno[1],
1156 /// speedscope[2], or a number of other tools.
1157 ///
1158 /// [1]: https://github.com/jonhoo/inferno
1159 /// [2]: www.speedscope.app
1160 #[serde(default)]
1161 pub tracing_flame: bool,
1162
1163 /// default: "info"
1164 #[serde(default = "default_tracing_flame_filter")]
1165 pub tracing_flame_filter: String,
1166
1167 /// default: "./tracing.folded"
1168 #[serde(default = "default_tracing_flame_output_path")]
1169 pub tracing_flame_output_path: String,
1170
1171 #[cfg(not(doctest))]
1172 /// Examples:
1173 ///
1174 /// - No proxy (default):
1175 ///
1176 /// proxy = "none"
1177 ///
1178 /// - For global proxy, create the section at the bottom of this file:
1179 ///
1180 /// [global.proxy]
1181 /// global = { url = "socks5h://localhost:9050" }
1182 ///
1183 /// - To proxy some domains:
1184 ///
1185 /// [global.proxy]
1186 /// [[global.proxy.by_domain]]
1187 /// url = "socks5h://localhost:9050"
1188 /// include = ["*.onion", "matrix.myspecial.onion"]
1189 /// exclude = ["*.myspecial.onion"]
1190 ///
1191 /// Include vs. Exclude:
1192 ///
1193 /// - If include is an empty list, it is assumed to be `["*"]`.
1194 ///
1195 /// - If a domain matches both the exclude and include list, the proxy will
1196 /// only be used if it was included because of a more specific rule than
1197 /// it was excluded. In the above example, the proxy would be used for
1198 /// `ordinary.onion`, `matrix.myspecial.onion`, but not
1199 /// `hello.myspecial.onion`.
1200 ///
1201 /// default: "none"
1202 #[serde(default)]
1203 pub proxy: ProxyConfig,
1204
1205 #[expect(clippy::doc_link_with_quotes)]
1206 /// Servers listed here will be used to gather public keys of other servers
1207 /// (notary trusted key servers).
1208 ///
1209 /// Currently, tuwunel doesn't support inbound batched key requests, so
1210 /// this list should only contain other Synapse servers.
1211 ///
1212 /// reloadable: yes
1213 /// example: ["matrix.org", "tchncs.de"]
1214 ///
1215 /// default: ["matrix.org"]
1216 #[serde(default = "default_trusted_servers")]
1217 pub trusted_servers: Vec<OwnedServerName>,
1218
1219 /// Whether to query the servers listed in trusted_servers first or query
1220 /// the origin server first. For best security, querying the origin server
1221 /// first is advised to minimize the exposure to a compromised trusted
1222 /// server. For maximum federation/join performance this can be set to true,
1223 /// however other options exist to query trusted servers first under
1224 /// specific high-load circumstances and should be evaluated before setting
1225 /// this to true.
1226 /// reloadable: yes
1227 #[serde(default)]
1228 pub query_trusted_key_servers_first: bool,
1229
1230 /// Whether to query the servers listed in trusted_servers first
1231 /// specifically on room joins. This option limits the exposure to a
1232 /// compromised trusted server to room joins only. The join operation
1233 /// requires gathering keys from many origin servers which can cause
1234 /// significant delays. Therefor this defaults to true to mitigate
1235 /// unexpected delays out-of-the-box. The security-paranoid or those willing
1236 /// to tolerate delays are advised to set this to false. Note that setting
1237 /// query_trusted_key_servers_first to true causes this option to be
1238 /// ignored.
1239 /// reloadable: yes
1240 #[serde(default = "true_fn")]
1241 pub query_trusted_key_servers_first_on_join: bool,
1242
1243 /// Only query trusted servers for keys and never the origin server. This is
1244 /// intended for clusters or custom deployments using their trusted_servers
1245 /// as forwarding-agents to cache and deduplicate requests. Notary servers
1246 /// do not act as forwarding-agents by default, therefor do not enable this
1247 /// unless you know exactly what you are doing.
1248 /// reloadable: yes
1249 #[serde(default)]
1250 pub only_query_trusted_key_servers: bool,
1251
1252 /// Maximum number of keys to request in each trusted server batch query.
1253 ///
1254 /// reloadable: yes
1255 /// default: 192
1256 #[serde(default = "default_trusted_server_batch_size")]
1257 pub trusted_server_batch_size: usize,
1258
1259 /// Maximum number of request batches in flight simultaneously when querying
1260 /// a trusted server.
1261 ///
1262 /// reloadable: yes
1263 /// default: 2
1264 #[serde(default = "default_trusted_server_batch_concurrency")]
1265 pub trusted_server_batch_concurrency: usize,
1266
1267 /// Max log level for tuwunel. Allows debug, info, warn, or error.
1268 ///
1269 /// See also:
1270 /// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
1271 ///
1272 /// **Caveat**:
1273 /// For release builds, the tracing crate is configured to only implement
1274 /// levels higher than error to avoid unnecessary overhead in the compiled
1275 /// binary from trace macros. For debug builds, this restriction is not
1276 /// applied.
1277 ///
1278 /// default: "info"
1279 #[serde(default = "default_log")]
1280 pub log: String,
1281
1282 /// Output logs with ANSI colours.
1283 #[serde(default = "true_fn", alias = "log_colours")]
1284 pub log_colors: bool,
1285
1286 /// Sets the log format to compact mode.
1287 #[serde(default)]
1288 pub log_compact: bool,
1289
1290 /// Configures the span events which will be outputted with the log.
1291 ///
1292 /// default: "none"
1293 #[serde(default = "default_log_span_events")]
1294 pub log_span_events: String,
1295
1296 /// Configures whether TUWUNEL_LOG EnvFilter matches values using regular
1297 /// expressions. See the tracing_subscriber documentation on Directives.
1298 ///
1299 /// default: true
1300 #[serde(default = "true_fn")]
1301 pub log_filter_regex: bool,
1302
1303 /// Toggles the display of ThreadId in tracing log output.
1304 ///
1305 /// default: false
1306 #[serde(default)]
1307 pub log_thread_ids: bool,
1308
1309 /// Redirects logging to standard error (stderr). The default is false for
1310 /// stdout. For those using our systemd features the redirection to stderr
1311 /// occurs as necessary and setting this option should not be required. We
1312 /// offer this option for all other users who desire such redirection.
1313 ///
1314 /// default: false
1315 #[serde(default)]
1316 pub log_to_stderr: bool,
1317
1318 /// Setting to false disables the logging/tracing system at a lower level.
1319 /// In contrast to configuring an empty `log` string where the system is
1320 /// still operating but muted, when this option is false the system was not
1321 /// initialized and is not operating. Changing this option has no effect
1322 /// after startup. This option is intended for developers and expert use
1323 /// only: configuring an empty log string is preferred over using this.
1324 ///
1325 /// default: true
1326 #[serde(default = "true_fn")]
1327 pub log_enable: bool,
1328
1329 /// Setting to false disables the logging/tracing system at a lower level
1330 /// similar to `log_enable`. In this case the system is configured normally,
1331 /// but not registered as the global handler in the final steps. This option
1332 /// is for developers and expert use only.
1333 ///
1334 /// default: true
1335 #[serde(default = "true_fn")]
1336 pub log_global_default: bool,
1337
1338 /// OpenID token expiration/TTL in seconds.
1339 ///
1340 /// These are the OpenID tokens that are primarily used for Matrix account
1341 /// integrations (e.g. Vector Integrations in Element), *not* OIDC/OpenID
1342 /// Connect/etc.
1343 ///
1344 /// reloadable: yes
1345 /// default: 3600
1346 #[serde(default = "default_openid_token_ttl")]
1347 pub openid_token_ttl: u64,
1348
1349 /// Allow an existing session to mint a login token for another client.
1350 /// This requires interactive authentication, but has security ramifications
1351 /// as a malicious client could use the mechanism to spawn more than one
1352 /// session. Enabled by default.
1353 ///
1354 /// reloadable: yes
1355 /// default: true
1356 #[serde(default = "true_fn")]
1357 pub login_via_existing_session: bool,
1358
1359 /// Whether to enable the login token route to accept login tokens at all.
1360 /// Login tokens may be generated by the server for authorization flows such
1361 /// as SSO; disabling tokens may break such features.
1362 ///
1363 /// This option is distinct from `login_via_existing_session` and does not
1364 /// carry the same security implications; the intent is to leave this
1365 /// enabled while disabling the former to prevent clients from commanding
1366 /// login token creation but without preventing the server from doing so.
1367 ///
1368 /// reloadable: yes
1369 /// default: true
1370 #[serde(default = "true_fn")]
1371 pub login_via_token: bool,
1372
1373 /// Whether to enable login using traditional user/password authorization
1374 /// flow.
1375 ///
1376 /// Set this option to false if you intend to allow logging in only using
1377 /// other mechanisms, such as SSO.
1378 ///
1379 /// reloadable: yes
1380 /// default: true
1381 #[serde(default = "true_fn")]
1382 pub login_with_password: bool,
1383
1384 /// Login token expiration/TTL in milliseconds.
1385 ///
1386 /// These are short-lived tokens for the m.login.token endpoint.
1387 /// This is used to allow existing sessions to create new sessions.
1388 /// see login_via_existing_session.
1389 ///
1390 /// reloadable: yes
1391 /// default: 120000
1392 #[serde(default = "default_login_token_ttl")]
1393 pub login_token_ttl: u64,
1394
1395 /// Access token TTL in seconds.
1396 ///
1397 /// For clients that support refresh-tokens, the access-token provided on
1398 /// login will be invalidated after this amount of time and the client will
1399 /// be soft-logged-out until refreshing it.
1400 ///
1401 /// reloadable: yes
1402 /// default: 604800
1403 #[serde(default = "default_access_token_ttl")]
1404 pub access_token_ttl: u64,
1405
1406 /// Refresh token TTL in seconds.
1407 ///
1408 /// Refresh tokens are rejected once this lifetime elapses. Whether the
1409 /// deadline slides forward on each use or stays fixed at issuance is
1410 /// controlled by `refresh_token_idle_only`. The default of `0` disables
1411 /// refresh-token expiry entirely; a typical enabled value is `259200`
1412 /// (three days).
1413 ///
1414 /// reloadable: yes
1415 /// default: 0
1416 #[serde(default)]
1417 pub refresh_token_ttl: u64,
1418
1419 /// Whether `refresh_token_ttl` acts as an idle timeout or an absolute
1420 /// session lifetime.
1421 ///
1422 /// When `true` (default), each successful refresh resets the deadline to
1423 /// `now + refresh_token_ttl`. A session in continuous use never expires.
1424 /// When `false`, the deadline is fixed at first issuance and rotation
1425 /// carries it forward, forcing re-auth after `refresh_token_ttl`
1426 /// regardless of activity.
1427 ///
1428 /// reloadable: yes
1429 /// default: true
1430 #[serde(default = "true_fn")]
1431 pub refresh_token_idle_only: bool,
1432
1433 /// Whether refresh-token expiry triggers a hard logout instead of a soft
1434 /// one.
1435 ///
1436 /// When `false` (default), an expired refresh token is rejected with
1437 /// `M_UNKNOWN_TOKEN` carrying `soft_logout: true`. The client can preserve
1438 /// E2EE keys and local state, then re-authenticate to resume the same
1439 /// device.
1440 ///
1441 /// When `true`, the device is removed entirely on expiry: the access
1442 /// token is invalidated, the device record is deleted, and the client is
1443 /// signalled with `soft_logout: false`. The next session is a brand-new
1444 /// device, so the client cannot recover E2EE history from local state
1445 /// alone; this is the CWE-613 stance and trades usability for that
1446 /// guarantee.
1447 ///
1448 /// reloadable: yes
1449 /// default: false
1450 #[serde(default)]
1451 pub refresh_token_hard_logout: bool,
1452
1453 /// Grace window in seconds for a benign refresh-token double-submit.
1454 ///
1455 /// After a refresh token rotates, the spent token is retained for one
1456 /// generation so a later reuse is detectable. If that spent token is
1457 /// presented again within this window while its successor is still the
1458 /// device's current refresh token, the request is treated as a client that
1459 /// lost the rotated response: a fresh access token is issued for the
1460 /// unchanged refresh token rather than revoking the device. Outside the
1461 /// window, or once the chain has advanced, a replayed refresh token revokes
1462 /// the device as a suspected compromise. Set to `0` to treat every reuse as
1463 /// a compromise.
1464 ///
1465 /// reloadable: yes
1466 /// default: 15
1467 #[serde(default = "default_refresh_token_reuse_grace")]
1468 pub refresh_token_reuse_grace: u64,
1469
1470 /// Whether a detected refresh-token reuse revokes the device.
1471 ///
1472 /// When true (default), presenting a refresh token that was already rotated
1473 /// (outside the `refresh_token_reuse_grace` window) removes the device, the
1474 /// RFC 6819 stance that treats reuse as a compromised session. When false,
1475 /// the replayed request is rejected but the device is left intact, the
1476 /// laxer behaviour an operator fronting another OAuth client may prefer.
1477 ///
1478 /// reloadable: yes
1479 /// default: true
1480 #[serde(default = "true_fn")]
1481 pub refresh_token_reuse_revoke: bool,
1482
1483 /// Enable native registration and login on the built-in OIDC provider
1484 /// (next-gen auth), authenticating Matrix clients against this server's own
1485 /// accounts without a third-party `identity_provider`.
1486 ///
1487 /// When false (default), the OIDC server runs only to broker for a
1488 /// configured `identity_provider`, redirecting users to that upstream IdP.
1489 /// When true, an authorization request that selects no provider is served a
1490 /// native login or registration page checked against local accounts;
1491 /// `well_known.client` must be set. Native and external providers coexist;
1492 /// a configured `identity_provider` still brokers as before. Registration
1493 /// here honors `allow_registration`, the registration token, and
1494 /// `registration_terms` exactly as the client registration endpoint does.
1495 ///
1496 /// reloadable: yes
1497 /// default: false
1498 #[serde(default)]
1499 pub oidc_native_auth: bool,
1500
1501 /// Require OIDC clients (next-gen auth) to request an MSC2967 device scope.
1502 ///
1503 /// When false, a client that omits the `urn:matrix:client:device:<id>`
1504 /// scope is assigned a server-generated device id, which is echoed back in
1505 /// the granted scope. When true, the authorization-code grant is rejected
1506 /// unless the client supplies a device scope, per the MSC2967 expectation
1507 /// that the client owns its device id.
1508 ///
1509 /// reloadable: yes
1510 /// default: false
1511 #[serde(default)]
1512 pub oidc_require_device_scope: bool,
1513
1514 /// Require PKCE (RFC 7636) with the S256 method on the OIDC
1515 /// authorization-code grant.
1516 ///
1517 /// When true, the authorize endpoint rejects a request that carries no
1518 /// `code_challenge`, as MSC2964 mandates for public clients. A present
1519 /// challenge must always use S256; the `plain` method is rejected
1520 /// regardless of this setting. Set to false only as a transition escape
1521 /// hatch for a legacy client that cannot send a challenge.
1522 ///
1523 /// reloadable: yes
1524 /// default: true
1525 #[serde(default = "true_fn")]
1526 pub oidc_require_pkce: bool,
1527
1528 /// Reject an OIDC authorization-code grant that requests a scope this
1529 /// server does not recognise, instead of narrowing the granted scope down
1530 /// to the recognised tokens.
1531 ///
1532 /// When false (default), an unrecognised scope token is dropped and the
1533 /// narrowed `scope` is echoed back to the client per RFC 6749. When true,
1534 /// an unrecognised scope is rejected. `openid` and the MSC2967 device and
1535 /// api scopes (both spellings) are always recognised.
1536 ///
1537 /// reloadable: yes
1538 /// default: false
1539 #[serde(default)]
1540 pub oidc_strict_scope: bool,
1541
1542 /// Initial access token required to register an OIDC client dynamically
1543 /// (RFC 7591).
1544 ///
1545 /// When set, the registration endpoint requires the caller to present this
1546 /// token as an `Authorization: Bearer` credential. The default (empty)
1547 /// leaves dynamic client registration open.
1548 ///
1549 /// reloadable: yes
1550 /// default:
1551 #[serde(default)]
1552 pub oidc_registration_access_token: String,
1553
1554 /// Allowlist of hostnames permitted in a dynamically-registered OIDC
1555 /// client's redirect_uris.
1556 ///
1557 /// When non-empty, every redirect_uri presented at registration must have a
1558 /// host in this list or the registration is rejected. The default (empty)
1559 /// imposes no host restriction.
1560 ///
1561 /// reloadable: yes
1562 /// default: []
1563 #[serde(default)]
1564 pub oidc_registration_allowed_redirect_hosts: Vec<String>,
1565
1566 /// Require a `client_uri` in dynamic client registration requests
1567 /// (RFC 7591 / MSC2966).
1568 ///
1569 /// When false (default), `client_uri` is optional; a client that supplies
1570 /// one still has it validated (https, host, no userinfo) and the other URLs
1571 /// in the request must share its host or a subdomain. When true, a
1572 /// registration without an https `client_uri` is rejected with
1573 /// `invalid_client_metadata`, enforcing the MSC2966 common-base model on
1574 /// every client.
1575 ///
1576 /// reloadable: yes
1577 /// default: false
1578 #[serde(default)]
1579 pub oidc_registration_require_client_uri: bool,
1580
1581 /// Token-bucket refill rate (requests per second) for the OIDC endpoints.
1582 ///
1583 /// Applies a shared per-client-IP throttle across the authorize, token,
1584 /// dynamic-registration and device-grant endpoints. The default of `0`
1585 /// disables the throttle, preserving open
1586 /// access; raise it together with `oidc_rc_burst_count` to protect a server
1587 /// exposed to a hostile network. The key is the client IP, so a rate low
1588 /// enough to bite a brute-force attempt can also throttle many users behind
1589 /// one NAT; size the burst accordingly.
1590 ///
1591 /// reloadable: yes
1592 /// default: 0
1593 #[serde(default)]
1594 pub oidc_rc_per_second: u32,
1595
1596 /// Token-bucket depth (burst size) for the OIDC endpoint throttle.
1597 ///
1598 /// The number of requests a single client IP may make in a burst before the
1599 /// `oidc_rc_per_second` refill rate governs. Ignored while
1600 /// `oidc_rc_per_second` is `0`.
1601 ///
1602 /// reloadable: yes
1603 /// default: 0
1604 #[serde(default)]
1605 pub oidc_rc_burst_count: u32,
1606
1607 /// Static TURN username to provide the client if not using a shared secret
1608 /// ("turn_secret"), It is recommended to use a shared secret over static
1609 /// credentials.
1610 /// reloadable: yes
1611 #[serde(default)]
1612 pub turn_username: String,
1613
1614 /// Static TURN password to provide the client if not using a shared secret
1615 /// ("turn_secret"). It is recommended to use a shared secret over static
1616 /// credentials.
1617 ///
1618 /// display: sensitive
1619 /// reloadable: yes
1620 #[serde(default)]
1621 pub turn_password: String,
1622
1623 #[expect(clippy::doc_link_with_quotes)]
1624 /// Vector list of TURN URIs/servers to use.
1625 ///
1626 /// Replace "example.turn.uri" with your TURN domain, such as the coturn
1627 /// "realm" config option. If using TURN over TLS, replace the URI prefix
1628 /// "turn:" with "turns:".
1629 ///
1630 /// reloadable: yes
1631 /// example: ["turn:example.turn.uri?transport=udp",
1632 /// "turn:example.turn.uri?transport=tcp"]
1633 ///
1634 /// default: []
1635 #[serde(default)]
1636 pub turn_uris: Vec<String>,
1637
1638 /// TURN secret to use for generating the HMAC-SHA1 hash apart of username
1639 /// and password generation.
1640 ///
1641 /// This is more secure, but if needed you can use traditional static
1642 /// username/password credentials.
1643 ///
1644 /// display: sensitive
1645 #[serde(default)]
1646 pub turn_secret: Option<String>,
1647
1648 /// TURN secret to use that's read from the file path specified.
1649 ///
1650 /// This takes priority over "turn_secret" first, and falls back to
1651 /// "turn_secret" if invalid or failed to open.
1652 ///
1653 /// example: "/etc/tuwunel/.turn_secret"
1654 pub turn_secret_file: Option<PathBuf>,
1655
1656 /// TURN TTL, in seconds.
1657 ///
1658 /// reloadable: yes
1659 /// default: 86400
1660 #[serde(default = "default_turn_ttl")]
1661 pub turn_ttl: u64,
1662
1663 #[expect(clippy::doc_link_with_quotes)]
1664 /// List/vector of room IDs or room aliases that tuwunel will make newly
1665 /// registered users join. The rooms specified must be rooms that you have
1666 /// joined at least once on the server, and must be public.
1667 ///
1668 /// reloadable: yes
1669 /// example: ["#tuwunel:grin.hu",
1670 /// "!l2xV0sd51lraysuRcsWVECge4NULaH3g-ou95vgDgiM"]
1671 ///
1672 /// default: []
1673 #[serde(default = "Vec::new")]
1674 pub auto_join_rooms: Vec<OwnedRoomOrAliasId>,
1675
1676 /// Config option to automatically deactivate the account of any user who
1677 /// attempts to join a:
1678 /// - banned room
1679 /// - forbidden room alias
1680 /// - room alias or ID with a forbidden server name
1681 ///
1682 /// This may be useful if all your banned lists consist of toxic rooms or
1683 /// servers that no good faith user would ever attempt to join, and
1684 /// to automatically remediate the problem without any admin user
1685 /// intervention.
1686 ///
1687 /// This will also make the user leave all rooms. Federation (e.g. remote
1688 /// room invites) are ignored here.
1689 ///
1690 /// Defaults to false as rooms can be banned for non-moderation-related
1691 /// reasons and this performs a full user deactivation.
1692 /// reloadable: yes
1693 #[serde(default)]
1694 pub auto_deactivate_banned_room_attempts: bool,
1695
1696 /// RocksDB log level. This is not the same as tuwunel's log level. This
1697 /// is the log level for the RocksDB engine/library which show up in your
1698 /// database folder/path as `LOG` files. tuwunel will log RocksDB errors
1699 /// as normal through tracing or panics if severe for safety.
1700 ///
1701 /// default: "error"
1702 #[serde(default = "default_rocksdb_log_level")]
1703 pub rocksdb_log_level: String,
1704
1705 #[serde(default)]
1706 pub rocksdb_log_stderr: bool,
1707
1708 /// Max RocksDB `LOG` file size before rotating. Accepts an integer byte
1709 /// count or a string with SI/IEC suffix such as "4 MiB".
1710 ///
1711 /// default: 4194304
1712 #[serde(
1713 default = "default_rocksdb_max_log_file_size",
1714 deserialize_with = "deserialize_bytesize_usize"
1715 )]
1716 pub rocksdb_max_log_file_size: usize,
1717
1718 /// Time in seconds before RocksDB will forcibly rotate logs.
1719 ///
1720 /// default: 0
1721 #[serde(default = "default_rocksdb_log_time_to_roll")]
1722 pub rocksdb_log_time_to_roll: usize,
1723
1724 /// Use RocksDB tunings tailored to spinning disks (HDDs). On NVMe or SSD
1725 /// storage, leave this disabled.
1726 ///
1727 /// When enabled, RocksDB skips compaction readahead and parallel file-open
1728 /// threads at startup. This option does not affect Direct IO; for that, see
1729 /// `rocksdb_direct_io`.
1730 #[serde(default)]
1731 pub rocksdb_optimize_for_spinning_disks: bool,
1732
1733 /// Enables direct-io to increase database performance via unbuffered I/O.
1734 ///
1735 /// For more details about direct I/O and RockDB, see:
1736 /// https://github.com/facebook/rocksdb/wiki/Direct-IO
1737 ///
1738 /// Set this option to false if the database resides on a filesystem which
1739 /// does not support direct-io like FUSE, or any form of complex filesystem
1740 /// setup such as possibly ZFS.
1741 #[serde(default = "true_fn")]
1742 pub rocksdb_direct_io: bool,
1743
1744 /// Amount of threads that RocksDB will use for parallelism on database
1745 /// operations such as cleanup, sync, flush, compaction, etc. Set to 0 to
1746 /// use all your logical threads. Defaults to your CPU logical thread count.
1747 ///
1748 /// default: varies by system
1749 #[serde(default = "default_rocksdb_parallelism_threads")]
1750 pub rocksdb_parallelism_threads: usize,
1751
1752 /// Maximum number of LOG files RocksDB will keep. This must *not* be set to
1753 /// 0. It must be at least 1. Defaults to 3 as these are not very useful
1754 /// unless troubleshooting/debugging a RocksDB bug.
1755 ///
1756 /// default: 3
1757 #[serde(default = "default_rocksdb_max_log_files")]
1758 pub rocksdb_max_log_files: usize,
1759
1760 /// Type of RocksDB database compression to use.
1761 ///
1762 /// Available options are "zstd", "bz2", "lz4", or "none".
1763 ///
1764 /// It is best to use ZSTD as an overall good balance between
1765 /// speed/performance, storage, IO amplification, and CPU usage. For more
1766 /// performance but less compression (more storage used) and less CPU usage,
1767 /// use LZ4.
1768 ///
1769 /// For more details, see:
1770 /// https://github.com/facebook/rocksdb/wiki/Compression
1771 ///
1772 /// "none" will disable compression.
1773 ///
1774 /// default: "zstd"
1775 #[serde(default = "default_rocksdb_compression_algo")]
1776 pub rocksdb_compression_algo: String,
1777
1778 /// Level of compression the specified compression algorithm for RocksDB to
1779 /// use.
1780 ///
1781 /// Default is 32767, which is internally read by RocksDB as the default
1782 /// magic number and translated to the library's default compression level
1783 /// as they all differ. See their `kDefaultCompressionLevel`.
1784 ///
1785 /// Note when using the default value we may override it with a setting
1786 /// tailored specifically tuwunel.
1787 ///
1788 /// default: 32767
1789 #[serde(default = "default_rocksdb_compression_level")]
1790 pub rocksdb_compression_level: i32,
1791
1792 /// Level of compression the specified compression algorithm for the
1793 /// bottommost level/data for RocksDB to use. Default is 32767, which is
1794 /// internally read by RocksDB as the default magic number and translated to
1795 /// the library's default compression level as they all differ. See their
1796 /// `kDefaultCompressionLevel`.
1797 ///
1798 /// Since this is the bottommost level (generally old and least used data),
1799 /// it may be desirable to have a very high compression level here as it's
1800 /// less likely for this data to be used. Research your chosen compression
1801 /// algorithm.
1802 ///
1803 /// Note when using the default value we may override it with a setting
1804 /// tailored specifically tuwunel.
1805 ///
1806 /// default: 32767
1807 #[serde(default = "default_rocksdb_bottommost_compression_level")]
1808 pub rocksdb_bottommost_compression_level: i32,
1809
1810 /// Whether to enable RocksDB's "bottommost_compression".
1811 ///
1812 /// At the expense of more CPU usage, this will further compress the
1813 /// database to reduce more storage. It is recommended to use ZSTD
1814 /// compression with this for best compression results. This may be useful
1815 /// if you're trying to reduce storage usage from the database.
1816 ///
1817 /// See https://github.com/facebook/rocksdb/wiki/Compression for more details.
1818 #[serde(default = "true_fn")]
1819 pub rocksdb_bottommost_compression: bool,
1820
1821 /// Database recovery mode (for RocksDB WAL corruption).
1822 ///
1823 /// Use this option when the server reports corruption and refuses to start.
1824 /// Set mode 2 (PointInTime) to cleanly recover from this corruption. The
1825 /// server will continue from the last good state, several seconds or
1826 /// minutes prior to the crash. Clients may have to run "clear-cache &
1827 /// reload" to account for the rollback. Upon success, you may reset the
1828 /// mode back to default and restart again. Please note in some cases the
1829 /// corruption error may not be cleared for at least 30 minutes of operation
1830 /// in PointInTime mode.
1831 ///
1832 /// As a very last ditch effort, if PointInTime does not fix or resolve
1833 /// anything, you can try mode 3 (SkipAnyCorruptedRecord) but this will
1834 /// leave the server in a potentially inconsistent state.
1835 ///
1836 /// The default mode 1 (TolerateCorruptedTailRecords) will automatically
1837 /// drop the last entry in the database if corrupted during shutdown, but
1838 /// nothing more. It is extraordinarily unlikely this will desynchronize
1839 /// clients. To disable any form of silent rollback set mode 0
1840 /// (AbsoluteConsistency).
1841 ///
1842 /// The options are:
1843 /// 0 = AbsoluteConsistency
1844 /// 1 = TolerateCorruptedTailRecords (default)
1845 /// 2 = PointInTime (use me if trying to recover)
1846 /// 3 = SkipAnyCorruptedRecord (you now voided your tuwunel warranty)
1847 ///
1848 /// For more information on these modes, see:
1849 /// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes
1850 ///
1851 /// For more details on recovering a corrupt database, see:
1852 /// https://tuwunel.chat/troubleshooting.html#database-corruption
1853 ///
1854 /// default: 1
1855 #[serde(default = "default_rocksdb_recovery_mode")]
1856 pub rocksdb_recovery_mode: u8,
1857
1858 /// Enables or disables paranoid SST file checks. This can improve RocksDB
1859 /// database consistency at a potential performance impact due to further
1860 /// safety checks ran.
1861 ///
1862 /// For more information, see:
1863 /// https://github.com/facebook/rocksdb/wiki/Online-Verification#columnfamilyoptionsparanoid_file_checks
1864 #[serde(default)]
1865 pub rocksdb_paranoid_file_checks: bool,
1866
1867 /// Enables or disables checksum verification in rocksdb at runtime.
1868 /// Checksums are usually hardware accelerated with low overhead; they are
1869 /// enabled in rocksdb by default. Older or slower platforms may see gains
1870 /// from disabling.
1871 ///
1872 /// default: true
1873 #[serde(default = "true_fn")]
1874 pub rocksdb_checksums: bool,
1875
1876 /// Enables the "atomic flush" mode in rocksdb. This option is not intended
1877 /// for users. It may be removed or ignored in future versions. Atomic flush
1878 /// may be enabled by the paranoid to possibly improve database integrity at
1879 /// the cost of performance.
1880 #[serde(default)]
1881 pub rocksdb_atomic_flush: bool,
1882
1883 /// Database repair mode (for RocksDB SST corruption).
1884 ///
1885 /// Use this option when the server reports corruption while running or
1886 /// panics. If the server refuses to start use the recovery mode options
1887 /// first. Corruption errors containing the acronym 'SST' which occur after
1888 /// startup will likely require this option.
1889 ///
1890 /// - Backing up your database directory is recommended prior to running the
1891 /// repair.
1892 ///
1893 /// - Disabling repair mode and restarting the server is recommended after
1894 /// running the repair.
1895 ///
1896 /// See https://tuwunel.chat/troubleshooting.html#database-corruption for more details on recovering a corrupt database.
1897 #[serde(default)]
1898 pub rocksdb_repair: bool,
1899
1900 #[serde(default)]
1901 pub rocksdb_read_only: bool,
1902
1903 #[serde(default)]
1904 pub rocksdb_secondary: bool,
1905
1906 /// Enables idle CPU priority for compaction thread. This is not enabled by
1907 /// default to prevent compaction from falling too far behind on busy
1908 /// systems.
1909 #[serde(default)]
1910 pub rocksdb_compaction_prio_idle: bool,
1911
1912 /// Enables idle IO priority for compaction thread. This prevents any
1913 /// unexpected lag in the server's operation and is usually a good idea.
1914 /// Enabled by default.
1915 #[serde(default = "true_fn")]
1916 pub rocksdb_compaction_ioprio_idle: bool,
1917
1918 /// Enables RocksDB compaction. You should never ever have to set this
1919 /// option to false. If you for some reason find yourself needing to use
1920 /// this option as part of troubleshooting or a bug, please reach out to us
1921 /// in the tuwunel Matrix room with information and details.
1922 ///
1923 /// Disabling compaction will lead to a significantly bloated and
1924 /// explosively large database, gradually poor performance, unnecessarily
1925 /// excessive disk read/writes, and slower shutdowns and startups.
1926 #[serde(default = "true_fn")]
1927 pub rocksdb_compaction: bool,
1928
1929 /// Level of statistics collection. Some admin commands to display database
1930 /// statistics may require this option to be set. Database performance may
1931 /// be impacted by higher settings.
1932 ///
1933 /// Option is a number ranging from 0 to 6:
1934 /// 0 = No statistics.
1935 /// 1 = No statistics in release mode (default).
1936 /// 2 to 3 = Statistics with no performance impact.
1937 /// 3 to 5 = Statistics with possible performance impact.
1938 /// 6 = All statistics.
1939 ///
1940 /// default: 1
1941 #[serde(default = "default_rocksdb_stats_level")]
1942 pub rocksdb_stats_level: u8,
1943
1944 /// Ignores the list of dropped columns set by developers.
1945 ///
1946 /// This should be set to true when knowingly moving between versions in
1947 /// ways which are not recommended or otherwise forbidden, or for
1948 /// diagnostic and development purposes; requiring preservation across such
1949 /// movements.
1950 ///
1951 /// The developer's list of dropped columns is meant to safely reduce space
1952 /// by erasing data no longer in use. If this is set to true that storage
1953 /// will not be reclaimed as intended.
1954 ///
1955 /// default: false
1956 #[serde(default)]
1957 pub rocksdb_never_drop_columns: bool,
1958
1959 /// Configures RocksDB to not preallocate WAL logs.
1960 ///
1961 /// Normally, RocksDB allocates certain types of files by calling
1962 /// fallocate, writing the file contents, then truncating the logs to the
1963 /// proper size. This causes pathological disk space usage on btrfs due to
1964 /// how it interacts with its Copy-on-Write implementation. On ZFS,
1965 /// fallocate(2) for preallocation is unsupported and returns EOPNOTSUPP;
1966 /// only `FALLOC_FL_PUNCH_HOLE` and `FALLOC_FL_ZERO_RANGE` are implemented.
1967 ///
1968 /// Set this to false if you run the server on btrfs or ZFS, and do not
1969 /// touch it otherwise.
1970 ///
1971 /// default: true
1972 #[serde(default = "true_fn")]
1973 pub rocksdb_allow_fallocate: bool,
1974
1975 /// This is a password that can be configured that will let you login to the
1976 /// server bot account (currently `@conduit`) for emergency troubleshooting
1977 /// purposes such as recovering/recreating your admin room, or inviting
1978 /// yourself back.
1979 ///
1980 /// See https://tuwunel.chat/troubleshooting.html#lost-access-to-admin-room
1981 /// for other ways to get back into your admin room.
1982 ///
1983 /// Once this password is unset, all sessions will be logged out for
1984 /// security purposes.
1985 ///
1986 /// example: "F670$2CP@Hw8mG7RY1$%!#Ic7YA"
1987 ///
1988 /// display: sensitive
1989 pub emergency_password: Option<String>,
1990
1991 /// reloadable: yes
1992 /// default: "/_matrix/push/v1/notify"
1993 #[serde(default = "default_notification_push_path")]
1994 pub notification_push_path: String,
1995
1996 /// For compatibility and special purpose use only. Setting this option to
1997 /// true will not filter messages sent to pushers based on rules or actions.
1998 /// Everything will be sent to the pusher. This option is offered for
1999 /// several reasons, but should not be necessary:
2000 /// - Bypass to workaround bugs or outdated server-side ruleset support.
2001 /// - Allow clients to evaluate pushrules themselves (due to the above).
2002 /// - Hosting or companies which have custom pushers and internal needs.
2003 ///
2004 /// Note that setting this option to true will not affect the record of
2005 /// notifications found in the notifications pane.
2006 /// reloadable: yes
2007 #[serde(default)]
2008 pub push_everything: bool,
2009
2010 /// Setting to false disables the heroes calculation made by sliding and
2011 /// legacy client sync. The heroes calculation is mandated by the Matrix
2012 /// specification and your client may not operate properly unless this
2013 /// option is set to true.
2014 ///
2015 /// This option is intended for custom software deployments seeking purely
2016 /// to minimize unused resources; the overall savings are otherwise
2017 /// negligible.
2018 /// reloadable: yes
2019 #[serde(default = "true_fn")]
2020 pub calculate_heroes: bool,
2021
2022 /// Allow local (your server only) presence updates/requests.
2023 ///
2024 /// Note that presence on tuwunel is very fast unlike Synapse's. If using
2025 /// outgoing presence, this MUST be enabled.
2026 /// reloadable: yes
2027 #[serde(default = "true_fn")]
2028 pub allow_local_presence: bool,
2029
2030 /// Allow incoming federated presence updates/requests.
2031 ///
2032 /// This option receives presence updates from other servers, but does not
2033 /// send any unless `allow_outgoing_presence` is true. Note that presence on
2034 /// tuwunel is very fast unlike Synapse's.
2035 /// reloadable: yes
2036 #[serde(default = "true_fn")]
2037 pub allow_incoming_presence: bool,
2038
2039 /// Allow outgoing presence updates/requests.
2040 ///
2041 /// This option sends presence updates to other servers, but does not
2042 /// receive any unless `allow_incoming_presence` is true. Note that presence
2043 /// on tuwunel is very fast unlike Synapse's. If using outgoing presence,
2044 /// you MUST enable `allow_local_presence` as well.
2045 /// reloadable: yes
2046 #[serde(default = "true_fn")]
2047 pub allow_outgoing_presence: bool,
2048
2049 /// How many seconds without presence updates before you become idle.
2050 /// Defaults to 5 minutes.
2051 ///
2052 /// default: 300
2053 #[serde(default = "default_presence_idle_timeout_s")]
2054 pub presence_idle_timeout_s: u64,
2055
2056 /// How many seconds without presence updates before you become offline.
2057 /// Defaults to 30 minutes.
2058 ///
2059 /// default: 1800
2060 #[serde(default = "default_presence_offline_timeout_s")]
2061 pub presence_offline_timeout_s: u64,
2062
2063 /// Enable the presence idle timer for remote users.
2064 ///
2065 /// Disabling is offered as an optimization for servers participating in
2066 /// many large rooms or when resources are limited. Disabling it may cause
2067 /// incorrect presence states (i.e. stuck online) to be seen for some remote
2068 /// users.
2069 #[serde(default = "true_fn")]
2070 pub presence_timeout_remote_users: bool,
2071
2072 /// Suppresses push notifications for users marked as active. (Experimental)
2073 ///
2074 /// When enabled, users with `Online` presence and recent activity
2075 /// (based on presence state and sync activity) won’t receive push
2076 /// notifications, reducing duplicate alerts while they're active
2077 /// on another client.
2078 ///
2079 /// Disabled by default to preserve legacy behavior.
2080 /// reloadable: yes
2081 #[serde(default)]
2082 pub suppress_push_when_active: bool,
2083
2084 /// Allow receiving incoming read receipts from remote servers.
2085 /// reloadable: yes
2086 #[serde(default = "true_fn")]
2087 pub allow_incoming_read_receipts: bool,
2088
2089 /// Allow sending read receipts to remote servers.
2090 /// reloadable: yes
2091 #[serde(default = "true_fn")]
2092 pub allow_outgoing_read_receipts: bool,
2093
2094 /// Allow outgoing typing updates to federation.
2095 /// reloadable: yes
2096 #[serde(default = "true_fn")]
2097 pub allow_outgoing_typing: bool,
2098
2099 /// Allow incoming typing updates from federation.
2100 /// reloadable: yes
2101 #[serde(default = "true_fn")]
2102 pub allow_incoming_typing: bool,
2103
2104 /// Maximum time federation user can indicate typing.
2105 ///
2106 /// reloadable: yes
2107 /// default: 30
2108 #[serde(default = "default_typing_federation_timeout_s")]
2109 pub typing_federation_timeout_s: u64,
2110
2111 /// Minimum time local client can indicate typing. This does not override a
2112 /// client's request to stop typing. It only enforces a minimum value in
2113 /// case of no stop request.
2114 ///
2115 /// reloadable: yes
2116 /// default: 15
2117 #[serde(default = "default_typing_client_timeout_min_s")]
2118 pub typing_client_timeout_min_s: u64,
2119
2120 /// Maximum time local client can indicate typing.
2121 ///
2122 /// reloadable: yes
2123 /// default: 45
2124 #[serde(default = "default_typing_client_timeout_max_s")]
2125 pub typing_client_timeout_max_s: u64,
2126
2127 /// Set this to true for tuwunel to compress HTTP response bodies using
2128 /// zstd. This option does nothing if tuwunel was not built with
2129 /// `zstd_compression` feature. Please be aware that enabling HTTP
2130 /// compression may weaken TLS. Most users should not need to enable this.
2131 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH
2132 /// before deciding to enable this.
2133 #[serde(default)]
2134 pub zstd_compression: bool,
2135
2136 /// Set this to true for tuwunel to compress HTTP response bodies using
2137 /// gzip. This option does nothing if tuwunel was not built with
2138 /// `gzip_compression` feature. Please be aware that enabling HTTP
2139 /// compression may weaken TLS. Most users should not need to enable this.
2140 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH before
2141 /// deciding to enable this.
2142 ///
2143 /// If you are in a large amount of rooms, you may find that enabling this
2144 /// is necessary to reduce the significantly large response bodies.
2145 #[serde(default)]
2146 pub gzip_compression: bool,
2147
2148 /// Set this to true for tuwunel to compress HTTP response bodies using
2149 /// brotli. This option does nothing if tuwunel was not built with
2150 /// `brotli_compression` feature. Please be aware that enabling HTTP
2151 /// compression may weaken TLS. Most users should not need to enable this.
2152 /// See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH
2153 /// before deciding to enable this.
2154 #[serde(default)]
2155 pub brotli_compression: bool,
2156
2157 /// Set to true to allow user type "guest" registrations. Some clients like
2158 /// Element attempt to register guest users automatically.
2159 /// reloadable: yes
2160 #[serde(default)]
2161 pub allow_guest_registration: bool,
2162
2163 /// Set to true to log guest registrations in the admin room. Note that
2164 /// these may be noisy or unnecessary if you're a public homeserver.
2165 /// reloadable: yes
2166 #[serde(default)]
2167 pub log_guest_registrations: bool,
2168
2169 /// Set to true to allow guest registrations/users to auto join any rooms
2170 /// specified in `auto_join_rooms`.
2171 /// reloadable: yes
2172 #[serde(default)]
2173 pub allow_guests_auto_join_rooms: bool,
2174
2175 /// Enable the legacy unauthenticated Matrix media repository endpoints.
2176 /// These endpoints consist of:
2177 /// - /_matrix/media/*/config
2178 /// - /_matrix/media/*/upload
2179 /// - /_matrix/media/*/preview_url
2180 /// - /_matrix/media/*/download/*
2181 /// - /_matrix/media/*/thumbnail/*
2182 ///
2183 /// The authenticated equivalent endpoints are always enabled.
2184 ///
2185 /// Defaults to false.
2186 #[serde(default)]
2187 pub allow_legacy_media: bool,
2188
2189 /// Fallback to requesting legacy unauthenticated media from remote servers.
2190 /// Unauthenticated media was removed in ~2024Q3; enabling this adds
2191 /// considerable federation requests which are unlikely to succeed.
2192 /// reloadable: yes
2193 #[serde(default)]
2194 pub request_legacy_media: bool,
2195
2196 /// reloadable: yes
2197 #[serde(default = "true_fn")]
2198 pub freeze_legacy_media: bool,
2199
2200 /// Check consistency of the media directory at startup:
2201 /// 1. When `media_compat_file_link` is enabled, this check will upgrade
2202 /// media when switching back and forth between Conduit and tuwunel. Both
2203 /// options must be enabled to handle this.
2204 /// 2. When media is deleted from the directory, this check will also delete
2205 /// its database entry.
2206 ///
2207 /// If none of these checks apply to your use cases, and your media
2208 /// directory is significantly large setting this to false may reduce
2209 /// startup time.
2210 #[serde(default = "true_fn")]
2211 pub media_startup_check: bool,
2212
2213 /// Enable backward-compatibility with Conduit's media directory by creating
2214 /// symlinks of media.
2215 ///
2216 /// This option is only necessary if you plan on using Conduit again.
2217 /// Otherwise setting this to false reduces filesystem clutter and overhead
2218 /// for managing these symlinks in the directory. This is now disabled by
2219 /// default. You may still return to upstream Conduit but you have to run
2220 /// tuwunel at least once with this set to true and allow the
2221 /// media_startup_check to take place before shutting down to return to
2222 /// Conduit.
2223 #[serde(default)]
2224 pub media_compat_file_link: bool,
2225
2226 /// Prune missing media from the database as part of the media startup
2227 /// checks.
2228 ///
2229 /// This means if you delete files from the media directory the
2230 /// corresponding entries will be removed from the database. This is
2231 /// disabled by default because if the media directory is accidentally moved
2232 /// or inaccessible, the metadata entries in the database will be lost with
2233 /// sadness.
2234 #[serde(default)]
2235 pub prune_missing_media: bool,
2236
2237 /// List of storage providers to use for media. Providers can be configured
2238 /// below in respective sections designated by
2239 /// `global.storage_provider.<NAME>.<brand>` where `NAME` can be listed
2240 /// here.
2241 ///
2242 /// For advanced features and future extensions involving multiple providers
2243 /// the list may contain multiple entries. You MUST take note of other
2244 /// configuration options when listing multiple providers or resource
2245 /// duplication costs and poor performance can result.
2246 ///
2247 /// The list defaults to `["media"]` which is an implicit storage provider
2248 /// representing the media directory on the local filesystem. It can be
2249 /// altered by configuring `global.storage_provider.media.local` explicitly
2250 /// or disabled by omitting it from this list entirely. Users with existing
2251 /// deployments are advised to continue listing "media" as a fallback along
2252 /// with their new provider.
2253 ///
2254 /// reloadable: yes
2255 /// default: ["media"]
2256 #[serde(default = "default_media_storage_providers")]
2257 pub media_storage_providers: BTreeSet<String>,
2258
2259 /// List of configured storage providers where new media will be sent. When
2260 /// this list is not explicitly configured all entries in
2261 /// `media_storage_providers` are used as default.
2262 ///
2263 /// This list is important for users passively migrating to a new media
2264 /// storage provider by only writing to one while querying the other as a
2265 /// fallback.
2266 ///
2267 /// For example:
2268 ///
2269 /// `media_storage_providers = ["media", "media_on_s3"]`
2270 /// `store_media_on_providers = ["media_on_s3"]`
2271 ///
2272 /// Entries in this list must also be listed in `media_storage_providers`.
2273 ///
2274 /// reloadable: yes
2275 /// default: []
2276 #[serde(default)]
2277 pub store_media_on_providers: BTreeSet<String>,
2278
2279 /// Redirect local media downloads to a presigned object-store URL when the
2280 /// client sends `allow_redirect=true` (MSC3860). When a configured storage
2281 /// provider can presign the object (S3), the download responds with a 307
2282 /// to a short-lived URL instead of proxying the bytes. Media held only on
2283 /// the local filesystem is always served directly.
2284 ///
2285 /// reloadable: yes
2286 /// default: false
2287 #[serde(default)]
2288 pub media_allow_redirect: bool,
2289
2290 /// Vector list of regex patterns of server names that tuwunel will refuse
2291 /// to download remote media from.
2292 ///
2293 /// reloadable: yes
2294 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
2295 ///
2296 /// default: []
2297 #[serde(default, with = "serde_regex")]
2298 pub prevent_media_downloads_from: RegexSet,
2299
2300 /// List of forbidden server names via regex patterns that we will block
2301 /// incoming AND outgoing federation with, and block client room joins /
2302 /// remote user invites.
2303 ///
2304 /// This check is applied on the room ID, room alias, sender server name,
2305 /// sender user's server name, inbound federation X-Matrix origin, and
2306 /// outbound federation handler.
2307 ///
2308 /// Basically "global" ACLs.
2309 ///
2310 /// The server's own name is always permitted and is never subject to this
2311 /// list.
2312 ///
2313 /// reloadable: yes
2314 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
2315 ///
2316 /// default: []
2317 #[serde(default, with = "serde_regex")]
2318 pub forbidden_remote_server_names: RegexSet,
2319
2320 /// (EXPERIMENTAL) The behavior of this option will change; the
2321 /// _experimental suffix will be removed for that change in an upcoming
2322 /// release.
2323 ///
2324 /// List of allowed server names via regex patterns. This is an allow-list
2325 /// rather than a deny-list with all the same details as its counterpart in
2326 /// `forbidden_remote_server_names`.
2327 ///
2328 /// This feature becomes active when this list has one or more entries;
2329 /// everything not matching is denied. By default it is empty and inactive.
2330 ///
2331 /// The server's own name is always permitted and is never subject to this
2332 /// list.
2333 ///
2334 /// Entries in `forbidden_remote_server_names` are still applied after
2335 /// this is applied. This allows you to match e.g. "*\.example\.com" here
2336 /// while still singling out "bad\.example\.com" for exclusion.
2337 ///
2338 /// reloadable: yes
2339 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
2340 ///
2341 /// default: []
2342 #[serde(default, with = "serde_regex")]
2343 pub allowed_remote_server_names_experimental: RegexSet,
2344
2345 /// List of forbidden server names via regex patterns that we will block all
2346 /// outgoing federated room directory requests for. Useful for preventing
2347 /// our users from wandering into bad servers or spaces.
2348 ///
2349 /// reloadable: yes
2350 /// example: ["badserver\.tld$", "badphrase", "19dollarfortnitecards"]
2351 ///
2352 /// default: []
2353 #[serde(default, with = "serde_regex")]
2354 pub forbidden_remote_room_directory_server_names: RegexSet,
2355
2356 #[expect(clippy::doc_link_with_quotes)]
2357 /// Vector list of IPv4 and IPv6 CIDR ranges / subnets *in quotes* that you
2358 /// do not want tuwunel to send outbound requests to. Defaults to
2359 /// RFC1918, unroutable, loopback, multicast, and testnet addresses for
2360 /// security.
2361 ///
2362 /// Please be aware that this is *not* a guarantee. You should be using a
2363 /// firewall with zones as doing this on the application layer may have
2364 /// bypasses.
2365 ///
2366 /// Currently this does not account for proxies in use like Synapse does.
2367 ///
2368 /// To disable, set this to be an empty vector (`[]`).
2369 ///
2370 /// Defaults to:
2371 /// ["127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
2372 /// "192.168.0.0/16", "100.64.0.0/10", "192.0.0.0/24", "169.254.0.0/16",
2373 /// "192.88.99.0/24", "198.18.0.0/15", "192.0.2.0/24", "198.51.100.0/24",
2374 /// "203.0.113.0/24", "224.0.0.0/4", "::1/128", "fe80::/10", "fc00::/7",
2375 /// "2001:db8::/32", "ff00::/8", "fec0::/10"]
2376 #[serde(default = "default_ip_range_denylist")]
2377 pub ip_range_denylist: Vec<String>,
2378
2379 /// Optional IP address or network interface-name to bind as the source of
2380 /// URL preview requests. If not set, it will not bind to a specific
2381 /// address or interface.
2382 ///
2383 /// Interface names only supported on Linux, Android, and Fuchsia platforms;
2384 /// all other platforms can specify the IP address. To list the interfaces
2385 /// on your system, use the command `ip link show`.
2386 ///
2387 /// example: `"eth0"` or `"1.2.3.4"`
2388 ///
2389 /// default:
2390 #[serde(default, with = "either::serde_untagged_optional")]
2391 pub url_preview_bound_interface: Option<Either<IpAddr, String>>,
2392
2393 /// Vector list of domains allowed to send requests to for URL previews.
2394 ///
2395 /// This is a *contains* match, not an explicit match. Putting "google.com"
2396 /// will match "https://google.com" and
2397 /// "http://mymaliciousdomainexamplegoogle.com" Setting this to "*" will
2398 /// allow all URL previews. Please note that this opens up significant
2399 /// attack surface to your server, you are expected to be aware of the risks
2400 /// by doing so.
2401 ///
2402 /// reloadable: yes
2403 /// default: []
2404 #[serde(default)]
2405 pub url_preview_domain_contains_allowlist: Vec<String>,
2406
2407 /// Vector list of explicit domains allowed to send requests to for URL
2408 /// previews.
2409 ///
2410 /// This is an *explicit* match, not a contains match. Putting "google.com"
2411 /// will match "https://google.com", "http://google.com", but not
2412 /// "https://mymaliciousdomainexamplegoogle.com". Setting this to "*" will
2413 /// allow all URL previews. Please note that this opens up significant
2414 /// attack surface to your server, you are expected to be aware of the risks
2415 /// by doing so.
2416 ///
2417 /// reloadable: yes
2418 /// default: []
2419 #[serde(default)]
2420 pub url_preview_domain_explicit_allowlist: Vec<String>,
2421
2422 /// Vector list of explicit domains not allowed to send requests to for URL
2423 /// previews.
2424 ///
2425 /// This is an *explicit* match, not a contains match. Putting "google.com"
2426 /// will match "https://google.com", "http://google.com", but not
2427 /// "https://mymaliciousdomainexamplegoogle.com". The denylist is checked
2428 /// first before allowlist. Setting this to "*" will not do anything.
2429 ///
2430 /// reloadable: yes
2431 /// default: []
2432 #[serde(default)]
2433 pub url_preview_domain_explicit_denylist: Vec<String>,
2434
2435 /// Vector list of URLs allowed to send requests to for URL previews.
2436 ///
2437 /// Note that this is a *contains* match, not an explicit match. Putting
2438 /// "google.com" will match "https://google.com/",
2439 /// "https://google.com/url?q=https://mymaliciousdomainexample.com", and
2440 /// "https://mymaliciousdomainexample.com/hi/google.com" Setting this to "*"
2441 /// will allow all URL previews. Please note that this opens up significant
2442 /// attack surface to your server, you are expected to be aware of the risks
2443 /// by doing so.
2444 ///
2445 /// reloadable: yes
2446 /// default: []
2447 #[serde(default)]
2448 pub url_preview_url_contains_allowlist: Vec<String>,
2449
2450 /// Maximum body size allowed when spidering a URL for previews. Accepts an
2451 /// integer byte count or a string with SI/IEC suffix such as "256 KB".
2452 ///
2453 /// reloadable: yes
2454 /// default: 256000
2455 #[serde(
2456 default = "default_url_preview_max_spider_size",
2457 deserialize_with = "deserialize_bytesize_usize"
2458 )]
2459 pub url_preview_max_spider_size: usize,
2460
2461 /// Option to decide whether you would like to run the domain allowlist
2462 /// checks (contains and explicit) on the root domain or not. Does not apply
2463 /// to URL contains allowlist. Defaults to false.
2464 ///
2465 /// Example usecase: If this is enabled and you have "wikipedia.org" allowed
2466 /// in the explicit and/or contains domain allowlist, it will allow all
2467 /// subdomains under "wikipedia.org" such as "en.m.wikipedia.org" as the
2468 /// root domain is checked and matched. Useful if the domain contains
2469 /// allowlist is still too broad for you but you still want to allow all the
2470 /// subdomains under a root domain.
2471 /// reloadable: yes
2472 #[serde(default)]
2473 pub url_preview_check_root_domain: bool,
2474
2475 /// List of forbidden room aliases and room IDs as strings of regex
2476 /// patterns.
2477 ///
2478 /// Regex can be used or explicit contains matches can be done by just
2479 /// specifying the words (see example).
2480 ///
2481 /// This is checked upon room alias creation, custom room ID creation if
2482 /// used, and startup as warnings if any room aliases in your database have
2483 /// a forbidden room alias/ID.
2484 ///
2485 /// reloadable: yes
2486 /// example: ["19dollarfortnitecards", "b[4a]droom", "badphrase"]
2487 ///
2488 /// default: []
2489 #[serde(default, with = "serde_regex")]
2490 pub forbidden_alias_names: RegexSet,
2491
2492 /// List of forbidden username patterns/strings.
2493 ///
2494 /// Regex can be used or explicit contains matches can be done by just
2495 /// specifying the words (see example).
2496 ///
2497 /// This is checked upon username availability check, registration, and
2498 /// startup as warnings if any local users in your database have a forbidden
2499 /// username.
2500 ///
2501 /// reloadable: yes
2502 /// example: ["administrator", "b[a4]dusernam[3e]", "badphrase"]
2503 ///
2504 /// default: []
2505 #[serde(default, with = "serde_regex")]
2506 pub forbidden_usernames: RegexSet,
2507
2508 /// List of server names to deprioritize joining through.
2509 ///
2510 /// If a client requests a join through one of these servers,
2511 /// they will be tried last.
2512 ///
2513 /// Useful for preventing failed joins due to timeouts
2514 /// from a certain homeserver.
2515 ///
2516 /// reloadable: yes
2517 /// default: ["matrix\.org"]
2518 #[serde(
2519 default = "default_deprioritize_joins_through_servers",
2520 with = "serde_regex"
2521 )]
2522 pub deprioritize_joins_through_servers: RegexSet,
2523
2524 /// Maximum make_join requests to attempt within each join attempt. Each
2525 /// attempt tries a different server, as each server is only tried once;
2526 /// though retries can occur when the join request as a whole is retried.
2527 ///
2528 /// reloadable: yes
2529 /// default: 48
2530 #[serde(default = "default_max_make_join_attempts_per_join_attempt")]
2531 pub max_make_join_attempts_per_join_attempt: usize,
2532
2533 /// Maximum join attempts to conduct per client join request. Each join
2534 /// attempt consists of one or more make_join requests limited above, and a
2535 /// single send_join request. This value allows for additional servers to
2536 /// act as the join-server prior to reporting the last error back to the
2537 /// client, which can be frustrating for users. Therefor the default value
2538 /// is greater than one, but less than excessively exceeding the client's
2539 /// request timeout, though that may not be avoidable in some cases.
2540 ///
2541 /// reloadable: yes
2542 /// default: 3
2543 #[serde(default = "default_max_join_attempts_per_join_request")]
2544 pub max_join_attempts_per_join_request: usize,
2545
2546 /// Retry failed and incomplete messages to remote servers immediately upon
2547 /// startup. This is called bursting. If this is disabled, said messages may
2548 /// not be delivered until more messages are queued for that server. Do not
2549 /// change this option unless server resources are extremely limited or the
2550 /// scale of the server's deployment is huge. Do not disable this unless you
2551 /// know what you are doing.
2552 #[serde(default = "true_fn")]
2553 pub startup_netburst: bool,
2554
2555 /// Messages are dropped and not reattempted. The `startup_netburst` option
2556 /// must be enabled for this value to have any effect. Do not change this
2557 /// value unless you know what you are doing. Set this value to -1 to
2558 /// reattempt every message without trimming the queues; this may consume
2559 /// significant disk. Set this value to 0 to drop all messages without any
2560 /// attempt at redelivery.
2561 ///
2562 /// default: 50
2563 #[serde(default = "default_startup_netburst_keep")]
2564 pub startup_netburst_keep: i64,
2565
2566 /// Block non-admin local users from sending room invites (local and
2567 /// remote), and block non-admin users from receiving remote room invites.
2568 ///
2569 /// Admins are always allowed to send and receive all room invites.
2570 /// reloadable: yes
2571 #[serde(default)]
2572 pub block_non_admin_invites: bool,
2573
2574 /// Enforce MSC4311 validation of the create event in federated invite and
2575 /// knock stripped state. When enabled, an invite whose m.room.create event
2576 /// is missing, not a full PDU, bound to a different room, or fails
2577 /// signature checks is rejected, and such events are dropped from knock
2578 /// stripped state. When disabled (the default), failures are logged but
2579 /// tolerated to preserve interoperability during ecosystem migration; a
2580 /// create event that is present as a full PDU but cryptographically bound
2581 /// to a different room is always rejected for room version 12 and above
2582 /// regardless of this setting.
2583 ///
2584 /// reloadable: yes
2585 #[serde(default)]
2586 pub enforce_stripped_state_pdu_validation: bool,
2587
2588 /// Allow admins to enter commands in rooms other than "#admins" (admin
2589 /// room) by prefixing your message with "\!admin" or "\\!admin" followed up
2590 /// a normal tuwunel admin command. The reply will be publicly visible to
2591 /// the room, originating from the sender.
2592 ///
2593 /// reloadable: yes
2594 /// example: \\!admin debug ping puppygock.gay
2595 #[serde(default = "true_fn")]
2596 pub admin_escape_commands: bool,
2597
2598 /// Automatically activate the tuwunel admin room console / CLI on
2599 /// startup. This option can also be enabled with `--console` tuwunel
2600 /// argument.
2601 #[serde(default)]
2602 pub admin_console_automatic: bool,
2603
2604 #[expect(clippy::doc_link_with_quotes)]
2605 /// List of admin commands to execute on startup.
2606 ///
2607 /// This option can also be configured with the `--execute` tuwunel
2608 /// argument and can take standard shell commands and environment variables
2609 ///
2610 /// For example: `./tuwunel --execute "server admin-notice tuwunel has
2611 /// started up at $(date)"`
2612 ///
2613 /// example: admin_execute = ["debug ping puppygock.gay", "debug echo hi"]`
2614 ///
2615 /// default: []
2616 #[serde(default)]
2617 pub admin_execute: Vec<String>,
2618
2619 /// Ignore errors in startup commands.
2620 ///
2621 /// If false, tuwunel will error and fail to start if an admin execute
2622 /// command (`--execute` / `admin_execute`) fails.
2623 /// reloadable: yes
2624 #[serde(default)]
2625 pub admin_execute_errors_ignore: bool,
2626
2627 /// List of admin commands to execute on SIGUSR2.
2628 ///
2629 /// Similar to admin_execute, but these commands are executed when the
2630 /// server receives SIGUSR2 on supporting platforms.
2631 ///
2632 /// reloadable: yes
2633 /// default: []
2634 #[serde(default)]
2635 pub admin_signal_execute: Vec<String>,
2636
2637 /// Controls the max log level for admin command log captures (logs
2638 /// generated from running admin commands). Defaults to "info" on release
2639 /// builds, else "debug" on debug builds.
2640 ///
2641 /// reloadable: yes
2642 /// default: "info"
2643 #[serde(default = "default_admin_log_capture")]
2644 pub admin_log_capture: String,
2645
2646 /// The default room tag to apply on the admin room.
2647 ///
2648 /// On some clients like Element, the room tag "m.server_notice" is a
2649 /// special pinned room at the very bottom of your room list. The tuwunel
2650 /// admin room can be pinned here so you always have an easy-to-access
2651 /// shortcut dedicated to your admin room.
2652 ///
2653 /// reloadable: yes
2654 /// default: "m.server_notice"
2655 #[serde(default = "default_admin_room_tag")]
2656 pub admin_room_tag: String,
2657
2658 /// The room that user, room, and event reports are posted to, instead of
2659 /// the admin room. Accepts a room ID or room alias; the server user must be
2660 /// joined with permission to post there. Reports fall back to the admin
2661 /// room when this is unset, cannot be resolved, or the server user is not a
2662 /// member.
2663 ///
2664 /// reloadable: yes
2665 /// default: (none)
2666 #[serde(default)]
2667 pub report_room: Option<OwnedRoomOrAliasId>,
2668
2669 /// Whether to grant the first user to register admin privileges by joining
2670 /// them to the admin room. Note that technically the next user to register
2671 /// when the admin room is empty (or only contains the server-user) is
2672 /// granted, and only when the admin room is enabled.
2673 ///
2674 /// reloadable: yes
2675 /// default: true
2676 #[serde(default = "true_fn")]
2677 pub grant_admin_to_first_user: bool,
2678
2679 /// Whether the admin room is created on first startup. Users should not set
2680 /// this to false. Developers can set this to false during integration tests
2681 /// to reduce activity and output.
2682 ///
2683 /// default: true
2684 #[serde(default = "true_fn")]
2685 pub create_admin_room: bool,
2686
2687 /// Whether to enable federation on the admin room. This cannot be changed
2688 /// after the admin room is created.
2689 ///
2690 /// default: true
2691 #[serde(default = "true_fn")]
2692 pub federate_admin_room: bool,
2693
2694 /// Sentry.io crash/panic reporting, performance monitoring/metrics, etc.
2695 /// This is NOT enabled by default. tuwunel's default Sentry reporting
2696 /// endpoint domain is `o4509498990067712.ingest.us.sentry.io`.
2697 #[serde(default)]
2698 pub sentry: bool,
2699
2700 /// Sentry reporting URL, if a custom one is desired.
2701 ///
2702 /// display: sensitive
2703 /// default: ""
2704 #[serde(default = "default_sentry_endpoint")]
2705 pub sentry_endpoint: Option<Url>,
2706
2707 /// Report your tuwunel server_name in Sentry.io crash reports and
2708 /// metrics.
2709 #[serde(default)]
2710 pub sentry_send_server_name: bool,
2711
2712 /// Performance monitoring/tracing sample rate for Sentry.io.
2713 ///
2714 /// Note that too high values may impact performance, and can be disabled by
2715 /// setting it to 0.0 (0%) This value is read as a percentage to Sentry,
2716 /// represented as a decimal. Defaults to 15% of traces (0.15)
2717 ///
2718 /// default: 0.15
2719 #[serde(default = "default_sentry_traces_sample_rate")]
2720 pub sentry_traces_sample_rate: f32,
2721
2722 /// Whether to attach a stacktrace to Sentry reports.
2723 #[serde(default)]
2724 pub sentry_attach_stacktrace: bool,
2725
2726 /// Send panics to Sentry. This is true by default, but Sentry has to be
2727 /// enabled. The global `sentry` config option must be enabled to send any
2728 /// data.
2729 #[serde(default = "true_fn")]
2730 pub sentry_send_panic: bool,
2731
2732 /// Send errors to sentry. This is true by default, but sentry has to be
2733 /// enabled. This option is only effective in release-mode; forced to false
2734 /// in debug-mode.
2735 #[serde(default = "true_fn")]
2736 pub sentry_send_error: bool,
2737
2738 /// Controls the tracing log level for Sentry to send things like
2739 /// breadcrumbs and transactions
2740 ///
2741 /// default: "info"
2742 #[serde(default = "default_sentry_filter")]
2743 pub sentry_filter: String,
2744
2745 /// Enable the tokio-console. This option is only relevant to developers.
2746 ///
2747 /// For more information, see:
2748 /// https://tuwunel.chat/development.html#debugging-with-tokio-console
2749 #[serde(default)]
2750 pub tokio_console: bool,
2751
2752 /// Arbitrary argument vector for integration testing. Functionality in the
2753 /// server is altered or informed for the requirements of integration tests.
2754 /// - "smoke" performs a shutdown after startup admin commands rather than
2755 /// hanging on client handling.
2756 ///
2757 /// display: hidden
2758 #[serde(default)]
2759 pub test: BTreeSet<String>,
2760
2761 /// Indicates the server has started in maintenance mode. Historically
2762 /// maintenance mode has been enabled by the command line argument
2763 /// `--maintenance` which then sets various configuration items such as
2764 /// `listening=false` among others. That is still the case. This option was
2765 /// only added as a single source of truth that `--maintenance` mode is
2766 /// active.
2767 ///
2768 /// This option must never be set manually.
2769 ///
2770 /// display: hidden
2771 #[serde(default)]
2772 pub maintenance: bool,
2773
2774 /// Controls whether admin room notices like account registrations, password
2775 /// changes, account deactivations, room directory publications, etc will be
2776 /// sent to the admin room. Update notices and normal admin command
2777 /// responses will still be sent.
2778 /// reloadable: yes
2779 #[serde(default = "true_fn")]
2780 pub admin_room_notices: bool,
2781
2782 /// Save original events before applying redaction to them.
2783 ///
2784 /// They can be retrieved with `admin debug get-retained-pdu` or MSC2815.
2785 ///
2786 /// reloadable: yes
2787 /// default: true
2788 #[serde(default = "true_fn")]
2789 pub save_unredacted_events: bool,
2790
2791 /// Redaction retention period in seconds.
2792 ///
2793 /// By default the unredacted events are stored for 60 days.
2794 ///
2795 /// reloadable: yes
2796 /// default: 5184000
2797 #[serde(default = "default_redaction_retention_seconds")]
2798 pub redaction_retention_seconds: u64,
2799
2800 /// Allows users with `redact` power level to request unredacted events with
2801 /// MSC2815.
2802 ///
2803 /// Server admins can request unredacted events regardless of the value of
2804 /// this option.
2805 ///
2806 /// reloadable: yes
2807 /// default: true
2808 #[serde(default = "true_fn")]
2809 pub allow_room_admins_to_request_unredacted_events: bool,
2810
2811 /// Prevents local users from sending redactions.
2812 ///
2813 /// This check does not apply to server admins.
2814 /// reloadable: yes
2815 #[serde(default)]
2816 pub disable_local_redactions: bool,
2817
2818 /// Enable database pool affinity support. On supporting systems, block
2819 /// device queue topologies are detected and the request pool is optimized
2820 /// for the hardware; db_pool_workers is determined automatically.
2821 ///
2822 /// default: true
2823 #[serde(default = "true_fn")]
2824 pub db_pool_affinity: bool,
2825
2826 /// Sets the number of worker threads in the frontend-pool of the database.
2827 /// This number should reflect the I/O capabilities of the system,
2828 /// such as the queue-depth or the number of simultaneous requests in
2829 /// flight. Defaults to 32 times the number of CPU cores.
2830 ///
2831 /// Note: This value is only used if db_pool_affinity is disabled or not
2832 /// detected on the system, otherwise it is determined automatically.
2833 ///
2834 /// default: 32
2835 #[serde(default = "default_db_pool_workers")]
2836 pub db_pool_workers: usize,
2837
2838 /// When db_pool_affinity is enabled and detected, the size of any worker
2839 /// group will not exceed the determined value. This is necessary when
2840 /// thread-pooling approach does not scale to the full capabilities of
2841 /// high-end hardware; using detected values without limitation could
2842 /// degrade performance.
2843 ///
2844 /// The value is multiplied by the number of cores which share a device
2845 /// queue, since group workers can be scheduled on any of those cores.
2846 ///
2847 /// default: 64
2848 #[serde(default = "default_db_pool_workers_limit")]
2849 pub db_pool_workers_limit: usize,
2850
2851 /// Limits the total number of workers across all worker groups. When the
2852 /// sum of all groups exceeds this value the worker counts are reduced until
2853 /// this constraint is satisfied.
2854 ///
2855 /// By default this value is only effective on larger systems (e.g. 16+
2856 /// cores) where it will tamper the overall thread-count. The thread-pool
2857 /// model will never achieve hardware capacity but this value can be raised
2858 /// on huge systems if the scheduling overhead is determined to not
2859 /// bottleneck and the worker groups are divided too small.
2860 ///
2861 /// default: 2048
2862 #[serde(default = "default_db_pool_max_workers")]
2863 pub db_pool_max_workers: usize,
2864
2865 /// Determines the size of the queues feeding the database's frontend-pool.
2866 /// The size of the queue is determined by multiplying this value with the
2867 /// number of pool workers. When this queue is full, tokio tasks conducting
2868 /// requests will yield until space is available; this is good for
2869 /// flow-control by avoiding buffer-bloat, but can inhibit throughput if
2870 /// too low.
2871 ///
2872 /// default: 4
2873 #[serde(default = "default_db_pool_queue_mult")]
2874 pub db_pool_queue_mult: usize,
2875
2876 /// Sets the initial value for the concurrency of streams. This value simply
2877 /// allows overriding the default in the code. The default is 32, which is
2878 /// the same as the default in the code. Note this value is itself
2879 /// overridden by the computed stream_width_scale, unless that is disabled;
2880 /// this value can serve as a fixed-width instead.
2881 ///
2882 /// default: 32
2883 #[serde(default = "default_stream_width_default")]
2884 pub stream_width_default: usize,
2885
2886 /// Scales the stream width starting from a base value detected for the
2887 /// specific system. The base value is the database pool worker count
2888 /// determined from the hardware queue size (e.g. 32 for SSD or 64 or 128+
2889 /// for NVMe). This float allows scaling the width up or down by multiplying
2890 /// it (e.g. 1.5, 2.0, etc). The maximum result can be the size of the pool
2891 /// queue (see: db_pool_queue_mult) as any larger value will stall the tokio
2892 /// task. The value can also be scaled down (e.g. 0.5) to improve
2893 /// responsiveness for many users at the cost of throughput for each.
2894 ///
2895 /// Setting this value to 0.0 causes the stream width to be fixed at the
2896 /// value of stream_width_default. The default scale is 1.0 to match the
2897 /// capabilities detected for the system.
2898 ///
2899 /// default: 1.0
2900 #[serde(default = "default_stream_width_scale")]
2901 pub stream_width_scale: f32,
2902
2903 /// Sets the initial amplification factor. This controls batch sizes of
2904 /// requests made by each pool worker, multiplying the throughput of each
2905 /// stream. This value is somewhat abstract from specific hardware
2906 /// characteristics and can be significantly larger than any thread count or
2907 /// queue size. This is because each database query may require several
2908 /// index lookups, thus many database queries in a batch may make progress
2909 /// independently while also sharing index and data blocks which may or may
2910 /// not be cached. It is worthwhile to submit huge batches to reduce
2911 /// complexity. The maximum value is 32768, though sufficient hardware is
2912 /// still advised for that.
2913 ///
2914 /// default: 1024
2915 #[serde(default = "default_stream_amplification")]
2916 pub stream_amplification: usize,
2917
2918 /// Number of sender task workers; determines sender parallelism. Default is
2919 /// '0' which means the value is determined internally, likely matching the
2920 /// number of tokio worker-threads or number of cores, etc. Override by
2921 /// setting a non-zero value.
2922 ///
2923 /// default: 0
2924 #[serde(default)]
2925 pub sender_workers: usize,
2926
2927 /// Enables listener sockets; can be set to false to disable listening. This
2928 /// option is intended for developer/diagnostic purposes only.
2929 #[serde(default = "true_fn")]
2930 pub listening: bool,
2931
2932 /// Enables configuration reload when the server receives SIGUSR1 on
2933 /// supporting platforms.
2934 ///
2935 /// reloadable: yes
2936 /// default: true
2937 #[serde(default = "true_fn")]
2938 pub config_reload_signal: bool,
2939
2940 /// Toggles ignore checking/validating TLS certificates
2941 ///
2942 /// This applies to everything, including URL previews, federation requests,
2943 /// etc. This is a hidden argument that should NOT be used in production as
2944 /// it is highly insecure and I will personally yell at you if I catch you
2945 /// using this.
2946 #[serde(default)]
2947 pub allow_invalid_tls_certificates: bool,
2948
2949 /// Sets the `Access-Control-Allow-Origin` header included by this server in
2950 /// all responses. A list of multiple values can be specified. The default
2951 /// is an empty list. The actual header defaults to `*` upon an empty list.
2952 ///
2953 /// There is no reason to configure this without specific intent. Incorrect
2954 /// values may degrade or disrupt clients.
2955 ///
2956 /// default: []
2957 #[serde(default)]
2958 pub access_control_allow_origin: BTreeSet<String>,
2959
2960 /// Backport state-reset security fixes to all room versions.
2961 ///
2962 /// This option applies the State Resolution 2.1 mitigation developed during
2963 /// project Hydra for room version 12 to all prior State Resolution 2.0 room
2964 /// versions (all room versions supported by this server). These mitigations
2965 /// increase resilience to state-resets without any new definition of
2966 /// correctness; therefor it is safe to set this to true for existing rooms.
2967 ///
2968 /// Furthermore, state-reset attacks are not consistent as they result in
2969 /// rooms without any single consensus, therefor it is unnecessary to set
2970 /// this to false to match other servers which set this to false or simply
2971 /// lack support; even if replicating the post-reset state suffered by other
2972 /// servers is somehow desired.
2973 ///
2974 /// This option exists for developer and debug use, and as a failsafe in
2975 /// lieu of hardcoding it.
2976 /// reloadable: yes
2977 #[serde(default = "true_fn")]
2978 pub hydra_backports: bool,
2979
2980 /// Delete rooms when the last user from this server leaves. This feature is
2981 /// experimental and for the purpose of least-surprise is not enabled by
2982 /// default but can be enabled for deployments interested in conserving
2983 /// space. It may eventually default to true in a future release.
2984 ///
2985 /// Note that not all pathways which can remove the last local user
2986 /// currently invoke this operation, so in some cases you may find the room
2987 /// still exists.
2988 ///
2989 /// reloadable: yes
2990 /// default: false
2991 #[serde(default)]
2992 pub delete_rooms_after_leave: bool,
2993
2994 /// Limits the number of One Time Keys per device (not per-algorithm). The
2995 /// reference implementation maintains 50 OTK's at any given time, therefor
2996 /// our default is at least five times that. There is no known reason for an
2997 /// administrator to adjust this value; it is provided here rather than
2998 /// hardcoding it.
2999 ///
3000 /// reloadable: yes
3001 /// default: 256
3002 #[serde(default = "default_one_time_key_limit")]
3003 pub one_time_key_limit: usize,
3004
3005 /// (EXPERIMENTAL) Setting this option to true replaces the list of identity
3006 /// providers displayed on a client's login page with a single button "Sign
3007 /// in with single sign-on" linking to the URL
3008 /// `/_matrix/client/v3/login/sso/redirect`. All configured providers are
3009 /// attempted for authorization. All authorizations associate with the same
3010 /// Matrix user. NOTE: All authorizations must succeed, as there is no
3011 /// reliable way to skip a provider.
3012 ///
3013 /// This option is disabled by default, allowing the client to list
3014 /// configured providers and permitting privacy-conscious users to authorize
3015 /// only their choice.
3016 ///
3017 /// Note that fluffychat always displays a single button anyway. You do not
3018 /// need to enable this to use fluffychat; instead we offer a
3019 /// default-provider option, see `default` in the provider config section.
3020 /// reloadable: yes
3021 #[serde(default)]
3022 pub single_sso: bool,
3023
3024 /// Setting this option to true replaces the list of identity providers on
3025 /// the client's login screen with a single button "Sign in with single
3026 /// sign-on" linking to the URL `/_matrix/client/v3/login/sso/redirect`. The
3027 /// deployment is expected to intercept this URL with their reverse-proxy to
3028 /// provide a custom webpage listing providers; each entry linking or
3029 /// redirecting back to one of the configured identity providers at
3030 /// /_matrix/client/v3/login/sso/redirect/<client_id>`.
3031 ///
3032 /// This option defaults to false, allowing the client to generate the list
3033 /// of providers or hide all SSO-related options when none configured.
3034 /// reloadable: yes
3035 #[serde(default)]
3036 pub sso_custom_providers_page: bool,
3037
3038 /// From MSC3824:
3039 /// > If the client finds oauth_aware_preferred to be true then, assuming it
3040 /// > supports that auth type, it should present this as the only
3041 /// > login/registration method available to the user.
3042 /// reloadable: yes
3043 #[serde(default, alias = "sso_aware_preferred")]
3044 pub oidc_aware_preferred: bool,
3045
3046 /// Directory containing appservice yaml registration files.
3047 ///
3048 /// default: ""
3049 #[serde(default)]
3050 pub appservice_dir: Option<PathBuf>,
3051
3052 /// Skip database migration on startup. This option is intended for
3053 /// developer debugging and testing only. Never set this option to false
3054 /// unless you have been instructed to do so. Setting this option to false
3055 /// may cause permanent damage and permanent loss of data.
3056 ///
3057 /// Any new database migrations will not be applied on startup, and the
3058 /// database schema version will not be adjusted. These migrations and
3059 /// schema changes may be expected by the current codebase but may not be
3060 /// available when this option is set to false.
3061 ///
3062 /// Setting this option to false will have no effect if no new migrations
3063 /// are to be applied. New migrations are applied once during any execution
3064 /// where this option is set to true (which is the default).
3065 #[serde(default = "true_fn")]
3066 pub database_migrations: bool,
3067
3068 /// Open a database whose schema version is newer than this build supports.
3069 ///
3070 /// A database reporting a higher schema version than this build is normally
3071 /// refused, since opening it stamps the schema down to this build's version
3072 /// and may permanently lose data written by the newer build. Setting this
3073 /// to true overrides that refusal: the database opens, one-time migrations
3074 /// run, and the schema is stamped down to this build's version.
3075 ///
3076 /// It has no effect when the discovered version is at or below this build's
3077 /// version, where migrations apply normally either way. It is also not
3078 /// needed to import a Conduit database or a fork of conduwuit; those are
3079 /// recognized by lineage and open without it.
3080 ///
3081 /// This option is extremely dangerous and intended for developer debugging
3082 /// and testing only. Never set it unless you have been instructed to do so;
3083 /// it may cause permanent damage and permanent loss of data.
3084 #[serde(default)]
3085 pub force_migration: bool,
3086
3087 /// When importing a Conduit database in place, the filesystem path to
3088 /// Conduit's media directory. Leave unset to use `<database_path>/media`,
3089 /// which is Conduit's own default location.
3090 ///
3091 /// example: "/var/lib/matrix-conduit/media"
3092 pub conduit_source_media_path: Option<PathBuf>,
3093
3094 /// When importing a Conduit database, the sharding depth of Conduit's media
3095 /// directory (0 for a flat directory). Must match the importing Conduit's
3096 /// `media.directory_structure`; the default matches Conduit's own default
3097 /// of `Deep { length = 2, depth = 2 }`.
3098 ///
3099 /// default: 2
3100 #[serde(default = "default_conduit_media_directory_depth")]
3101 pub conduit_media_directory_depth: u8,
3102
3103 /// When importing a Conduit database, the shard-segment length of Conduit's
3104 /// media directory. Paired with `conduit_media_directory_depth`.
3105 ///
3106 /// default: 2
3107 #[serde(default = "default_conduit_media_directory_length")]
3108 pub conduit_media_directory_length: u8,
3109
3110 /// When importing a Conduit database whose media lived in an S3 bucket
3111 /// rather than on disk, the name of a `[global.storage_provider.<name>]`
3112 /// entry to read the source originals from. Leave unset to read from the
3113 /// filesystem at `conduit_source_media_path`. Define the named provider
3114 /// with Conduit's own S3 credentials and set its `base_path` to Conduit's
3115 /// `media.path` prefix; the importer reads each content-addressed object
3116 /// using `conduit_media_directory_depth`/`length` for the key sharding.
3117 ///
3118 /// Scope `media_storage_providers` to your destination provider only (e.g.
3119 /// `["media"]`) so the import writes solely there; otherwise media is also
3120 /// copied back into the read-only source bucket.
3121 ///
3122 /// example: "conduit_source"
3123 pub conduit_source_media_provider: Option<String>,
3124
3125 /// Set this to true for excluding unencrypted rooms from the common-rooms
3126 /// calculation deciding the receivers of device list updates.
3127 ///
3128 /// Setting this to true can help performance on very large homeservers,
3129 /// but it may not be spec compliant and risky for client expectations.
3130 /// reloadable: yes
3131 #[serde(default)]
3132 pub device_key_update_encrypted_rooms_only: bool,
3133
3134 // external structure; separate section
3135 #[serde(default)]
3136 pub storage_provider: BTreeMap<String, StorageProvider>,
3137
3138 // external structure; separate section
3139 #[serde(default)]
3140 pub registration_terms: BTreeMap<String, TermsPolicy>,
3141
3142 // external structure; separate section
3143 #[serde(default)]
3144 pub ldap: LdapConfig,
3145
3146 // external structure; separate section
3147 #[serde(default)]
3148 pub jwt: JwtConfig,
3149
3150 // external structure; separate section
3151 #[serde(default)]
3152 pub smtp: SmtpConfig,
3153
3154 // external structure; separate section
3155 #[serde(default)]
3156 pub appservice: BTreeMap<String, AppService>,
3157
3158 // external structure; separate sections
3159 #[serde(default, with = "identity_provider_serde")]
3160 pub identity_provider: BTreeMap<String, IdentityProvider>,
3161
3162 #[serde(flatten)]
3163 #[expect(clippy::zero_sized_map_values)]
3164 // this is a catchall, the map shouldn't be zero at runtime
3165 catchall: BTreeMap<String, IgnoredAny>,
3166}
3167
3168#[derive(Clone, Debug, Deserialize, Default)]
3169#[config_example_generator(filename = "tuwunel-example.toml", section = "global.tls")]
3170pub struct TlsConfig {
3171 /// Path to a valid TLS certificate file.
3172 ///
3173 /// example: "/path/to/my/certificate.crt"
3174 pub certs: Option<String>,
3175
3176 /// Path to a valid TLS certificate private key.
3177 ///
3178 /// example: "/path/to/my/certificate.key"
3179 pub key: Option<String>,
3180
3181 /// Whether to listen and allow for HTTP and HTTPS connections (insecure!)
3182 #[serde(default)]
3183 pub dual_protocol: bool,
3184}
3185
3186#[expect(rustdoc::bare_urls)]
3187#[derive(Clone, Debug, Deserialize, Default)]
3188#[config_example_generator(
3189 filename = "tuwunel-example.toml",
3190 section = "global.well_known",
3191 ignore = "support_contact support_role support_email support_mxid support_page \
3192 support_pgp_key support_policy"
3193)]
3194pub struct WellKnownConfig {
3195 /// The server URL that the client well-known file will serve. This should
3196 /// not contain a port, and should just be a valid HTTPS URL.
3197 ///
3198 /// example: "https://matrix.example.com"
3199 pub client: Option<Url>,
3200
3201 /// The server base domain of the URL with a specific port that the server
3202 /// well-known file will serve. This should contain a port at the end, and
3203 /// should not be a URL.
3204 ///
3205 /// reloadable: yes
3206 /// example: "matrix.example.com:443"
3207 pub server: Option<OwnedServerName>,
3208
3209 // external structure; separate section
3210 #[serde(default)]
3211 pub support_contact: BTreeMap<String, SupportContact>,
3212
3213 // external structure; separate section
3214 #[serde(default)]
3215 pub support_policy: BTreeMap<String, SupportPolicy>,
3216
3217 /// The URL of the support web page. This and the below generate the content
3218 /// of `/.well-known/matrix/support`.
3219 ///
3220 /// example: "https://example.com/support"
3221 pub support_page: Option<Url>,
3222
3223 /// The name of the support role.
3224 ///
3225 ///
3226 /// display: hidden
3227 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
3228 // used instead. However for compatibility purposes the config option will still function and
3229 // be prioritised first.
3230 pub support_role: Option<ContactRole>,
3231
3232 /// The email address for the above support role.
3233 ///
3234 ///
3235 /// display: hidden
3236 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
3237 // used instead. However for compatibility purposes the config option will still function and
3238 // be prioritised first.
3239 pub support_email: Option<String>,
3240
3241 /// The Matrix User ID for the above support role.
3242 ///
3243 /// display: hidden
3244 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
3245 // used instead. However for compatibility purposes the config option will still function and
3246 // be prioritised first.
3247 pub support_mxid: Option<OwnedUserId>,
3248
3249 /// The PGP key (i.e. OpenPGP) that one may use for encrypted communications
3250 /// for the above support role. The value must be a URI. Use a web URL
3251 /// pointing to the key (for example "https://example.com/key.asc"), an
3252 /// OPENPGPKEY DNS record ("dns:..."), or a fingerprint carried with the
3253 /// "openpgp4fpr:" scheme. A bare fingerprint without a scheme, or raw
3254 /// inlined key material, is rejected at startup.
3255 ///
3256 /// As this is a spec proposal (MSC4439), the identifier/prefix for this
3257 /// field is currently "dev.zirco.msc4439.pgp_key"
3258 ///
3259 /// display: hidden
3260 // This config option is hidden because [global.well_known.support_contact.<ID>] should be
3261 // used instead. However for compatibility purposes the config option will still function and
3262 // be prioritised first.
3263 pub support_pgp_key: Option<String>,
3264
3265 /// LiveKit JWT endpoint.
3266 /// Required for Element Call / MatrixRTC (MSC4143).
3267 ///
3268 /// Note: You must also set `client` above to your homeserver URL.
3269 ///
3270 /// reloadable: yes
3271 /// default: ""
3272 #[serde(default)]
3273 pub livekit_url: Option<String>,
3274
3275 /// Custom MatrixRTC transports.
3276 ///
3277 /// If you're looking to setup Element Call / MatrixRTC with Livekit,
3278 /// you should not use this option and instead set `livekit_url`.
3279 /// This is only required if you want to configure a non-livekit MatrixRTC
3280 /// transport. There are no known client implementations that support any
3281 /// other transport types.
3282 ///
3283 /// This option was previously the only way to configure a Livekit
3284 /// transport. It has been superseded by `livekit_url`.
3285 ///
3286 /// Example:
3287 /// ```toml
3288 /// [global.well_known]
3289 /// client = "https://matrix.yourdomain.com"
3290 ///
3291 /// [[global.well_known.rtc_transports]]
3292 /// type = "livekit"
3293 /// livekit_service_url = "https://livekit.yourdomain.com"
3294 /// ```
3295 ///
3296 /// reloadable: yes
3297 /// default: []
3298 #[serde(default)]
3299 pub rtc_transports: Vec<serde_json::Value>,
3300}
3301
3302#[derive(Clone, Debug, Deserialize)]
3303#[config_example_generator(
3304 filename = "tuwunel-example.toml",
3305 section = "global.well_known.support_policy.<ID>",
3306 ignore = "policy_translation"
3307)]
3308pub struct SupportPolicy {
3309 /// Version string of the policy document.
3310 ///
3311 /// example: "v6.7"
3312 /// reloadable: yes
3313 pub version: String,
3314
3315 // external structure; separate section
3316 pub policy_translation: BTreeMap<String, SupportPolicyTranslation>,
3317}
3318
3319#[derive(Clone, Debug, Deserialize)]
3320#[config_example_generator(
3321 filename = "tuwunel-example.toml",
3322 section = "global.well_known.support_policy.<ID>.policy_translation.<LANG>"
3323)]
3324pub struct SupportPolicyTranslation {
3325 /// User friendly name of the policy document.
3326 ///
3327 /// example: "Privacy Policy"
3328 /// reloadable: yes
3329 pub name: String,
3330
3331 /// Link to the test of the policy document. A valid URL must be specified.
3332 ///
3333 /// example: "https://website.local/privacy-policy"
3334 /// reloadable: yes
3335 pub url: Url,
3336}
3337
3338#[derive(Clone, Debug, Deserialize)]
3339#[config_example_generator(
3340 filename = "tuwunel-example.toml",
3341 section = "global.registration_terms.<ID>",
3342 ignore = "translations"
3343)]
3344pub struct TermsPolicy {
3345 /// Version of this policy document, presented to the client. Configuring
3346 /// any `[global.registration_terms.<ID>]` block makes registration
3347 /// require an `m.login.terms` stage listing every such document; the
3348 /// `<ID>` is the policy id sent to clients.
3349 ///
3350 /// example: "1.2"
3351 /// reloadable: yes
3352 pub version: String,
3353
3354 // external structure; separate section
3355 pub translations: BTreeMap<String, TermsPolicyTranslation>,
3356}
3357
3358#[derive(Clone, Debug, Deserialize)]
3359#[config_example_generator(
3360 filename = "tuwunel-example.toml",
3361 section = "global.registration_terms.<ID>.translations.<LANG>"
3362)]
3363pub struct TermsPolicyTranslation {
3364 /// User friendly name of the policy document in this language.
3365 ///
3366 /// example: "Terms of Service"
3367 /// reloadable: yes
3368 pub name: String,
3369
3370 /// Link to the text of the policy document. Must be a valid http(s) URL.
3371 ///
3372 /// example: "https://example.org/terms-1.2-en.html"
3373 /// reloadable: yes
3374 pub url: Url,
3375}
3376
3377impl From<SupportPolicyTranslation>
3378 for ruma::api::identity_service::tos::get_terms_of_service::v2::LocalizedPolicy
3379{
3380 fn from(conf: SupportPolicyTranslation) -> Self {
3381 Self {
3382 name: conf.name,
3383 url: conf.url.to_string(),
3384 }
3385 }
3386}
3387
3388#[derive(Clone, Debug, Deserialize)]
3389#[config_example_generator(
3390 filename = "tuwunel-example.toml",
3391 section = "global.well_known.support_contact.<ID>"
3392)]
3393pub struct SupportContact {
3394 /// The name of the support role.
3395 ///
3396 /// example: "m.role.admin"
3397 pub role: ContactRole,
3398
3399 /// The email address for the above support role.
3400 ///
3401 /// example: "admin@example.com"
3402 pub email_address: Option<String>,
3403
3404 /// The Matrix User ID for the above support role.
3405 ///
3406 /// example "@admin:example.com"
3407 pub matrix_id: Option<OwnedUserId>,
3408
3409 /// The PGP key (i.e. OpenPGP) that one may use for encrypted communications
3410 /// for the above support role. The value must be a URI. Use a web URL
3411 /// pointing to the key (for example "https://example.com/key.asc"), an
3412 /// OPENPGPKEY DNS record ("dns:..."), or a fingerprint carried with the
3413 /// "openpgp4fpr:" scheme. A bare fingerprint without a scheme, or raw
3414 /// inlined key material, is rejected at startup.
3415 ///
3416 /// As this is a spec proposal (MSC4439), the identifier/prefix for this
3417 /// field is currently "dev.zirco.msc4439.pgp_key"
3418 ///
3419 /// example: "openpgp4fpr:8B77919975EAFA5E2456EE03665FE73077489DB0"
3420 pub pgp_key: Option<String>,
3421}
3422
3423impl From<SupportContact> for ruma::api::client::discovery::discover_support::Contact {
3424 fn from(conf: SupportContact) -> Self {
3425 Self {
3426 role: conf.role,
3427 matrix_id: conf.matrix_id,
3428 email_address: conf.email_address,
3429 pgp_key: conf.pgp_key,
3430 }
3431 }
3432}
3433
3434#[derive(Clone, Debug, Default, Deserialize)]
3435#[config_example_generator(filename = "tuwunel-example.toml", section = "global.ldap")]
3436pub struct LdapConfig {
3437 /// Whether to enable LDAP login.
3438 ///
3439 /// reloadable: yes
3440 /// example: "true"
3441 #[serde(default)]
3442 pub enable: bool,
3443
3444 /// URI of the LDAP server.
3445 ///
3446 /// reloadable: yes
3447 /// example: "ldap://ldap.example.com:389"
3448 pub uri: Option<Url>,
3449
3450 /// Root of the searches.
3451 ///
3452 /// reloadable: yes
3453 /// example: "ou=users,dc=example,dc=org"
3454 ///
3455 /// default:
3456 #[serde(default)]
3457 pub base_dn: String,
3458
3459 /// Bind DN if anonymous search is not enabled.
3460 ///
3461 /// You can use the variable `{username}` that will be replaced by the
3462 /// entered username. In such case, the password used to bind will be the
3463 /// one provided for the login and not the one given by
3464 /// `bind_password_file`. Beware: automatically granting admin rights will
3465 /// not work if you use this direct bind instead of a LDAP search.
3466 ///
3467 /// reloadable: yes
3468 /// example: "cn=ldap-reader,dc=example,dc=org" or
3469 /// "cn={username},ou=users,dc=example,dc=org"
3470 ///
3471 /// default: ""
3472 #[serde(default)]
3473 pub bind_dn: Option<String>,
3474
3475 /// Path to a file on the system that contains the password for the
3476 /// `bind_dn`.
3477 ///
3478 /// The server must be able to access the file, and it must not be empty.
3479 ///
3480 /// reloadable: yes
3481 /// default: ""
3482 #[serde(default)]
3483 pub bind_password_file: Option<PathBuf>,
3484
3485 /// Search filter to limit user searches.
3486 ///
3487 /// You can use the variable `{username}` that will be replaced by the
3488 /// entered username for more complex filters.
3489 ///
3490 /// reloadable: yes
3491 /// example: "(&(objectClass=person)(memberOf=matrix))"
3492 ///
3493 /// default: "(objectClass=*)"
3494 #[serde(default = "default_ldap_search_filter")]
3495 pub filter: String,
3496
3497 /// Attribute to use to uniquely identify the user.
3498 ///
3499 /// reloadable: yes
3500 /// example: "uid" or "cn"
3501 ///
3502 /// default: "uid"
3503 #[serde(default = "default_ldap_uid_attribute")]
3504 pub uid_attribute: String,
3505
3506 /// Attribute containing the distinguished name of the user.
3507 ///
3508 /// reloadable: yes
3509 /// example: "givenName" or "sn"
3510 ///
3511 /// default: "givenName"
3512 #[serde(default = "default_ldap_name_attribute")]
3513 pub name_attribute: String,
3514
3515 /// Root of the searches for admin users.
3516 ///
3517 /// Defaults to `base_dn` if empty.
3518 ///
3519 /// reloadable: yes
3520 /// example: "ou=admins,dc=example,dc=org"
3521 ///
3522 /// default:
3523 #[serde(default)]
3524 pub admin_base_dn: String,
3525
3526 /// The LDAP search filter to find administrative users for tuwunel.
3527 ///
3528 /// If left blank, administrative state must be configured manually for each
3529 /// user.
3530 ///
3531 /// You can use the variable `{username}` that will be replaced by the
3532 /// entered username for more complex filters.
3533 ///
3534 /// reloadable: yes
3535 /// example: "(objectClass=tuwunelAdmin)" or "(uid={username})"
3536 ///
3537 /// default:
3538 #[serde(default)]
3539 pub admin_filter: String,
3540}
3541
3542#[derive(Clone, Debug, Default, Deserialize)]
3543#[config_example_generator(filename = "tuwunel-example.toml", section = "global.jwt")]
3544pub struct JwtConfig {
3545 /// Enable JWT logins
3546 ///
3547 /// reloadable: yes
3548 /// default: false
3549 #[serde(default)]
3550 pub enable: bool,
3551
3552 /// Validation key, also called 'secret' in Synapse config. The type of key
3553 /// can be configured in 'format', but defaults to the common HMAC which
3554 /// is a plaintext shared-secret, so you should keep this value private.
3555 ///
3556 /// display: sensitive
3557 /// reloadable: yes
3558 /// default:
3559 #[serde(default, alias = "secret")]
3560 pub key: String,
3561
3562 /// Format of the 'key'. Only HMAC, ECDSA, and B64HMAC are supported
3563 /// Binary keys cannot be pasted into this config, so B64HMAC is an
3564 /// alternative to HMAC for properly random secret strings.
3565 /// - HMAC is a plaintext shared-secret private-key.
3566 /// - B64HMAC is a base64-encoded version of HMAC.
3567 /// - ECDSA is a PEM-encoded public-key.
3568 /// - EDDSA is a PEM-encoded Ed25519 public-key.
3569 ///
3570 /// reloadable: yes
3571 /// default: "HMAC"
3572 #[serde(default = "default_jwt_format")]
3573 pub format: String,
3574
3575 /// Automatically create new user from a valid claim, otherwise access is
3576 /// denied for an unknown even with an authentic token.
3577 ///
3578 /// reloadable: yes
3579 /// default: true
3580 #[serde(default = "true_fn")]
3581 pub register_user: bool,
3582
3583 /// JWT algorithm
3584 ///
3585 /// reloadable: yes
3586 /// default: "HS256"
3587 #[serde(default = "default_jwt_algorithm")]
3588 pub algorithm: String,
3589
3590 /// Optional audience claim list. The token must claim one or more values
3591 /// from this list when set.
3592 ///
3593 /// reloadable: yes
3594 /// default: []
3595 #[serde(default)]
3596 pub audience: Vec<String>,
3597
3598 /// Optional issuer claim list. The token must claim one or more values
3599 /// from this list when set.
3600 ///
3601 /// reloadable: yes
3602 /// default: []
3603 #[serde(default)]
3604 pub issuer: Vec<String>,
3605
3606 /// Require expiration claim in the token. This defaults to false for
3607 /// synapse migration compatibility.
3608 ///
3609 /// reloadable: yes
3610 /// default: false
3611 #[serde(default)]
3612 pub require_exp: bool,
3613
3614 /// Require not-before claim in the token. This defaults to false for
3615 /// synapse migration compatibility.
3616 ///
3617 /// reloadable: yes
3618 /// default: false
3619 #[serde(default)]
3620 pub require_nbf: bool,
3621
3622 /// Validate expiration time of the token when present. Whether or not it is
3623 /// required depends on require_exp, but when present this ensures the token
3624 /// is not used after a time.
3625 ///
3626 /// reloadable: yes
3627 /// default: true
3628 #[serde(default = "true_fn")]
3629 pub validate_exp: bool,
3630
3631 /// Validate not-before time of the token when present. Whether or not it is
3632 /// required depends on require_nbf, but when present this ensures the token
3633 /// is not used before a time.
3634 ///
3635 /// reloadable: yes
3636 /// default: true
3637 #[serde(default = "true_fn")]
3638 pub validate_nbf: bool,
3639
3640 /// Bypass validation for diagnostic/debug use only.
3641 ///
3642 /// reloadable: yes
3643 /// default: true
3644 #[serde(default = "true_fn")]
3645 pub validate_signature: bool,
3646}
3647
3648#[derive(Clone, Debug, Default, Deserialize)]
3649#[config_example_generator(filename = "tuwunel-example.toml", section = "global.smtp")]
3650pub struct SmtpConfig {
3651 /// Connection URL for the outbound SMTP relay used to send email
3652 /// verification messages. Setting this enables the email subsystem;
3653 /// without it no mail is sent.
3654 ///
3655 /// Use a `smtp://` URL for an unencrypted or STARTTLS connection and a
3656 /// `smtps://` URL for implicit TLS. Credentials and the host go inline:
3657 /// `smtps://user:pass@host:port`. The port defaults per scheme when
3658 /// omitted.
3659 ///
3660 /// The userinfo component is URL-encoded, so an `@` inside the username
3661 /// must be written as `%40` (for example a login of `bot@example.com`
3662 /// becomes `smtps://bot%40example.com:pass@host:465`). Other reserved
3663 /// characters in the username or password are percent-encoded the same
3664 /// way.
3665 ///
3666 /// example: "smtps://user:pass@mail.example.com:465"
3667 pub connection_uri: Option<String>,
3668
3669 /// The mailbox that outbound verification messages are sent from. Accepts
3670 /// either a bare address or a display-name form.
3671 ///
3672 /// example: "Example <noreply@example.com>"
3673 pub sender: Option<String>,
3674
3675 /// Require a verified email address to complete registration. When set,
3676 /// the registration flow does not finish until the user proves control of
3677 /// an email address.
3678 ///
3679 /// default: false
3680 #[serde(default)]
3681 pub require_email_for_registration: bool,
3682
3683 /// Require a verified email address when registering with a registration
3684 /// token. When set, token-based registration also demands a verified
3685 /// email address.
3686 ///
3687 /// default: false
3688 #[serde(default)]
3689 pub require_email_for_token_registration: bool,
3690}
3691
3692#[derive(Clone, Debug, Deserialize)]
3693#[config_example_generator(
3694 filename = "tuwunel-example.toml",
3695 section = "[global.identity_provider]"
3696)]
3697pub struct IdentityProvider {
3698 /// The brand-name of the service (e.g. Apple, Facebook, GitHub, GitLab,
3699 /// Google) or the software (e.g. keycloak, MAS) providing the identity.
3700 /// When a brand is recognized we apply certain defaults to this config
3701 /// for your convenience. For certain brands we apply essential internal
3702 /// workarounds specific to that provider; it is important to configure this
3703 /// field properly when a provider needs to be recognized (like GitHub for
3704 /// example).
3705 ///
3706 /// Several configured providers can share the same brand name. It is not
3707 /// case-sensitive. As a convenience for common simple deployments we can
3708 /// identify this provider by brand in addition to the unique `client_id` if
3709 /// and only if there is a single provider for the brand; see notes for
3710 /// `client_id`.
3711 #[serde(deserialize_with = "utils::string::de::to_lowercase")]
3712 pub brand: String,
3713
3714 /// The ID of your OAuth application which the provider generates upon
3715 /// registration. This ID then uniquely identifies this configuration
3716 /// instance itself, becoming the identity provider's ID and must be unique
3717 /// and remain unchanged.
3718 ///
3719 /// As a convenience we also identify this config by `brand` if and only if
3720 /// there is a single provider configured for a `brand`. Note carefully that
3721 /// multiple providers configured with the same `brand` is not an error and
3722 /// this provider will simply not be found when querying by `brand`.
3723 pub client_id: String,
3724
3725 /// Secret key the provider generated for you along with the `client_id`
3726 /// above. Unlike the `client_id`, the `client_secret` can be changed here
3727 /// whenever the provider regenerates one for you.
3728 ///
3729 /// display: sensitive
3730 pub client_secret: Option<String>,
3731
3732 /// Secret key to use, read from the file path specified.
3733 ///
3734 /// Alternative to `client_secret` for deployments that prefer to keep the
3735 /// secret outside the config file. When both are configured `client_secret`
3736 /// is used and this field is ignored. The file is read at startup and on
3737 /// each OAuth exchange, must exist and must be non-empty; leading and
3738 /// trailing whitespace is trimmed. Under systemd the path must be visible
3739 /// to the service after sandboxing (`ReadWritePaths` / `ProtectHome`),
3740 /// typically by placing the file under `/etc/tuwunel/`.
3741 ///
3742 /// example: "/etc/tuwunel/.client_secret"
3743 pub client_secret_file: Option<PathBuf>,
3744
3745 /// Issuer URL the provider publishes for you. We have pre-supplied default
3746 /// values for some of the canonical public providers, making this field
3747 /// optional based on the `brand` set above. Otherwise it is required to
3748 /// find self-hosted providers. It must be identical to what is configured
3749 /// and expected by the provider and must never change because we associate
3750 /// identities to it. If the `/.well-known/openid-configuration` is not
3751 /// found behind this URL see `base_path` below as a workaround.
3752 pub issuer_url: Option<Url>,
3753
3754 /// The callback URL configured when registering the OAuth application with
3755 /// the provider. Tuwunel's callback URL must be strictly formatted exactly
3756 /// as instructed. The URL host must point directly at the matrix server and
3757 /// use the following path:
3758 /// `/_matrix/client/unstable/login/sso/callback/<client_id>` where
3759 /// `<client_id>` is the same one configured for this provider above.
3760 pub callback_url: Option<Url>,
3761
3762 /// When more than one identity_provider has been configured and
3763 /// `single_sso` is false and `sso_custom_providers_page` is false this will
3764 /// determine the behavior of the `/_matrix/client/v3/login/sso/redirect`
3765 /// endpoint (note the url lacks a trailing `client_id`).
3766 ///
3767 /// When only one identity_provider is configured it will be interpreted
3768 /// as the default and this does not need to be set. Otherwise a default
3769 /// *must* be selected for some clients (e.g. fluffychat) to work properly
3770 /// when the above conditions require it. To operate out-of-the-box we
3771 /// default to one configured provider if none are explicitly default; a
3772 /// warning will be logged on startup for this condition.
3773 ///
3774 /// (EXPERIMENTAL) Multiple providers can be set to default. All providers
3775 /// configured with this option set to `true` will associate with the same
3776 /// Matrix account when a client flows through
3777 /// `/_matrix/client/v3/login/sso/redirect`.
3778 ///
3779 /// When a user authorizes any provider configured default, the flow will
3780 /// include all other providers configured default as well for association.
3781 /// NOTE: authorization must succeed for ALL default providers.
3782 #[serde(default)]
3783 pub default: bool,
3784
3785 /// Optional display-name for this provider instance seen on the login page
3786 /// by users. It defaults to `brand`. When configuring multiple providers
3787 /// using the same `brand` this can be set to distinguish them.
3788 pub name: Option<String>,
3789
3790 /// Optional icon for the provider. The canonical providers have a default
3791 /// icon based on the `brand` supplied above when this is not supplied. Note
3792 /// that it uses an MXC url which is curious in the auth-media era and may
3793 /// not be reliable.
3794 pub icon: Option<OwnedMxcUri>,
3795
3796 /// Optional list of scopes to authorize. An empty array does not impose any
3797 /// restrictions from here, effectively defaulting to all scopes you
3798 /// configured for the OAuth application at the provider. This setting
3799 /// allows for restricting to a subset of those scopes for this instance.
3800 /// Note the user can further restrict scopes during their authorization.
3801 ///
3802 /// default: []
3803 #[serde(default)]
3804 pub scope: BTreeSet<String>,
3805
3806 /// Optional list of userinfo claims which shape and restrict the way we
3807 /// compute a Matrix UserId for new registrations. Reviewing Tuwunel's
3808 /// documentation will be necessary for a complete description in detail. An
3809 /// empty array imposes no restriction here, avoiding generated fallbacks as
3810 /// much as possible.
3811 ///
3812 /// For simplicity we reserve a claim called "unique" which can be listed
3813 /// alone to ensure *only* generated ID's are used for registrations.
3814 ///
3815 /// Note that listing the claim "sub" has special significance and will take
3816 /// precedence over all other claims, listed or unlisted. "sub" is not
3817 /// normally used to determine a UserId unless explicitly listed here.
3818 ///
3819 /// As of now arbitrary claims cannot be listed here, we only recognize
3820 /// specific hard-coded claims.
3821 ///
3822 /// default: []
3823 #[serde(default)]
3824 pub userid_claims: BTreeSet<String>,
3825
3826 /// Trusted providers can cause username conflicts (i.e. account hijacking)
3827 /// but this is precisely how an existing matrix account can be associated
3828 /// with a provider. When this option is set to true, the way we compute a
3829 /// Matrix UserId from userinfo claims is inverted: we find the first
3830 /// matching user and grant access to it. Whereas by default, when set to
3831 /// false, we skip matching users and register the first available username;
3832 /// falling-back to random characters to avoid conflicts.
3833 ///
3834 /// Only set this option to true for providers you self-host and control.
3835 /// Never set this option to true for the public providers such as GitHub,
3836 /// GitLab, etc.
3837 ///
3838 /// Note that associating an existing user with an untrusted provider is
3839 /// still possible but only with the command '!admin query oauth associate'.
3840 ///
3841 /// default: false
3842 #[serde(default)]
3843 pub trusted: bool,
3844
3845 /// Setting this option to false will inhibit unique ID's from being
3846 /// generated as a last-resort when determining a UserId from a provider's
3847 /// claims. In the case of untrusted providers, when all provided claims
3848 /// conflict with existing user accounts, a unique fallback ID needs
3849 /// to be generated for registration to not be denied with an error.
3850 ///
3851 /// Set this option to false if you operate a private server or a trusted
3852 /// identity provider where random UserId's are undesirable; the result of a
3853 /// misconfiguration or other issue where an error is warranted.
3854 ///
3855 /// This option should be set to true for public servers or some users may
3856 /// never be able to register.
3857 ///
3858 /// default: true
3859 #[serde(default = "true_fn")]
3860 pub unique_id_fallbacks: bool,
3861
3862 /// Controls whether new user registration is possible from this provider.
3863 /// When this option is set to false, authorizations from this provider
3864 /// only affect existing users and will never result in a new registration
3865 /// when the claims fail to match any existing user (in the case of trusted
3866 /// providers) or an available username is found (in the case of untrusted
3867 /// providers).
3868 ///
3869 /// Setting this option to false is generally not useful unless there is
3870 /// an explicit reason to do so.
3871 ///
3872 /// default: true
3873 #[serde(default = "true_fn")]
3874 pub registration: bool,
3875
3876 /// Optional extra path components after the issuer_url leading to the
3877 /// location of the `.well-known` directory used for discovery. If the path
3878 /// starts with a slash it will be treated as absolute, meaning overwriting
3879 /// any path in the issuer_url. The path needs to end with a slash. This
3880 /// will be empty for specification-compliant providers.
3881 pub base_path: Option<String>,
3882
3883 /// Overrides the `.well-known` location where the provider's openid
3884 /// configuration is found. It is very unlikely you will need to set this;
3885 /// available for developers or special purposes only.
3886 pub discovery_url: Option<Url>,
3887
3888 /// Overrides the authorize URL requested during the grant phase. This is
3889 /// generally discovered or derived automatically, but may be required as a
3890 /// workaround for any non-standard or undiscoverable provider.
3891 pub authorization_url: Option<Url>,
3892
3893 /// Overrides the access token URL; the same caveats apply as with the other
3894 /// URL overrides.
3895 pub token_url: Option<Url>,
3896
3897 /// Overrides the revocation URL; the same caveats apply as with the other
3898 /// URL overrides.
3899 pub revocation_url: Option<Url>,
3900
3901 /// Overrides the introspection URL; the same caveats apply as with the
3902 /// other URL overrides.
3903 pub introspection_url: Option<Url>,
3904
3905 /// Overrides the userinfo URL; the same caveats apply as with the other URL
3906 /// overrides.
3907 pub userinfo_url: Option<Url>,
3908
3909 /// Whether to perform discovery and adjust this provider's configuration
3910 /// accordingly. This defaults to true. When true, it is an error when
3911 /// discovery fails and authorizations will not be attempted to the
3912 /// provider.
3913 #[serde(default = "true_fn")]
3914 pub discovery: bool,
3915
3916 /// The duration in seconds before a grant authorization session expires.
3917 ///
3918 /// default: 300
3919 #[serde(default = "default_sso_grant_session_duration")]
3920 pub grant_session_duration: Option<u64>,
3921
3922 /// Whether to check the redirect cookie during the callback. This is a
3923 /// security feature and should remain enabled. This is available for
3924 /// developers or deployments which cannot tolerate cookies and are willing
3925 /// to tolerate the risks.
3926 ///
3927 /// default: true
3928 #[serde(default = "true_fn")]
3929 pub check_cookie: bool,
3930
3931 /// Extra query parameters appended to every authorization request sent to
3932 /// the identity provider.
3933 ///
3934 /// E.g. to force re-authentication even if IdP cookies are present:
3935 /// ```toml
3936 /// [[global.identity_provider]]
3937 /// extra_authorization_parameters = { prompt = "login" }
3938 /// ```
3939 ///
3940 /// default: {}
3941 #[serde(default)]
3942 pub extra_authorization_parameters: BTreeMap<String, String>,
3943
3944 /// Forward the MSC3824 `action` query parameter from the SSO redirect
3945 /// endpoints to this provider as an OpenID Connect `prompt` value.
3946 ///
3947 /// When a client appends `action=register` to a `/login/sso/redirect`
3948 /// request the upstream authorization request carries `prompt=create`
3949 /// (the OpenID Connect "Initiating User Registration" extension) so the
3950 /// provider can present its registration screen. `action=login` is left
3951 /// unforwarded to avoid forcing a re-authentication, and a `prompt` set in
3952 /// `extra_authorization_parameters` still applies in that case. An
3953 /// action-derived `prompt` takes precedence over one configured there.
3954 ///
3955 /// Leave this disabled unless the provider supports the `prompt=create`
3956 /// registration extension; a provider that does not may reject or ignore
3957 /// the request.
3958 ///
3959 /// default: false
3960 #[serde(default)]
3961 pub forward_action_prompt: bool,
3962}
3963
3964impl IdentityProvider {
3965 #[must_use]
3966 pub fn id(&self) -> &str { self.client_id.as_str() }
3967
3968 pub async fn get_client_secret(&self) -> Result<String> {
3969 if let Some(client_secret) = &self.client_secret {
3970 return Ok(client_secret.clone());
3971 }
3972
3973 let Some(client_secret_file) = &self.client_secret_file else {
3974 return Err!("No client secret or client secret file configured");
3975 };
3976
3977 let client_secret = tokio::fs::read_to_string(client_secret_file).await?;
3978
3979 Ok(client_secret.trim().to_owned())
3980 }
3981}
3982
3983#[derive(Clone, Debug, Default, Deserialize)]
3984pub enum StorageProvider {
3985 #[expect(non_camel_case_types)]
3986 local(StorageProviderLocal),
3987 #[expect(non_camel_case_types)]
3988 #[serde(rename = "s3", alias = "S3")]
3989 s3(Box<StorageProviderS3>),
3990 #[default]
3991 None,
3992}
3993
3994#[derive(Clone, Debug, Default, Deserialize)]
3995#[config_example_generator(
3996 filename = "tuwunel-example.toml",
3997 section = "global.storage_provider.<ID>.local"
3998)]
3999pub struct StorageProviderLocal {
4000 /// Absolute path to this local filesystem storage provider. Technically the
4001 /// provider exists at the filesystem root, and the base_path is prefixed to
4002 /// all objects.
4003 #[serde(alias = "path")]
4004 pub base_path: String,
4005
4006 /// Creates the directory on the local filesystem if missing. This is not
4007 /// recommended to prevent misconfigured environments and missing mounts
4008 /// from silently succeeding.
4009 #[serde(default)]
4010 pub create_if_missing: bool,
4011
4012 /// Toggles the preservation of a directory after its last file contents are
4013 /// removed.
4014 #[serde(default = "true_fn")]
4015 pub delete_empty_directories: bool,
4016
4017 /// Enables checks performed at startup determining the usability of the
4018 /// local directory. Failures will abort the server's startup.
4019 ///
4020 /// default: true
4021 #[serde(default = "true_fn")]
4022 pub startup_check: bool,
4023}
4024
4025#[derive(Clone, Debug, Default, Deserialize)]
4026#[config_example_generator(
4027 filename = "tuwunel-example.toml",
4028 section = "global.storage_provider.<ID>.s3"
4029)]
4030pub struct StorageProviderS3 {
4031 /// Supply an s3 URL e.g. "s3://bucket/path". These URLs may contain one
4032 /// or all of `bucket`, `region`, and `path` . When not supplied, such
4033 /// additional items can be supplied below individually.
4034 pub url: Option<String>,
4035
4036 /// The name of the S3 bucket. e.g. "bucketname-123456789-us-west-2-an".
4037 pub bucket: Option<String>,
4038
4039 /// The region of the S3 bucket. e.g. "us-west-2".
4040 ///
4041 /// default: "us-east-1"
4042 pub region: Option<String>,
4043
4044 /// Your amazon IAM Key ID with access granted to this bucket.
4045 /// e.g. "ABCDEFG1X1ZZYYXXWWVV"
4046 #[debug("{}", redacted_debug!(key))]
4047 pub key: Option<String>,
4048
4049 /// The secret key component which is approx 40 characters of base64.
4050 ///
4051 /// default:
4052 /// display: sensitive
4053 #[serde(skip_serializing)]
4054 #[debug("{}", redacted_debug!(secret))]
4055 pub secret: Option<String>,
4056
4057 /// Optional path prefix within the bucket where all our operations will
4058 /// take place.
4059 #[serde(alias = "path")]
4060 pub base_path: Option<String>,
4061
4062 /// (expert use) Override the location of s3 applied after components of the
4063 /// parsed `url` (or when none set).
4064 pub endpoint: Option<String>,
4065
4066 /// (expert use) Override this property useful for some self-hosted
4067 /// environments. By default it is derived when parsing the primary `url`.
4068 #[serde(default)]
4069 pub use_vhost_request: Option<bool>,
4070
4071 /// (expert use) Alternative session-token authentication method.
4072 ///
4073 /// display: sensitive
4074 /// default:
4075 #[serde(skip_serializing)]
4076 #[debug("{}", redacted_debug!(token))]
4077 pub token: Option<String>,
4078
4079 /// (expert use) Associated SSE-KMS key material.
4080 ///
4081 /// display: sensitive
4082 #[debug("{}", redacted_debug!(kms))]
4083 pub kms: Option<String>,
4084
4085 /// (expert use) When configured for the bucket it should be reflected here.
4086 pub use_bucket_key: Option<bool>,
4087
4088 /// (expert use) Threshold size for switching to Multi-part uploads. This is
4089 /// a quirk of the S3 protocol which requires us to use a different approach
4090 /// for "large" uploads. This value determines what a "large" upload is. The
4091 /// default value should be sufficient for most providers. The value is a
4092 /// parsed string allowing SI or IEC units for convenience.
4093 ///
4094 /// default: 100 MiB
4095 #[serde(default = "default_multipart_threshold")]
4096 pub multipart_threshold: ByteSize,
4097
4098 /// (expert use) Size of each individual part within a Multi-part upload.
4099 /// Once an upload exceeds `multipart_threshold` the payload is split into
4100 /// parts of this size, each sent as a separate HTTP PUT. Smaller values
4101 /// keep individual requests under per-request timeouts on slow uplinks at
4102 /// the cost of more round-trips. S3 requires every part except the last
4103 /// to be at least 5 MiB. The value is a parsed string allowing SI or IEC
4104 /// units for convenience.
4105 ///
4106 /// default: 10 MiB
4107 #[serde(default = "default_multipart_part_size")]
4108 pub multipart_part_size: ByteSize,
4109
4110 /// (developer use) Allows relaxing default requirement forcing HTTPS.
4111 ///
4112 /// default: true
4113 #[serde(default = "some_true_fn")]
4114 pub use_https: Option<bool>,
4115
4116 /// (developer_use) Allows skipping request header signatures (will be
4117 /// reejected by AWS).
4118 ///
4119 /// default: true
4120 #[serde(default = "some_true_fn")]
4121 pub use_signatures: Option<bool>,
4122
4123 /// (developer_use) Allows disabling request payload signatures.
4124 ///
4125 /// default: true
4126 #[serde(default = "some_true_fn")]
4127 pub use_payload_signatures: Option<bool>,
4128
4129 /// (developer use) Enables checks performed at startup such as pinging the
4130 /// provider. Failures are considered critical startup errors which abort
4131 /// startup. When set to false, faulty providers are only discovered with
4132 /// first use and will not be fatal errors.
4133 ///
4134 /// Only set this to false if you expect a provider to be down at startup or
4135 /// for development/testing purposes; checks are disabled when the server
4136 /// is started in '--maintenance' mode.
4137 ///
4138 /// default: true
4139 #[serde(default = "true_fn")]
4140 pub startup_check: bool,
4141}
4142
4143#[derive(Clone, Debug, Default, Deserialize)]
4144#[config_example_generator(
4145 filename = "tuwunel-example.toml",
4146 section = "global.appservice.<ID>",
4147 ignore = "id users aliases rooms"
4148)]
4149pub struct AppService {
4150 #[serde(default)]
4151 pub id: String,
4152
4153 /// The URL for the application service.
4154 ///
4155 /// Optionally set to `null` if no traffic is required.
4156 pub url: Option<String>,
4157
4158 /// A unique token for application services to use to authenticate requests
4159 /// to Homeservers.
4160 ///
4161 /// default:
4162 /// display: sensitive
4163 pub as_token: String,
4164
4165 /// A unique token for Homeservers to use to authenticate requests to
4166 /// application services.
4167 ///
4168 /// default:
4169 /// display: sensitive
4170 pub hs_token: String,
4171
4172 /// The localpart of the user associated with the application service.
4173 pub sender_localpart: Option<String>,
4174
4175 /// Events which are sent from certain users.
4176 #[serde(default)]
4177 pub users: Vec<AppServiceNamespace>,
4178
4179 /// Events which are sent in rooms with certain room aliases.
4180 #[serde(default)]
4181 pub aliases: Vec<AppServiceNamespace>,
4182
4183 /// Events which are sent in rooms with certain room IDs.
4184 #[serde(default)]
4185 pub rooms: Vec<AppServiceNamespace>,
4186
4187 /// Whether requests from masqueraded users are rate-limited.
4188 ///
4189 /// The sender is excluded.
4190 #[serde(default)]
4191 pub rate_limited: bool,
4192
4193 /// The external protocols which the application service provides (e.g.
4194 /// IRC).
4195 ///
4196 /// default: []
4197 #[serde(default)]
4198 pub protocols: Vec<String>,
4199
4200 /// Whether the application service wants to receive ephemeral data.
4201 ///
4202 /// default: false
4203 #[serde(default)]
4204 pub receive_ephemeral: bool,
4205
4206 /// Whether the application service wants to do device management, as part
4207 /// of MSC4190.
4208 ///
4209 /// default: false
4210 #[serde(default)]
4211 pub device_management: bool,
4212}
4213
4214impl From<AppService> for ruma::api::appservice::Registration {
4215 fn from(conf: AppService) -> Self {
4216 use ruma::api::appservice::Namespaces;
4217
4218 let sender_localpart = conf
4219 .sender_localpart
4220 .unwrap_or_else(|| conf.id.clone());
4221
4222 Self {
4223 id: conf.id,
4224 url: conf.url,
4225 as_token: conf.as_token,
4226 hs_token: conf.hs_token,
4227 receive_ephemeral: conf.receive_ephemeral,
4228 device_management: conf.device_management,
4229 protocols: conf.protocols.into(),
4230 rate_limited: conf.rate_limited.into(),
4231 sender_localpart,
4232 namespaces: Namespaces {
4233 users: conf.users.into_iter().map(Into::into).collect(),
4234 aliases: conf.aliases.into_iter().map(Into::into).collect(),
4235 rooms: conf.rooms.into_iter().map(Into::into).collect(),
4236 },
4237 }
4238 }
4239}
4240
4241#[derive(Clone, Debug, Default, Deserialize)]
4242#[config_example_generator(
4243 filename = "tuwunel-example.toml",
4244 section = "[global.appservice.<ID>.<users|rooms|aliases>]"
4245)]
4246pub struct AppServiceNamespace {
4247 /// Whether this application service has exclusive access to events within
4248 /// this namespace.
4249 #[serde(default)]
4250 pub exclusive: bool,
4251
4252 /// A regular expression defining which values this namespace includes.
4253 pub regex: String,
4254}
4255
4256impl From<AppServiceNamespace> for ruma::api::appservice::Namespace {
4257 fn from(conf: AppServiceNamespace) -> Self {
4258 Self {
4259 exclusive: conf.exclusive,
4260 regex: conf.regex,
4261 }
4262 }
4263}
4264
4265/// Items matched here will not generate an "unknown to tuwunel" warning when
4266/// configured. This is important for environment variables which share the
4267/// `TUWUNEL_` prefix namespace but aren't config items; match them here in
4268/// their split+lowercased format.
4269static KNOWN_KEYS: &[&str; 2] = &["^config$", "^runtime_[a-z0-9_]+$"];
4270
4271/// Items listed here generate a deprecation warning when configured.
4272static DEPRECATED_KEYS: &[&str; 9] = &[
4273 "cache_capacity",
4274 "conduit_cache_capacity_modifier",
4275 "max_concurrent_requests",
4276 "well_known_client",
4277 "well_known_server",
4278 "well_known_support_page",
4279 "well_known_support_role",
4280 "well_known_support_email",
4281 "well_known_support_mxid",
4282];
4283
4284impl Config {
4285 /// Pre-initialize config
4286 pub fn load<'a, I>(paths: I) -> Result<Figment>
4287 where
4288 I: Iterator<Item = &'a Path>,
4289 {
4290 let envs = [
4291 Env::var("CONDUIT_CONFIG"),
4292 Env::var("CONDUWUIT_CONFIG"),
4293 Env::var("TUWUNEL_CONFIG"),
4294 ];
4295
4296 let toml_files = envs
4297 .iter()
4298 .flatten()
4299 .map(PathBuf::from)
4300 .chain(paths.map(Path::to_path_buf))
4301 .collect_vec();
4302
4303 let invalid_toml_files = toml_files
4304 .iter()
4305 .filter(|path| !path.exists())
4306 .map(|path| path.clone().into_os_string())
4307 .collect_vec();
4308
4309 if !invalid_toml_files.is_empty() {
4310 return Err!(
4311 "The following config files do not exist or have broken symlinks: \
4312 {invalid_toml_files:?}"
4313 );
4314 }
4315
4316 let config = toml_files
4317 .iter()
4318 .map(Toml::file)
4319 .map(Data::nested)
4320 .fold(Figment::new(), Figment::merge)
4321 .merge(Env::prefixed("CONDUIT_").global().split("__"))
4322 .merge(Env::prefixed("CONDUWUIT_").global().split("__"))
4323 .merge(Env::prefixed("TUWUNEL_").global().split("__"));
4324
4325 Ok(config)
4326 }
4327
4328 /// Finalize config
4329 pub fn new(raw_config: &Figment) -> Result<Self> {
4330 let config = raw_config
4331 .extract::<Self>()
4332 .map_err(|e| err!("There was a problem with your configuration file: {e}"))?;
4333
4334 Ok(config)
4335 }
4336
4337 pub fn check(&self) -> Result { check(self) }
4338}
4339
4340impl TlsConfig {
4341 #[must_use]
4342 pub fn get_tls_cert_key(&self) -> Option<(&Path, &Path)> {
4343 let cert = self.certs.as_ref()?;
4344
4345 let cert = Path::new(cert);
4346
4347 let key = self.key.as_ref()?; // this cannot fail, aborts startup on cert.is_some ^ key.is_some
4348
4349 let key = Path::new(key);
4350
4351 Some((cert, key))
4352 }
4353}
4354
4355fn true_fn() -> bool { true }
4356
4357fn default_policy_server_request_timeout() -> u64 { 5 }
4358
4359fn some_true_fn() -> Option<bool> { Some(true) }
4360
4361#[cfg(test)]
4362fn default_server_name() -> OwnedServerName { ruma::owned_server_name!("localhost") }
4363
4364fn default_database_path() -> PathBuf { "/var/lib/tuwunel".to_owned().into() }
4365
4366fn default_conduit_media_directory_depth() -> u8 { 2 }
4367
4368fn default_conduit_media_directory_length() -> u8 { 2 }
4369
4370fn default_port() -> ListeningPort { ListeningPort { ports: Left(8008) } }
4371
4372fn default_unix_socket_perms() -> u32 { 660 }
4373
4374fn default_database_backups_to_keep() -> i16 { 1 }
4375
4376fn default_db_write_buffer_capacity_mb() -> f64 { 48.0 + parallelism_scaled_f64(4.0) }
4377
4378fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
4379
4380fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
4381
4382fn default_cache_capacity_modifier() -> f64 { 1.0 }
4383
4384fn default_auth_chain_cache_capacity() -> u32 {
4385 parallelism_scaled_u32(400_000).saturating_add(1_500_000)
4386}
4387
4388fn default_shorteventid_cache_capacity() -> u32 {
4389 parallelism_scaled_u32(200_000).saturating_add(400_000)
4390}
4391
4392fn default_eventidshort_cache_capacity() -> u32 {
4393 parallelism_scaled_u32(100_000).saturating_add(400_000)
4394}
4395
4396fn default_eventid_pdu_cache_capacity() -> u32 {
4397 parallelism_scaled_u32(100_000).saturating_add(400_000)
4398}
4399
4400fn default_shortstatekey_cache_capacity() -> u32 {
4401 parallelism_scaled_u32(4_000).saturating_add(97_000)
4402}
4403
4404fn default_statekeyshort_cache_capacity() -> u32 {
4405 parallelism_scaled_u32(4_000).saturating_add(97_000)
4406}
4407
4408fn default_servernameevent_data_cache_capacity() -> u32 {
4409 parallelism_scaled_u32(60_000).saturating_add(470_000)
4410}
4411
4412fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(100) }
4413
4414fn default_spacehierarchy_cache_ttl_min() -> u64 { 60 * 60 * 3 }
4415
4416fn default_spacehierarchy_cache_ttl_max() -> u64 { 60 * 60 * 18 }
4417
4418fn default_dns_cache_entries() -> u32 { 32768 }
4419
4420fn default_dns_min_ttl() -> u64 { 60 * 180 }
4421
4422fn default_dns_min_ttl_nxdomain() -> u64 { 60 * 60 * 24 * 3 }
4423
4424fn default_dns_attempts() -> u16 { 10 }
4425
4426fn default_dns_timeout() -> u64 { 10 }
4427
4428fn default_ip_lookup_strategy() -> u8 { 5 }
4429
4430fn default_max_request_size() -> usize { 24 * 1024 * 1024 }
4431
4432fn default_max_response_size() -> usize { 256 * 1024 * 1024 }
4433
4434fn default_max_pending_media_uploads() -> usize { 5 }
4435
4436fn default_media_create_unused_expiration_time() -> u64 { 86400 }
4437
4438fn default_media_rc_create_per_second() -> u32 { 10 }
4439
4440fn default_media_rc_create_burst_count() -> u32 { 50 }
4441
4442fn default_request_conn_timeout() -> u64 { 10 }
4443
4444fn default_request_timeout() -> u64 { 35 }
4445
4446fn default_request_total_timeout() -> u64 { 320 }
4447
4448fn default_request_idle_timeout() -> u64 { 5 }
4449
4450fn default_request_idle_per_host() -> u16 { 1 }
4451
4452fn default_well_known_conn_timeout() -> u64 { 6 }
4453
4454fn default_well_known_timeout() -> u64 { 10 }
4455
4456fn default_federation_timeout() -> u64 { 25 }
4457
4458fn default_federation_keys_timeout() -> u64 { 8 }
4459
4460fn default_federation_idle_timeout() -> u64 { 25 }
4461
4462fn default_federation_idle_per_host() -> u16 { 1 }
4463
4464fn default_sender_timeout() -> u64 { 180 }
4465
4466fn default_sender_idle_timeout() -> u64 { 180 }
4467
4468fn default_sender_retry_backoff_limit() -> u64 { 86400 }
4469
4470fn default_appservice_timeout() -> u64 { 35 }
4471
4472fn default_appservice_idle_timeout() -> u64 { 300 }
4473
4474fn default_pusher_idle_timeout() -> u64 { 15 }
4475
4476fn default_max_fetch_prev_events() -> u16 { 192_u16 }
4477
4478fn default_fetch_prev_wait_ms() -> u64 { 750 }
4479
4480fn default_tracing_flame_filter() -> String {
4481 cfg!(debug_assertions)
4482 .then_some("trace,h2=off")
4483 .unwrap_or("info")
4484 .to_owned()
4485}
4486
4487fn default_jaeger_filter() -> String {
4488 cfg!(debug_assertions)
4489 .then_some("trace,h2=off")
4490 .unwrap_or("info")
4491 .to_owned()
4492}
4493
4494fn default_tracing_flame_output_path() -> String { "./tracing.folded".to_owned() }
4495
4496fn default_trusted_servers() -> Vec<OwnedServerName> {
4497 vec![OwnedServerName::try_from("matrix.org").expect("valid ServerName")]
4498}
4499
4500/// do debug logging by default for debug builds
4501#[must_use]
4502pub fn default_log() -> String {
4503 cfg!(debug_assertions)
4504 .then_some("debug")
4505 .unwrap_or("info")
4506 .to_owned()
4507}
4508
4509#[must_use]
4510pub fn default_log_span_events() -> String { "none".into() }
4511
4512fn default_notification_push_path() -> String { "/_matrix/push/v1/notify".to_owned() }
4513
4514fn default_openid_token_ttl() -> u64 { 60 * 60 }
4515
4516fn default_login_token_ttl() -> u64 { 2 * 60 * 1000 }
4517
4518fn default_turn_ttl() -> u64 { 60 * 60 * 24 }
4519
4520fn default_presence_idle_timeout_s() -> u64 { 5 * 60 }
4521
4522fn default_presence_offline_timeout_s() -> u64 { 30 * 60 }
4523
4524fn default_typing_federation_timeout_s() -> u64 { 30 }
4525
4526fn default_typing_client_timeout_min_s() -> u64 { 15 }
4527
4528fn default_typing_client_timeout_max_s() -> u64 { 45 }
4529
4530fn default_rocksdb_recovery_mode() -> u8 { 1 }
4531
4532fn default_rocksdb_log_level() -> String { "error".to_owned() }
4533
4534fn default_rocksdb_log_time_to_roll() -> usize { 0 }
4535
4536fn default_rocksdb_max_log_files() -> usize { 3 }
4537
4538fn default_rocksdb_max_log_file_size() -> usize {
4539 // 4 megabytes
4540 4 * 1024 * 1024
4541}
4542
4543fn default_rocksdb_parallelism_threads() -> usize { 0 }
4544
4545fn default_rocksdb_compression_algo() -> String {
4546 cfg!(feature = "zstd_compression")
4547 .then_some("zstd")
4548 .unwrap_or("none")
4549 .to_owned()
4550}
4551
4552/// Default RocksDB compression level is 32767, which is internally read by
4553/// RocksDB as the default magic number and translated to the library's default
4554/// compression level as they all differ. See their `kDefaultCompressionLevel`.
4555#[expect(clippy::doc_markdown)]
4556fn default_rocksdb_compression_level() -> i32 { 32767 }
4557
4558/// Default RocksDB compression level is 32767, which is internally read by
4559/// RocksDB as the default magic number and translated to the library's default
4560/// compression level as they all differ. See their `kDefaultCompressionLevel`.
4561#[expect(clippy::doc_markdown)]
4562fn default_rocksdb_bottommost_compression_level() -> i32 { 32767 }
4563
4564fn default_rocksdb_stats_level() -> u8 { 1 }
4565
4566// I know, it's a great name
4567#[must_use]
4568#[inline]
4569pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
4570
4571fn default_ip_range_denylist() -> Vec<String> {
4572 vec![
4573 "127.0.0.0/8".to_owned(),
4574 "10.0.0.0/8".to_owned(),
4575 "172.16.0.0/12".to_owned(),
4576 "192.168.0.0/16".to_owned(),
4577 "100.64.0.0/10".to_owned(),
4578 "192.0.0.0/24".to_owned(),
4579 "169.254.0.0/16".to_owned(),
4580 "192.88.99.0/24".to_owned(),
4581 "198.18.0.0/15".to_owned(),
4582 "192.0.2.0/24".to_owned(),
4583 "198.51.100.0/24".to_owned(),
4584 "203.0.113.0/24".to_owned(),
4585 "224.0.0.0/4".to_owned(),
4586 "::1/128".to_owned(),
4587 "fe80::/10".to_owned(),
4588 "fc00::/7".to_owned(),
4589 "2001:db8::/32".to_owned(),
4590 "ff00::/8".to_owned(),
4591 "fec0::/10".to_owned(),
4592 ]
4593}
4594
4595fn default_url_preview_max_spider_size() -> usize {
4596 256_000 // 256KB
4597}
4598
4599fn default_new_user_displayname_suffix() -> String { "💕".to_owned() }
4600
4601fn default_sentry_endpoint() -> Option<Url> {
4602 let url = "https://8994b1762a6a95af9502a7900edabc4c@o4509498990067712.ingest.us.sentry.io/4509498993213440"
4603 .try_into()
4604 .expect("default sentry url is invalid");
4605
4606 Some(url)
4607}
4608
4609fn default_sentry_traces_sample_rate() -> f32 { 0.15 }
4610
4611fn default_sentry_filter() -> String { "info".to_owned() }
4612
4613fn default_startup_netburst_keep() -> i64 { 50 }
4614
4615fn default_admin_log_capture() -> String {
4616 cfg!(debug_assertions)
4617 .then_some("debug")
4618 .unwrap_or("info")
4619 .to_owned()
4620}
4621
4622fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
4623
4624#[expect(clippy::as_conversions, clippy::cast_precision_loss)]
4625fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
4626
4627fn parallelism_scaled_u32(val: u32) -> u32 {
4628 let val = val
4629 .try_into()
4630 .expect("failed to cast u32 to usize");
4631 parallelism_scaled(val)
4632 .try_into()
4633 .unwrap_or(u32::MAX)
4634}
4635
4636fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
4637
4638fn default_trusted_server_batch_size() -> usize { 192 }
4639
4640fn default_trusted_server_batch_concurrency() -> usize { 2 }
4641
4642fn default_db_pool_workers() -> usize {
4643 sys::available_parallelism()
4644 .saturating_mul(4)
4645 .clamp(32, 1024)
4646}
4647
4648fn default_db_pool_workers_limit() -> usize { 64 }
4649
4650fn default_db_pool_max_workers() -> usize { 2048 }
4651
4652fn default_db_pool_queue_mult() -> usize { 4 }
4653
4654fn default_stream_width_default() -> usize { 32 }
4655
4656fn default_stream_width_scale() -> f32 { 1.0 }
4657
4658fn default_stream_amplification() -> usize { 1024 }
4659
4660fn default_client_receive_timeout() -> u64 { 75 }
4661
4662fn default_client_request_timeout() -> u64 { 240 }
4663
4664fn default_client_response_timeout() -> u64 { 120 }
4665
4666fn default_client_shutdown_timeout() -> u64 { 15 }
4667
4668fn default_sender_shutdown_timeout() -> u64 { 5 }
4669
4670fn default_ldap_search_filter() -> String { "(objectClass=*)".to_owned() }
4671
4672fn default_ldap_uid_attribute() -> String { String::from("uid") }
4673
4674fn default_ldap_name_attribute() -> String { String::from("givenName") }
4675
4676fn default_jwt_algorithm() -> String { "HS256".to_owned() }
4677
4678fn default_jwt_format() -> String { "HMAC".to_owned() }
4679
4680fn default_client_sync_timeout_min() -> u64 { 5000 }
4681
4682fn default_client_sync_timeout_default() -> u64 { 30000 }
4683
4684fn default_client_sync_timeout_max() -> u64 { 90000 }
4685
4686fn default_access_token_ttl() -> u64 { 604_800 }
4687
4688fn default_refresh_token_reuse_grace() -> u64 { 15 }
4689
4690fn default_deprioritize_joins_through_servers() -> RegexSet {
4691 RegexSet::new([r"matrix\.org"]).expect("valid set of regular expressions")
4692}
4693
4694fn default_one_time_key_limit() -> usize { 256 }
4695
4696fn default_max_make_join_attempts_per_join_attempt() -> usize { 48 }
4697
4698fn default_max_join_attempts_per_join_request() -> usize { 3 }
4699
4700fn default_sso_grant_session_duration() -> Option<u64> { Some(300) }
4701
4702fn default_redaction_retention_seconds() -> u64 { 5_184_000 }
4703
4704fn default_media_storage_providers() -> BTreeSet<String> { ["media".to_owned()].into() }
4705
4706fn default_multipart_threshold() -> ByteSize { ByteSize::mib(100) }
4707
4708fn default_multipart_part_size() -> ByteSize { ByteSize::mib(10) }