Skip to main content

tuwunel_core/config/
mod.rs

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