diff --git a/src/admin/server/commands.rs b/src/admin/server/commands.rs index 8d3358a8..6469a0e9 100644 --- a/src/admin/server/commands.rs +++ b/src/admin/server/commands.rs @@ -22,8 +22,8 @@ pub(super) async fn uptime(&self) -> Result { pub(super) async fn show_config(&self) -> Result { // Construct and send the response Ok(RoomMessageEventContent::text_markdown(format!( - "```\n{}\n```", - self.services.globals.config + "{}", + self.services.server.config ))) } diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 5cfed0b9..d6983540 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -3,7 +3,6 @@ pub mod proxy; use std::{ collections::{BTreeMap, BTreeSet, HashSet}, - fmt, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, path::PathBuf, }; @@ -15,7 +14,6 @@ use either::{ }; use figment::providers::{Env, Format, Toml}; pub use figment::{value::Value as FigmentValue, Figment}; -use itertools::Itertools; use regex::RegexSet; use ruma::{ api::client::discovery::discover_support::ContactRole, OwnedRoomOrAliasId, OwnedServerName, @@ -1859,405 +1857,6 @@ impl Config { pub fn check(&self) -> Result<(), Error> { check(self) } } -impl fmt::Display for Config { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Active config values:\n").expect("wrote line to formatter stream"); - let mut line = |key: &str, val: &str| { - writeln!(f, "{key}: {val}").expect("wrote line to formatter stream"); - }; - - line("Server name", self.server_name.host()); - line("Database path", &self.database_path.to_string_lossy()); - line( - "Database backup path", - self.database_backup_path - .as_ref() - .map_or("", |path| path.to_str().unwrap_or("")), - ); - line("Database backups to keep", &self.database_backups_to_keep.to_string()); - line("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string()); - line("Cache capacity modifier", &self.cache_capacity_modifier.to_string()); - line("PDU cache capacity", &self.pdu_cache_capacity.to_string()); - line("Auth chain cache capacity", &self.auth_chain_cache_capacity.to_string()); - line("Short eventid cache capacity", &self.shorteventid_cache_capacity.to_string()); - line("Eventid short cache capacity", &self.eventidshort_cache_capacity.to_string()); - line("Short statekey cache capacity", &self.shortstatekey_cache_capacity.to_string()); - line("Statekey short cache capacity", &self.statekeyshort_cache_capacity.to_string()); - line( - "Server visibility cache capacity", - &self.server_visibility_cache_capacity.to_string(), - ); - line( - "User visibility cache capacity", - &self.user_visibility_cache_capacity.to_string(), - ); - line("Stateinfo cache capacity", &self.stateinfo_cache_capacity.to_string()); - line( - "Roomid space hierarchy cache capacity", - &self.roomid_spacehierarchy_cache_capacity.to_string(), - ); - line("DNS cache entry limit", &self.dns_cache_entries.to_string()); - line("DNS minimum TTL", &self.dns_min_ttl.to_string()); - line("DNS minimum NXDOMAIN TTL", &self.dns_min_ttl_nxdomain.to_string()); - line("DNS attempts", &self.dns_attempts.to_string()); - line("DNS timeout", &self.dns_timeout.to_string()); - line("DNS fallback to TCP", &self.dns_tcp_fallback.to_string()); - line("DNS query over TCP only", &self.query_over_tcp_only.to_string()); - line("Query all nameservers", &self.query_all_nameservers.to_string()); - line("Maximum request size (bytes)", &self.max_request_size.to_string()); - line("Sender retry backoff limit", &self.sender_retry_backoff_limit.to_string()); - line("Request connect timeout", &self.request_conn_timeout.to_string()); - line("Request timeout", &self.request_timeout.to_string()); - line("Request total timeout", &self.request_total_timeout.to_string()); - line("Idle connections per host", &self.request_idle_per_host.to_string()); - line("Request pool idle timeout", &self.request_idle_timeout.to_string()); - line("Well_known connect timeout", &self.well_known_conn_timeout.to_string()); - line("Well_known timeout", &self.well_known_timeout.to_string()); - line("Federation timeout", &self.federation_timeout.to_string()); - line("Federation pool idle per host", &self.federation_idle_per_host.to_string()); - line("Federation pool idle timeout", &self.federation_idle_timeout.to_string()); - line("Sender timeout", &self.sender_timeout.to_string()); - line("Sender pool idle timeout", &self.sender_idle_timeout.to_string()); - line("Appservice timeout", &self.appservice_timeout.to_string()); - line("Appservice pool idle timeout", &self.appservice_idle_timeout.to_string()); - line("Pusher pool idle timeout", &self.pusher_idle_timeout.to_string()); - line("Allow registration", &self.allow_registration.to_string()); - line( - "Registration token", - if self.registration_token.is_none() - && self.registration_token_file.is_none() - && self.allow_registration - { - "not set (⚠️ open registration!)" - } else if self.registration_token.is_none() && self.registration_token_file.is_none() - { - "not set" - } else { - "set" - }, - ); - line( - "Registration token file path", - self.registration_token_file - .as_ref() - .map_or("", |path| path.to_str().unwrap_or_default()), - ); - line( - "Allow guest registration (inherently false if allow registration is false)", - &self.allow_guest_registration.to_string(), - ); - line( - "Log guest registrations in admin room", - &self.log_guest_registrations.to_string(), - ); - line( - "Allow guests to auto join rooms", - &self.allow_guests_auto_join_rooms.to_string(), - ); - line("New user display name suffix", &self.new_user_displayname_suffix); - line("Allow encryption", &self.allow_encryption.to_string()); - line("Allow federation", &self.allow_federation.to_string()); - line("Federation loopback", &self.federation_loopback.to_string()); - line( - "Require authentication for profile requests", - &self.require_auth_for_profile_requests.to_string(), - ); - line( - "Allow incoming federated presence requests (updates)", - &self.allow_incoming_presence.to_string(), - ); - line( - "Allow outgoing federated presence requests (updates)", - &self.allow_outgoing_presence.to_string(), - ); - line( - "Allow local presence requests (updates)", - &self.allow_local_presence.to_string(), - ); - line( - "Allow incoming remote read receipts", - &self.allow_incoming_read_receipts.to_string(), - ); - line( - "Allow outgoing remote read receipts", - &self.allow_outgoing_read_receipts.to_string(), - ); - line( - "Block non-admin room invites (local and remote, admins can still send and receive \ - invites)", - &self.block_non_admin_invites.to_string(), - ); - line("Enable admin escape commands", &self.admin_escape_commands.to_string()); - line( - "Activate admin console after startup", - &self.admin_console_automatic.to_string(), - ); - line("Execute admin commands after startup", &self.admin_execute.join(", ")); - line( - "Continue startup even if some commands fail", - &self.admin_execute_errors_ignore.to_string(), - ); - line("Filter for admin command log capture", &self.admin_log_capture); - line("Admin room tag", &self.admin_room_tag); - line("Allow outgoing federated typing", &self.allow_outgoing_typing.to_string()); - line("Allow incoming federated typing", &self.allow_incoming_typing.to_string()); - line( - "Incoming federated typing timeout", - &self.typing_federation_timeout_s.to_string(), - ); - line("Client typing timeout minimum", &self.typing_client_timeout_min_s.to_string()); - line("Client typing timeout maxmimum", &self.typing_client_timeout_max_s.to_string()); - line("Allow device name federation", &self.allow_device_name_federation.to_string()); - line( - "Allow incoming profile lookup federation requests", - &self - .allow_inbound_profile_lookup_federation_requests - .to_string(), - ); - line( - "Auto deactivate banned room join attempts", - &self.auto_deactivate_banned_room_attempts.to_string(), - ); - line("Notification push path", &self.notification_push_path); - line("Allow room creation", &self.allow_room_creation.to_string()); - line( - "Allow public room directory over federation", - &self.allow_public_room_directory_over_federation.to_string(), - ); - line( - "Allow public room directory without authentication", - &self.allow_public_room_directory_without_auth.to_string(), - ); - line( - "Lockdown public room directory (only allow admins to publish)", - &self.lockdown_public_room_directory.to_string(), - ); - line( - "Trusted key servers", - &self - .trusted_servers - .iter() - .map(|server| server.host()) - .join(", "), - ); - line("OpenID Token TTL", &self.openid_token_ttl.to_string()); - line( - "TURN username", - if self.turn_username.is_empty() { - "not set" - } else { - &self.turn_username - }, - ); - line("TURN password", { - if self.turn_password.is_empty() { - "not set" - } else { - "set" - } - }); - line("TURN secret", { - if self.turn_secret.is_empty() && self.turn_secret_file.is_none() { - "not set" - } else { - "set" - } - }); - line("TURN secret file path", { - self.turn_secret_file - .as_ref() - .map_or("", |path| path.to_str().unwrap_or_default()) - }); - line("Turn TTL", &self.turn_ttl.to_string()); - line("Turn URIs", { - let mut lst = Vec::with_capacity(self.turn_uris.len()); - for item in self.turn_uris.iter().cloned().enumerate() { - let (_, uri): (usize, String) = item; - lst.push(uri); - } - &lst.join(", ") - }); - line("Auto Join Rooms", { - let mut lst = Vec::with_capacity(self.auto_join_rooms.len()); - for room in &self.auto_join_rooms { - lst.push(room); - } - &lst.into_iter().join(", ") - }); - line("Zstd HTTP Compression", &self.zstd_compression.to_string()); - line("Gzip HTTP Compression", &self.gzip_compression.to_string()); - line("Brotli HTTP Compression", &self.brotli_compression.to_string()); - line("RocksDB database LOG level", &self.rocksdb_log_level); - line("RocksDB database LOG to stderr", &self.rocksdb_log_stderr.to_string()); - line("RocksDB database LOG time-to-roll", &self.rocksdb_log_time_to_roll.to_string()); - line("RocksDB Max LOG Files", &self.rocksdb_max_log_files.to_string()); - line( - "RocksDB database max LOG file size", - &self.rocksdb_max_log_file_size.to_string(), - ); - line( - "RocksDB database optimize for spinning disks", - &self.rocksdb_optimize_for_spinning_disks.to_string(), - ); - line("RocksDB Direct-IO", &self.rocksdb_direct_io.to_string()); - line("RocksDB Parallelism Threads", &self.rocksdb_parallelism_threads.to_string()); - line("RocksDB Compression Algorithm", &self.rocksdb_compression_algo); - line("RocksDB Compression Level", &self.rocksdb_compression_level.to_string()); - line( - "RocksDB Bottommost Compression Level", - &self.rocksdb_bottommost_compression_level.to_string(), - ); - line( - "RocksDB Bottommost Level Compression", - &self.rocksdb_bottommost_compression.to_string(), - ); - line("RocksDB Recovery Mode", &self.rocksdb_recovery_mode.to_string()); - line("RocksDB Repair Mode", &self.rocksdb_repair.to_string()); - line("RocksDB Read-only Mode", &self.rocksdb_read_only.to_string()); - line("RocksDB Secondary Mode", &self.rocksdb_secondary.to_string()); - line( - "RocksDB Compaction Idle Priority", - &self.rocksdb_compaction_prio_idle.to_string(), - ); - line( - "RocksDB Compaction Idle IOPriority", - &self.rocksdb_compaction_ioprio_idle.to_string(), - ); - line("RocksDB Compaction enabled", &self.rocksdb_compaction.to_string()); - line("RocksDB Statistics level", &self.rocksdb_stats_level.to_string()); - line("Media integrity checks on startup", &self.media_startup_check.to_string()); - line("Media compatibility filesystem links", &self.media_compat_file_link.to_string()); - line("Prune missing media from database", &self.prune_missing_media.to_string()); - line("Allow legacy (unauthenticated) media", &self.allow_legacy_media.to_string()); - line("Freeze legacy (unauthenticated) media", &self.freeze_legacy_media.to_string()); - line("Prevent Media Downloads From", { - let mut lst = Vec::with_capacity(self.prevent_media_downloads_from.len()); - for domain in &self.prevent_media_downloads_from { - lst.push(domain.host()); - } - &lst.join(", ") - }); - line("Forbidden Remote Server Names (\"Global\" ACLs)", { - let mut lst = Vec::with_capacity(self.forbidden_remote_server_names.len()); - for domain in &self.forbidden_remote_server_names { - lst.push(domain.host()); - } - &lst.join(", ") - }); - line("Forbidden Remote Room Directory Server Names", { - let mut lst = - Vec::with_capacity(self.forbidden_remote_room_directory_server_names.len()); - for domain in &self.forbidden_remote_room_directory_server_names { - lst.push(domain.host()); - } - &lst.join(", ") - }); - line("Outbound Request IP Range (CIDR) Denylist", { - let mut lst = Vec::with_capacity(self.ip_range_denylist.len()); - for item in self.ip_range_denylist.iter().cloned().enumerate() { - let (_, ip): (usize, String) = item; - lst.push(ip); - } - &lst.join(", ") - }); - line("Forbidden usernames", { - &self.forbidden_usernames.patterns().iter().join(", ") - }); - line("Forbidden room aliases", { - &self.forbidden_alias_names.patterns().iter().join(", ") - }); - line( - "URL preview bound interface", - self.url_preview_bound_interface - .as_ref() - .map(Either::as_ref) - .map(|either| either.map_left(ToString::to_string)) - .map(Either::either_into::) - .unwrap_or_default() - .as_str(), - ); - line( - "URL preview domain contains allowlist", - &self.url_preview_domain_contains_allowlist.join(", "), - ); - line( - "URL preview domain explicit allowlist", - &self.url_preview_domain_explicit_allowlist.join(", "), - ); - line( - "URL preview domain explicit denylist", - &self.url_preview_domain_explicit_denylist.join(", "), - ); - line( - "URL preview URL contains allowlist", - &self.url_preview_url_contains_allowlist.join(", "), - ); - line("URL preview maximum spider size", &self.url_preview_max_spider_size.to_string()); - line("URL preview check root domain", &self.url_preview_check_root_domain.to_string()); - line( - "Allow check for updates / announcements check", - &self.allow_check_for_updates.to_string(), - ); - line("Enable netburst on startup", &self.startup_netburst.to_string()); - #[cfg(feature = "sentry_telemetry")] - line("Sentry.io reporting and tracing", &self.sentry.to_string()); - #[cfg(feature = "sentry_telemetry")] - line("Sentry.io send server_name in logs", &self.sentry_send_server_name.to_string()); - #[cfg(feature = "sentry_telemetry")] - line("Sentry.io tracing sample rate", &self.sentry_traces_sample_rate.to_string()); - line("Sentry.io attach stacktrace", &self.sentry_attach_stacktrace.to_string()); - line("Sentry.io send panics", &self.sentry_send_panic.to_string()); - line("Sentry.io send errors", &self.sentry_send_error.to_string()); - line("Sentry.io tracing filter", &self.sentry_filter); - line( - "Well-known server name", - self.well_known - .server - .as_ref() - .map_or("", |server| server.as_str()), - ); - line( - "Well-known client URL", - self.well_known - .client - .as_ref() - .map_or("", |url| url.as_str()), - ); - line( - "Well-known support email", - self.well_known - .support_email - .as_ref() - .map_or("", |str| str.as_ref()), - ); - line( - "Well-known support Matrix ID", - self.well_known - .support_mxid - .as_ref() - .map_or("", |mxid| mxid.as_str()), - ); - line( - "Well-known support role", - self.well_known - .support_role - .as_ref() - .map_or("", |role| role.as_str()), - ); - line( - "Well-known support page/URL", - self.well_known - .support_page - .as_ref() - .map_or("", |url| url.as_str()), - ); - line("Enable the tokio-console", &self.tokio_console.to_string()); - line("Admin room notices", &self.admin_room_notices.to_string()); - - Ok(()) - } -} - fn true_fn() -> bool { true } fn default_address() -> ListeningAddr { diff --git a/src/macros/config.rs b/src/macros/config.rs index 452abd20..90d6ef15 100644 --- a/src/macros/config.rs +++ b/src/macros/config.rs @@ -1,8 +1,8 @@ use std::{collections::HashSet, fmt::Write as _, fs::OpenOptions, io::Write as _}; use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::ToTokens; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; use syn::{ parse::Parser, punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprLit, Field, Fields, FieldsNamed, ItemStruct, Lit, Meta, MetaList, MetaNameValue, Type, TypePath, @@ -19,18 +19,24 @@ const HIDDEN: &[&str] = &["default"]; #[allow(clippy::needless_pass_by_value)] pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result { - if is_cargo_build() && !is_cargo_test() { - generate_example(&input, args)?; - } + let write = is_cargo_build() && !is_cargo_test(); + let additional = generate_example(&input, args, write)?; - Ok(input.to_token_stream().into()) + Ok([input.to_token_stream(), additional] + .into_iter() + .collect::() + .into()) } #[allow(clippy::needless_pass_by_value)] #[allow(unused_variables)] -fn generate_example(input: &ItemStruct, args: &[Meta]) -> Result<()> { +fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result { let settings = get_simple_settings(args); + let section = settings.get("section").ok_or_else(|| { + Error::new(args[0].span(), "missing required 'section' attribute argument") + })?; + let filename = settings.get("filename").ok_or_else(|| { Error::new(args[0].span(), "missing required 'filename' attribute argument") })?; @@ -45,31 +51,33 @@ fn generate_example(input: &ItemStruct, args: &[Meta]) -> Result<()> { .split(' ') .collect(); - let section = settings.get("section").ok_or_else(|| { - Error::new(args[0].span(), "missing required 'section' attribute argument") - })?; - - let mut file = OpenOptions::new() + let fopts = OpenOptions::new() .write(true) .create(section == "global") .truncate(section == "global") .append(section != "global") - .open(filename) - .map_err(|e| { - Error::new( - Span::call_site(), - format!("Failed to open config file for generation: {e}"), - ) - })?; + .clone(); - if let Some(header) = settings.get("header") { - file.write_all(header.as_bytes()) + let mut file = write + .then(|| { + fopts.open(filename).map_err(|e| { + let msg = format!("Failed to open file for config generation: {e}"); + Error::new(Span::call_site(), msg) + }) + }) + .transpose()?; + + if let Some(file) = file.as_mut() { + if let Some(header) = settings.get("header") { + file.write_all(header.as_bytes()) + .expect("written to config file"); + } + + file.write_fmt(format_args!("\n[{section}]\n")) .expect("written to config file"); } - file.write_fmt(format_args!("\n[{section}]\n")) - .expect("written to config file"); - + let mut summary: Vec = Vec::new(); if let Fields::Named(FieldsNamed { named, .. }) = &input.fields { for field in named { let Some(ident) = &field.ident else { @@ -105,20 +113,41 @@ fn generate_example(input: &ItemStruct, args: &[Meta]) -> Result<()> { default }; - file.write_fmt(format_args!("\n{doc}")) - .expect("written to config file"); + if let Some(file) = file.as_mut() { + file.write_fmt(format_args!("\n{doc}")) + .expect("written to config file"); - file.write_fmt(format_args!("#{ident} ={default}\n")) + file.write_fmt(format_args!("#{ident} ={default}\n")) + .expect("written to config file"); + } + + let name = ident.to_string(); + summary.push(quote! { + writeln!(out, "| {} | {:?} |", #name, self.#ident)?; + }); + } + } + + if let Some(file) = file.as_mut() { + if let Some(footer) = settings.get("footer") { + file.write_all(footer.as_bytes()) .expect("written to config file"); } } - if let Some(footer) = settings.get("footer") { - file.write_all(footer.as_bytes()) - .expect("written to config file"); - } + let struct_name = &input.ident; + let display = quote! { + impl std::fmt::Display for #struct_name { + fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(out, "| name | value |")?; + writeln!(out, "| :--- | :--- |")?; + #( #summary )* + Ok(()) + } + } + }; - Ok(()) + Ok(display) } fn get_default(field: &Field) -> Option {