Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun

refactor: toml config #24

merged opened by isabelroses.com targeting main

most certainly a improvement but almost certainly going to have some errors

Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:qxichs7jsycphrsmbujwqbfb/sh.tangled.repo.pull/3meorfx6aki22
+538 -336
Interdiff #0 โ†’ #1
Cargo.lock

This file has not been changed.

Cargo.toml

This file has not been changed.

crates/tranquil-auth/Cargo.toml

This file has not been changed.

crates/tranquil-auth/src/token.rs

This file has not been changed.

crates/tranquil-cache/Cargo.toml

This file has not been changed.

+19 -13
crates/tranquil-cache/src/lib.rs
··· 172 172 pub async fn create_cache( 173 173 shutdown: tokio_util::sync::CancellationToken, 174 174 ) -> (Arc<dyn Cache>, Arc<dyn DistributedRateLimiter>) { 175 - let valkey_url = tranquil_config::try_get().and_then(|c| c.cache.valkey_url.clone()); 175 + let cache_cfg = tranquil_config::try_get().map(|c| &c.cache); 176 + let backend = cache_cfg.map(|c| c.backend.as_str()).unwrap_or("ripple"); 177 + let valkey_url = cache_cfg.and_then(|c| c.valkey_url.as_deref()); 176 178 177 179 #[cfg(feature = "valkey")] 178 - if let Some(url) = valkey_url.as_deref() { 179 - match ValkeyCache::new(url).await { 180 - Ok(cache) => { 181 - tracing::info!("using valkey cache at {url}"); 182 - let rate_limiter = Arc::new(RedisRateLimiter::new(cache.connection())); 183 - return (Arc::new(cache), rate_limiter); 180 + if backend == "valkey" { 181 + if let Some(url) = valkey_url { 182 + match ValkeyCache::new(url).await { 183 + Ok(cache) => { 184 + tracing::info!("using valkey cache at {url}"); 185 + let rate_limiter = Arc::new(RedisRateLimiter::new(cache.connection())); 186 + return (Arc::new(cache), rate_limiter); 187 + } 188 + Err(e) => { 189 + tracing::warn!("failed to connect to valkey: {e}. falling back to ripple."); 190 + } 184 191 } 185 - Err(e) => { 186 - tracing::warn!("failed to connect to valkey: {e}. falling back to ripple."); 187 - } 192 + } else { 193 + tracing::warn!("cache.backend is \"valkey\" but VALKEY_URL is not set. using ripple."); 188 194 } 189 195 } 190 196 191 197 #[cfg(not(feature = "valkey"))] 192 - if valkey_url.is_some() { 198 + if backend == "valkey" { 193 199 tracing::warn!( 194 - "valkey_url is set but binary was compiled without valkey feature. using ripple." 200 + "cache.backend is \"valkey\" but binary was compiled without valkey feature. using ripple." 195 201 ); 196 202 } 197 203 198 - match tranquil_ripple::RippleConfig::from_env() { 204 + match tranquil_ripple::RippleConfig::from_config() { 199 205 Ok(config) => { 200 206 let peer_count = config.seed_peers.len(); 201 207 match tranquil_ripple::RippleEngine::start(config, shutdown).await {
crates/tranquil-comms/Cargo.toml

This file has not been changed.

+9 -13
crates/tranquil-comms/src/sender.rs
··· 112 112 } 113 113 114 114 impl EmailSender { 115 - pub fn new(from_address: String, from_name: String) -> Self { 116 - let sendmail_path = tranquil_config::try_get() 117 - .map(|c| c.email.sendmail_path.clone()) 118 - .unwrap_or_else(|| "/usr/sbin/sendmail".to_string()); 115 + pub fn new(from_address: String, from_name: String, sendmail_path: String) -> Self { 119 116 Self { 120 117 from_address, 121 118 from_name, ··· 123 120 } 124 121 } 125 122 126 - pub fn from_env() -> Option<Self> { 127 - let cfg = tranquil_config::try_get()?; 123 + pub fn from_config(cfg: &tranquil_config::TranquilConfig) -> Option<Self> { 128 124 let from_address = cfg.email.from_address.clone()?; 129 125 let from_name = cfg.email.from_name.clone(); 130 - Some(Self::new(from_address, from_name)) 126 + let sendmail_path = cfg.email.sendmail_path.clone(); 127 + Some(Self::new(from_address, from_name, sendmail_path)) 131 128 } 132 129 133 130 pub fn format_email(&self, notification: &QueuedComms) -> String { ··· 192 189 } 193 190 } 194 191 195 - pub fn from_env() -> Option<Self> { 196 - let bot_token = tranquil_config::try_get()?.discord.bot_token.clone()?; 192 + pub fn from_config(cfg: &tranquil_config::TranquilConfig) -> Option<Self> { 193 + let bot_token = cfg.discord.bot_token.clone()?; 197 194 Some(Self::new(bot_token)) 198 195 } 199 196 ··· 456 453 } 457 454 } 458 455 459 - pub fn from_env() -> Option<Self> { 460 - let bot_token = tranquil_config::try_get()?.telegram.bot_token.clone()?; 456 + pub fn from_config(cfg: &tranquil_config::TranquilConfig) -> Option<Self> { 457 + let bot_token = cfg.telegram.bot_token.clone()?; 461 458 Some(Self::new(bot_token)) 462 459 } 463 460 ··· 588 585 } 589 586 } 590 587 591 - pub fn from_env() -> Option<Self> { 592 - let cfg = tranquil_config::try_get()?; 588 + pub fn from_config(cfg: &tranquil_config::TranquilConfig) -> Option<Self> { 593 589 let signal_cli_path = cfg.signal.cli_path.clone(); 594 590 let sender_number = cfg.signal.sender_number.clone()?; 595 591 Some(Self::new(signal_cli_path, sender_number))
crates/tranquil-config/Cargo.toml

This file has not been changed.

+58 -117
crates/tranquil-config/src/lib.rs
··· 113 113 pub import: ImportConfig, 114 114 115 115 #[config(nested)] 116 - pub crawlers: CrawlersConfig, 117 - 118 - #[config(nested)] 119 - pub ripple: RippleConfig, 120 - 121 - #[config(nested)] 122 116 pub scheduled: ScheduledConfig, 123 117 } 124 118 ··· 264 258 ); 265 259 } 266 260 261 + // -- cache ------------------------------------------------------------ 262 + match self.cache.backend.as_str() { 263 + "valkey" => { 264 + if self.cache.valkey_url.is_none() { 265 + errors.push( 266 + "cache.backend is \"valkey\" but cache.valkey_url (VALKEY_URL) \ 267 + is not set" 268 + .to_string(), 269 + ); 270 + } 271 + } 272 + "ripple" => {} 273 + other => { 274 + errors.push(format!( 275 + "cache.backend must be \"ripple\" or \"valkey\", got \"{other}\"" 276 + )); 277 + } 278 + } 279 + 267 280 if errors.is_empty() { 268 281 Ok(()) 269 282 } else { ··· 372 385 #[config(env = "SERVER_PORT", default = 3000)] 373 386 pub port: u16, 374 387 375 - /// Comma-separated list of domains that are considered service handle 376 - /// domains. Defaults to the PDS hostname when not set. 377 - #[config(env = "PDS_SERVICE_HANDLE_DOMAINS", parse_env = split_comma_list)] 378 - pub service_handle_domains: Option<Vec<String>>, 388 + /// List of domains for user handles. 389 + /// Defaults to the PDS hostname when not set. 390 + #[config(env = "PDS_USER_HANDLE_DOMAINS", parse_env = split_comma_list)] 391 + pub user_handle_domains: Option<Vec<String>>, 379 392 380 - /// Comma-separated list of domains available for user registration. 393 + /// List of domains available for user registration. 381 394 /// Defaults to the PDS hostname when not set. 382 395 #[config(env = "AVAILABLE_USER_DOMAINS", parse_env = split_comma_list)] 383 396 pub available_user_domains: Option<Vec<String>>, 384 397 385 - /// Enable self-hosted did:web identities. 386 - #[config(env = "ENABLE_SELF_HOSTED_DID_WEB", default = true)] 387 - pub enable_self_hosted_did_web: bool, 398 + /// Enable PDS-hosted did:web identities. Hosting did:web requires a 399 + /// long-term commitment to serve DID documents; opt-in only. 400 + #[config(env = "ENABLE_PDS_HOSTED_DID_WEB", default = false)] 401 + pub enable_pds_hosted_did_web: bool, 388 402 389 403 /// When set to true, skip age-assurance birthday prompt for all accounts. 390 404 #[config(env = "PDS_AGE_ASSURANCE_OVERRIDE", default = false)] 391 405 pub age_assurance_override: bool, 392 406 393 407 /// Require an invite code for new account registration. 394 - #[config(env = "INVITE_CODE_REQUIRED", default = false)] 408 + #[config(env = "INVITE_CODE_REQUIRED", default = true)] 395 409 pub invite_code_required: bool, 396 410 397 411 /// Allow HTTP (non-TLS) proxy requests. Only useful during development. ··· 402 416 #[config(env = "DISABLE_RATE_LIMITING", default = false)] 403 417 pub disable_rate_limiting: bool, 404 418 405 - /// Comma-separated list of additional banned words for handle validation. 419 + /// List of additional banned words for handle validation. 406 420 #[config(env = "PDS_BANNED_WORDS", parse_env = split_comma_list)] 407 421 pub banned_words: Option<Vec<String>>, 408 422 ··· 447 461 .unwrap_or_else(|| vec![self.hostname_without_port().to_string()]) 448 462 } 449 463 450 - /// Returns the service handle domains, falling back to `[hostname_without_port]`. 451 - pub fn service_handle_domain_list(&self) -> Vec<String> { 452 - self.service_handle_domains 464 + /// Returns the user handle domains, falling back to `[hostname_without_port]`. 465 + pub fn user_handle_domain_list(&self) -> Vec<String> { 466 + self.user_handle_domains 453 467 .clone() 454 468 .unwrap_or_else(|| vec![self.hostname_without_port().to_string()]) 455 469 } 456 470 } 457 471 458 - // --------------------------------------------------------------------------- 459 - // Database 460 - // --------------------------------------------------------------------------- 461 - 462 472 #[derive(Debug, Config)] 463 473 pub struct DatabaseConfig { 464 474 /// PostgreSQL connection URL. ··· 478 488 pub acquire_timeout_secs: u64, 479 489 } 480 490 481 - // --------------------------------------------------------------------------- 482 - // Secrets 483 - // --------------------------------------------------------------------------- 484 - 485 491 #[derive(Config)] 486 492 pub struct SecretsConfig { 487 493 /// Secret used for signing JWTs. Must be at least 32 characters in ··· 579 585 } 580 586 } 581 587 582 - // --------------------------------------------------------------------------- 583 - // Storage (blob) 584 - // --------------------------------------------------------------------------- 585 - 586 588 #[derive(Debug, Config)] 587 589 pub struct StorageConfig { 588 590 /// Storage backend: `filesystem` or `s3`. ··· 602 604 pub s3_endpoint: Option<String>, 603 605 } 604 606 605 - // --------------------------------------------------------------------------- 606 - // Backup 607 - // --------------------------------------------------------------------------- 608 - 609 607 #[derive(Debug, Config)] 610 608 pub struct BackupConfig { 611 609 /// Enable automatic backups. ··· 633 631 pub interval_secs: u64, 634 632 } 635 633 636 - // --------------------------------------------------------------------------- 637 - // Cache 638 - // --------------------------------------------------------------------------- 639 - 640 634 #[derive(Debug, Config)] 641 635 pub struct CacheConfig { 642 - /// Valkey / Redis connection URL. When set, Valkey is used for caching and 643 - /// distributed rate-limiting instead of the built-in Ripple protocol. 636 + /// Cache backend: `ripple` (default, built-in gossip) or `valkey`. 637 + #[config(env = "CACHE_BACKEND", default = "ripple")] 638 + pub backend: String, 639 + 640 + /// Valkey / Redis connection URL. Required when `backend = "valkey"`. 644 641 #[config(env = "VALKEY_URL")] 645 642 pub valkey_url: Option<String>, 643 + 644 + #[config(nested)] 645 + pub ripple: RippleCacheConfig, 646 646 } 647 647 648 - // --------------------------------------------------------------------------- 649 - // PLC directory 650 - // --------------------------------------------------------------------------- 651 - 652 648 #[derive(Debug, Config)] 653 649 pub struct PlcConfig { 654 650 /// Base URL of the PLC directory. ··· 668 664 pub did_cache_ttl_secs: u64, 669 665 } 670 666 671 - // --------------------------------------------------------------------------- 672 - // Firehose 673 - // --------------------------------------------------------------------------- 674 - 675 667 #[derive(Debug, Config)] 676 668 pub struct FirehoseConfig { 677 669 /// Size of the in-memory broadcast buffer for firehose events. ··· 686 678 /// Maximum number of lagged events before disconnecting a slow consumer. 687 679 #[config(env = "FIREHOSE_MAX_LAG", default = 5000)] 688 680 pub max_lag: u64, 681 + 682 + /// List of relay / crawler notification URLs. 683 + #[config(env = "CRAWLERS", parse_env = split_comma_list)] 684 + pub crawlers: Option<Vec<String>>, 689 685 } 690 686 691 - // --------------------------------------------------------------------------- 692 - // Email 693 - // --------------------------------------------------------------------------- 687 + impl FirehoseConfig { 688 + /// Returns the list of crawler URLs, falling back to `["https://bsky.network"]` 689 + /// when none are configured. 690 + pub fn crawler_list(&self) -> Vec<String> { 691 + self.crawlers 692 + .clone() 693 + .unwrap_or_else(|| vec!["https://bsky.network".to_string()]) 694 + } 695 + } 694 696 695 697 #[derive(Debug, Config)] 696 698 pub struct EmailConfig { ··· 707 709 pub sendmail_path: String, 708 710 } 709 711 710 - // --------------------------------------------------------------------------- 711 - // Discord 712 - // --------------------------------------------------------------------------- 713 - 714 712 #[derive(Debug, Config)] 715 713 pub struct DiscordConfig { 716 714 /// Discord bot token. When unset, Discord integration is disabled. ··· 718 716 pub bot_token: Option<String>, 719 717 } 720 718 721 - // --------------------------------------------------------------------------- 722 - // Telegram 723 - // --------------------------------------------------------------------------- 724 - 725 719 #[derive(Debug, Config)] 726 720 pub struct TelegramConfig { 727 721 /// Telegram bot token. When unset, Telegram integration is disabled. ··· 733 727 pub webhook_secret: Option<String>, 734 728 } 735 729 736 - // --------------------------------------------------------------------------- 737 - // Signal 738 - // --------------------------------------------------------------------------- 739 - 740 730 #[derive(Debug, Config)] 741 731 pub struct SignalConfig { 742 732 /// Path to the `signal-cli` binary. ··· 748 738 pub sender_number: Option<String>, 749 739 } 750 740 751 - // --------------------------------------------------------------------------- 752 - // Notification service 753 - // --------------------------------------------------------------------------- 754 - 755 741 #[derive(Debug, Config)] 756 742 pub struct NotificationConfig { 757 743 /// Polling interval in milliseconds for the comms queue. ··· 763 749 pub batch_size: i64, 764 750 } 765 751 766 - // --------------------------------------------------------------------------- 767 - // SSO 768 - // --------------------------------------------------------------------------- 769 - 770 752 #[derive(Debug, Config)] 771 753 pub struct SsoConfig { 772 754 #[config(nested)] ··· 838 820 pub private_key: Option<String>, 839 821 } 840 822 841 - // --------------------------------------------------------------------------- 842 - // Moderation 843 - // --------------------------------------------------------------------------- 844 - 845 823 #[derive(Debug, Config)] 846 824 pub struct ModerationConfig { 847 825 /// External report-handling service URL. ··· 853 831 pub report_service_did: Option<String>, 854 832 } 855 833 856 - // --------------------------------------------------------------------------- 857 - // Repo import 858 - // --------------------------------------------------------------------------- 859 - 860 834 #[derive(Debug, Config)] 861 835 pub struct ImportConfig { 862 836 /// Whether the PDS accepts repo imports. ··· 876 850 pub skip_verification: bool, 877 851 } 878 852 879 - // --------------------------------------------------------------------------- 880 - // Crawlers 881 - // --------------------------------------------------------------------------- 882 - 883 - #[derive(Debug, Config)] 884 - pub struct CrawlersConfig { 885 - /// Comma-separated list of relay / crawler notification URLs. 886 - #[config(env = "CRAWLERS", parse_env = split_comma_list)] 887 - pub urls: Option<Vec<String>>, 888 - } 889 - 890 - impl CrawlersConfig { 891 - /// Returns the list of crawler URLs, falling back to `["https://bsky.network"]` 892 - /// when none are configured. 893 - pub fn url_list(&self) -> Vec<String> { 894 - self.urls 895 - .clone() 896 - .unwrap_or_else(|| vec!["https://bsky.network".to_string()]) 897 - } 898 - } 899 - 900 853 /// Parse a comma-separated environment variable into a `Vec<String>`, 901 854 /// trimming whitespace and dropping empty entries. 902 855 /// ··· 909 862 .collect()) 910 863 } 911 864 912 - // --------------------------------------------------------------------------- 913 - // Ripple (built-in gossip cache) 914 - // --------------------------------------------------------------------------- 915 - 916 865 #[derive(Debug, Config)] 917 - pub struct RippleConfig { 866 + pub struct RippleCacheConfig { 918 867 /// Address to bind the Ripple gossip protocol listener. 919 868 #[config(env = "RIPPLE_BIND", default = "0.0.0.0:0")] 920 869 pub bind_addr: String, 921 870 922 - /// Comma-separated list of seed peer addresses. 923 - #[config(env = "RIPPLE_PEERS")] 924 - pub peers: Option<String>, 871 + /// List of seed peer addresses. 872 + #[config(env = "RIPPLE_PEERS", parse_env = split_comma_list)] 873 + pub peers: Option<Vec<String>>, 925 874 926 875 /// Unique machine identifier. Auto-derived from hostname when not set. 927 876 #[config(env = "RIPPLE_MACHINE_ID")] ··· 936 885 pub cache_max_mb: usize, 937 886 } 938 887 939 - // --------------------------------------------------------------------------- 940 - // Scheduled tasks 941 - // --------------------------------------------------------------------------- 942 - 943 888 #[derive(Debug, Config)] 944 889 pub struct ScheduledConfig { 945 890 /// Interval in seconds between scheduled delete checks. 946 891 #[config(env = "SCHEDULED_DELETE_CHECK_INTERVAL_SECS", default = 3600)] 947 892 pub delete_check_interval_secs: u64, 948 893 } 949 - 950 - // --------------------------------------------------------------------------- 951 - // TOML template generation 952 - // --------------------------------------------------------------------------- 953 894 954 895 /// Generate a TOML configuration template with all available options, 955 896 /// defaults, and documentation comments.
crates/tranquil-infra/Cargo.toml

This file has not been changed.

crates/tranquil-infra/src/lib.rs

This file has not been changed.

crates/tranquil-pds/Cargo.toml

This file has not been changed.

crates/tranquil-pds/src/api/admin/account/email.rs

This file has not been changed.

crates/tranquil-pds/src/api/admin/account/update.rs

This file has not been changed.

crates/tranquil-pds/src/api/delegation.rs

This file has not been changed.

crates/tranquil-pds/src/api/discord_webhook.rs

This file has not been changed.

crates/tranquil-pds/src/api/identity/account.rs

This file has not been changed.

crates/tranquil-pds/src/api/identity/did.rs

This file has not been changed.

crates/tranquil-pds/src/api/identity/plc/request.rs

This file has not been changed.

crates/tranquil-pds/src/api/identity/plc/submit.rs

This file has not been changed.

crates/tranquil-pds/src/api/moderation/mod.rs

This file has not been changed.

crates/tranquil-pds/src/api/notification_prefs.rs

This file has not been changed.

crates/tranquil-pds/src/api/proxy.rs

This file has not been changed.

crates/tranquil-pds/src/api/proxy_client.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/blob.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/import.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/meta.rs

This file has not been changed.

crates/tranquil-pds/src/api/repo/record/read.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/account_status.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/email.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/invite.rs

This file has not been changed.

+1 -1
crates/tranquil-pds/src/api/server/meta.rs
··· 27 27 ) 28 28 } 29 29 pub fn is_self_hosted_did_web_enabled() -> bool { 30 - tranquil_config::get().server.enable_self_hosted_did_web 30 + tranquil_config::get().server.enable_pds_hosted_did_web 31 31 } 32 32 33 33 pub async fn describe_server() -> impl IntoResponse {
crates/tranquil-pds/src/api/server/migration.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/passkey_account.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/password.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/session.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/totp.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/verify_email.rs

This file has not been changed.

crates/tranquil-pds/src/api/server/verify_token.rs

This file has not been changed.

crates/tranquil-pds/src/api/telegram_webhook.rs

This file has not been changed.

crates/tranquil-pds/src/appview/mod.rs

This file has not been changed.

crates/tranquil-pds/src/auth/service.rs

This file has not been changed.

crates/tranquil-pds/src/auth/verification_token.rs

This file has not been changed.

crates/tranquil-pds/src/comms/service.rs

This file has not been changed.

crates/tranquil-pds/src/config.rs

This file has not been changed.

+2 -3
crates/tranquil-pds/src/crawlers.rs
··· 41 41 self 42 42 } 43 43 44 - pub fn from_env() -> Option<Self> { 45 - let cfg = tranquil_config::get(); 44 + pub fn from_config(cfg: &tranquil_config::TranquilConfig) -> Option<Self> { 46 45 let hostname = &cfg.server.hostname; 47 46 if hostname == "localhost" { 48 47 return None; 49 48 } 50 49 51 - let crawler_urls = cfg.crawlers.url_list(); 50 + let crawler_urls = cfg.firehose.crawler_list(); 52 51 53 52 if crawler_urls.is_empty() { 54 53 return None;
+1 -1
crates/tranquil-pds/src/handle/mod.rs
··· 91 91 if !handle.contains('.') { 92 92 return true; 93 93 } 94 - let service_domains = tranquil_config::get().server.service_handle_domain_list(); 94 + let service_domains = tranquil_config::get().server.user_handle_domain_list(); 95 95 service_domains 96 96 .iter() 97 97 .any(|domain| handle.ends_with(&format!(".{}", domain)) || handle == domain)
crates/tranquil-pds/src/lib.rs

This file has not been changed.

+9 -7
crates/tranquil-pds/src/main.rs
··· 23 23 #[derive(Parser)] 24 24 #[command(name = "tranquil-pds", version = BUILD_VERSION, about = "Tranquil AT Protocol PDS")] 25 25 struct Cli { 26 - /// Path to a TOML configuration file (also settable via TRANQUIL_CONFIG env var) 27 - #[arg(short, long, value_name = "FILE", env = "TRANQUIL_CONFIG")] 26 + /// Path to a TOML configuration file (also settable via TRANQUIL_PDS_CONFIG env var) 27 + #[arg(short, long, value_name = "FILE", env = "TRANQUIL_PDS_CONFIG")] 28 28 config: Option<PathBuf>, 29 29 30 30 #[command(subcommand)] ··· 135 135 let mut comms_service = CommsService::new(state.infra_repo.clone()); 136 136 let mut deferred_discord_endpoint: Option<(DiscordSender, String, String)> = None; 137 137 138 - if let Some(email_sender) = EmailSender::from_env() { 138 + let cfg = tranquil_config::get(); 139 + 140 + if let Some(email_sender) = EmailSender::from_config(cfg) { 139 141 info!("Email comms enabled"); 140 142 comms_service = comms_service.register_sender(email_sender); 141 143 } else { 142 144 warn!("Email comms disabled (MAIL_FROM_ADDRESS not set)"); 143 145 } 144 146 145 - if let Some(discord_sender) = DiscordSender::from_env() { 147 + if let Some(discord_sender) = DiscordSender::from_config(cfg) { 146 148 info!("Discord comms enabled"); 147 149 match discord_sender.resolve_bot_username().await { 148 150 Ok(username) => { ··· 186 188 comms_service = comms_service.register_sender(discord_sender); 187 189 } 188 190 189 - if let Some(telegram_sender) = TelegramSender::from_env() { 191 + if let Some(telegram_sender) = TelegramSender::from_config(cfg) { 190 192 // Safe to unwrap: validated in TranquilConfig::validate() 191 193 let secret_token = tranquil_config::get() 192 194 .telegram ··· 215 217 comms_service = comms_service.register_sender(telegram_sender); 216 218 } 217 219 218 - if let Some(signal_sender) = SignalSender::from_env() { 220 + if let Some(signal_sender) = SignalSender::from_config(cfg) { 219 221 info!("Signal comms enabled"); 220 222 comms_service = comms_service.register_sender(signal_sender); 221 223 } 222 224 223 225 let comms_handle = tokio::spawn(comms_service.run(shutdown.clone())); 224 226 225 - let crawlers_handle = if let Some(crawlers) = Crawlers::from_env() { 227 + let crawlers_handle = if let Some(crawlers) = Crawlers::from_config(cfg) { 226 228 let crawlers = Arc::new( 227 229 crawlers.with_circuit_breaker(state.circuit_breakers.relay_notification.clone()), 228 230 );
crates/tranquil-pds/src/moderation/mod.rs

This file has not been changed.

crates/tranquil-pds/src/oauth/endpoints/authorize.rs

This file has not been changed.

crates/tranquil-pds/src/oauth/endpoints/metadata.rs

This file has not been changed.

crates/tranquil-pds/src/oauth/endpoints/token/grants.rs

This file has not been changed.

crates/tranquil-pds/src/oauth/endpoints/token/helpers.rs

This file has not been changed.

crates/tranquil-pds/src/oauth/endpoints/token/introspect.rs

This file has not been changed.

crates/tranquil-pds/src/plc/mod.rs

This file has not been changed.

crates/tranquil-pds/src/scheduled.rs

This file has not been changed.

crates/tranquil-pds/src/sso/config.rs

This file has not been changed.

crates/tranquil-pds/src/sso/endpoints.rs

This file has not been changed.

crates/tranquil-pds/src/state.rs

This file has not been changed.

crates/tranquil-pds/src/sync/subscribe_repos.rs

This file has not been changed.

crates/tranquil-pds/src/sync/verify.rs

This file has not been changed.

crates/tranquil-pds/src/util.rs

This file has not been changed.

crates/tranquil-ripple/Cargo.toml

This file has not been changed.

+4 -4
crates/tranquil-ripple/src/config.rs
··· 16 16 } 17 17 18 18 impl RippleConfig { 19 - pub fn from_env() -> Result<Self, RippleConfigError> { 20 - let ripple = &tranquil_config::get().ripple; 19 + pub fn from_config() -> Result<Self, RippleConfigError> { 20 + let ripple = &tranquil_config::get().cache.ripple; 21 21 22 22 let bind_addr: SocketAddr = ripple 23 23 .bind_addr ··· 27 27 let seed_peers: Vec<SocketAddr> = ripple 28 28 .peers 29 29 .as_deref() 30 - .unwrap_or("") 31 - .split(',') 30 + .unwrap_or(&[]) 31 + .iter() 32 32 .filter(|s| !s.trim().is_empty()) 33 33 .map(|s| { 34 34 s.trim()
crates/tranquil-storage/Cargo.toml

This file has not been changed.

+21 -17
crates/tranquil-storage/src/lib.rs
··· 508 508 }) 509 509 } 510 510 511 - pub async fn from_env() -> Result<Self, StorageError> { 512 - let path = tranquil_config::get().storage.path.clone().ok_or_else(|| { 513 - StorageError::Other("storage.path (BLOB_STORAGE_PATH) not set".into()) 514 - })?; 515 - Self::new(path).await 516 - } 517 - 518 511 fn resolve_path(&self, key: &str) -> Result<PathBuf, StorageError> { 519 512 validate_key(key)?; 520 513 Ok(split_cid_path(key).map_or_else( ··· 659 652 }) 660 653 } 661 654 662 - pub async fn from_env() -> Result<Self, StorageError> { 663 - let path = tranquil_config::get().backup.path.clone().ok_or_else(|| { 664 - StorageError::Other("backup.path (BACKUP_STORAGE_PATH) not set".into()) 665 - })?; 666 - Self::new(path).await 667 - } 668 - 669 655 fn resolve_path(&self, key: &str) -> Result<PathBuf, StorageError> { 670 656 validate_key(key)?; 671 657 Ok(self.base_path.join(key)) ··· 730 716 } 731 717 _ => { 732 718 tracing::info!("Initializing filesystem blob storage"); 733 - FilesystemBlobStorage::from_env() 719 + let path = cfg.storage.path.clone().unwrap_or_else(|| { 720 + panic!( 721 + "storage.path (BLOB_STORAGE_PATH) not set. \ 722 + Set BLOB_STORAGE_PATH to a valid directory path." 723 + ); 724 + }); 725 + FilesystemBlobStorage::new(path) 734 726 .await 735 727 .unwrap_or_else(|e| { 736 728 panic!( ··· 777 769 ); 778 770 None 779 771 } 780 - _ => FilesystemBackupStorage::from_env().await.map_or_else( 772 + _ => { 773 + let path = match cfg.backup.path.clone() { 774 + Some(p) => p, 775 + None => { 776 + tracing::error!( 777 + "backup.path (BACKUP_STORAGE_PATH) not set. \ 778 + Backups will be disabled." 779 + ); 780 + return None; 781 + } 782 + }; 783 + FilesystemBackupStorage::new(path).await.map_or_else( 781 784 |e| { 782 785 tracing::error!( 783 786 "Failed to initialize filesystem backup storage: {}. \ ··· 791 794 tracing::info!("Initialized filesystem backup storage"); 792 795 Some(Arc::new(storage) as Arc<dyn BackupStorage>) 793 796 }, 794 - ), 797 + ) 798 + } 795 799 } 796 800 } 797 801
docker-compose.prod.yaml

This file has not been changed.

docker-compose.yaml

This file has not been changed.

+8
docs/install-containers.md
··· 125 125 openssl rand -base64 48 126 126 ``` 127 127 128 + > **Note:** Every config option can also be set via environment variables 129 + > (see comments in `example.toml`). Environment variables always take 130 + > precedence over the config file. 131 + 128 132 ## Install quadlet definitions 129 133 130 134 Copy the quadlet files from the repository: ··· 274 278 ```sh 275 279 openssl rand -base64 48 276 280 ``` 281 + 282 + > **Note:** Every config option can also be set via environment variables 283 + > (see comments in `example.toml`). Environment variables always take 284 + > precedence over the config file. 277 285 278 286 ## Set up compose and nginx 279 287
+5
docs/install-debian.md
··· 82 82 openssl rand -base64 48 83 83 ``` 84 84 85 + > **Note:** Every config option can also be set via environment variables 86 + > (see comments in `example.toml`). Environment variables always take 87 + > precedence over the config file. You can also pass the config file path 88 + > via the `TRANQUIL_PDS_CONFIG` env var instead of `--config`. 89 + 85 90 You can validate your configuration before starting the service: 86 91 ```bash 87 92 /usr/local/bin/tranquil-pds --config /etc/tranquil-pds/config.toml validate
+3 -1
docs/install-kubernetes.md
··· 16 16 - `PDS_HOSTNAME` - your PDS hostname (without protocol) 17 17 - `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48` 18 18 - `CRAWLERS` - typically `https://bsky.network` 19 - and more, check the example.toml for all options. Environment variables can override any TOML values. 19 + 20 + and more, check the example.toml for all options. Environment variables can override any TOML value. 21 + You can also point to a config file via the `TRANQUIL_PDS_CONFIG` env var. 20 22 21 23 Health check: `GET /xrpc/_health` 22 24
+392 -156
example.toml
··· 1 - # copy this file to /etc/tranquil-pds/config.toml and edit as needed 1 + [server] 2 + # Public hostname of the PDS (e.g. `pds.example.com`). 2 3 # 3 - # environment variables always take precedence over values in this file 4 + # Can also be specified via environment variable `PDS_HOSTNAME`. 4 5 # 5 - # Generate secrets with: openssl rand -base64 48 6 + # Required! This value must be specified. 7 + #hostname = 6 8 7 - [server] 8 - # The public-facing hostname of the PDS (used in DID documents, JWTs, etc.) 9 - hostname = "localhost" 10 - 11 9 # Address to bind the HTTP server to. 12 - host = "127.0.0.1" 10 + # 11 + # Can also be specified via environment variable `SERVER_HOST`. 12 + # 13 + # Default value: "127.0.0.1" 14 + #host = "127.0.0.1" 13 15 14 16 # Port to bind the HTTP server to. 15 - port = 3000 17 + # 18 + # Can also be specified via environment variable `SERVER_PORT`. 19 + # 20 + # Default value: 3000 21 + #port = 3000 16 22 17 - # Comma-separated list of domains that are considered service handle domains. 23 + # List of domains for user handles. 18 24 # Defaults to the PDS hostname when not set. 19 - # service_handle_domains = "pds.example.com" 25 + # 26 + # Can also be specified via environment variable `PDS_USER_HANDLE_DOMAINS`. 27 + #user_handle_domains = 20 28 21 - # Comma-separated list of domains available for user registration. 29 + # List of domains available for user registration. 22 30 # Defaults to the PDS hostname when not set. 23 - # available_user_domains = "example.com" 31 + # 32 + # Can also be specified via environment variable `AVAILABLE_USER_DOMAINS`. 33 + #available_user_domains = 24 34 25 - # Enable self-hosted did:web identities. 26 - # Hosting did:web requires a long-term commitment to serve DID documents. 27 - # Set to false if you don't want to offer this option. 28 - enable_self_hosted_did_web = true 35 + # Enable PDS-hosted did:web identities. Hosting did:web requires a 36 + # long-term commitment to serve DID documents; opt-in only. 37 + # 38 + # Can also be specified via environment variable `ENABLE_PDS_HOSTED_DID_WEB`. 39 + # 40 + # Default value: false 41 + #enable_pds_hosted_did_web = false 29 42 30 - # When true, skip the age-assurance birthday prompt for all accounts. 31 - # Enable this if you have separately assured the ages of your users 32 - # (e.g., through your own age verification process). 33 - age_assurance_override = false 43 + # When set to true, skip age-assurance birthday prompt for all accounts. 44 + # 45 + # Can also be specified via environment variable `PDS_AGE_ASSURANCE_OVERRIDE`. 46 + # 47 + # Default value: false 48 + #age_assurance_override = false 34 49 35 50 # Require an invite code for new account registration. 36 - invite_code_required = false 51 + # 52 + # Can also be specified via environment variable `INVITE_CODE_REQUIRED`. 53 + # 54 + # Default value: true 55 + #invite_code_required = true 37 56 38 - # Allow HTTP (non-TLS) proxy requests. Only for development. 39 - allow_http_proxy = false 57 + # Allow HTTP (non-TLS) proxy requests. Only useful during development. 58 + # 59 + # Can also be specified via environment variable `ALLOW_HTTP_PROXY`. 60 + # 61 + # Default value: false 62 + #allow_http_proxy = false 40 63 41 - # Disable all rate limiting. Should NEVER be enabled in production. 42 - disable_rate_limiting = false 64 + # Disable all rate limiting. Should only be used in testing. 65 + # 66 + # Can also be specified via environment variable `DISABLE_RATE_LIMITING`. 67 + # 68 + # Default value: false 69 + #disable_rate_limiting = false 43 70 44 - # list of additional banned words for handle validation. 45 - # banned_words = [""] 71 + # List of additional banned words for handle validation. 72 + # 73 + # Can also be specified via environment variable `PDS_BANNED_WORDS`. 74 + #banned_words = 46 75 47 - # URL to a privacy policy page (optional, shown in describeServer). 48 - # privacy_policy_url = "https://example.com/privacy" 76 + # URL to a privacy policy page. 77 + # 78 + # Can also be specified via environment variable `PRIVACY_POLICY_URL`. 79 + #privacy_policy_url = 49 80 50 - # URL to terms of service page (optional, shown in describeServer). 51 - # terms_of_service_url = "https://example.com/terms" 81 + # URL to terms of service page. 82 + # 83 + # Can also be specified via environment variable `TERMS_OF_SERVICE_URL`. 84 + #terms_of_service_url = 52 85 53 - # Operator contact email address (optional, shown in describeServer). 54 - # contact_email = "admin@example.com" 86 + # Operator contact email address. 87 + # 88 + # Can also be specified via environment variable `CONTACT_EMAIL`. 89 + #contact_email = 55 90 56 - # Maximum allowed blob/body size in bytes (default 10 GiB). 57 - # Make sure your nginx client_max_body_size matches or exceeds this value. 58 - max_blob_size = 10_737_418_240 91 + # Maximum allowed blob size in bytes (default 10 GiB). 92 + # 93 + # Can also be specified via environment variable `MAX_BLOB_SIZE`. 94 + # 95 + # Default value: 10737418240 96 + #max_blob_size = 10737418240 59 97 60 98 [database] 61 - # PostgreSQL connection URL (REQUIRED). 62 - url = "postgres://postgres:postgres@localhost:5432/pds" 99 + # PostgreSQL connection URL. 100 + # 101 + # Can also be specified via environment variable `DATABASE_URL`. 102 + # 103 + # Required! This value must be specified. 104 + #url = 63 105 64 106 # Maximum number of connections in the pool. 65 - max_connections = 100 107 + # 108 + # Can also be specified via environment variable `DATABASE_MAX_CONNECTIONS`. 109 + # 110 + # Default value: 100 111 + #max_connections = 100 66 112 67 113 # Minimum number of idle connections kept in the pool. 68 - min_connections = 10 114 + # 115 + # Can also be specified via environment variable `DATABASE_MIN_CONNECTIONS`. 116 + # 117 + # Default value: 10 118 + #min_connections = 10 69 119 70 120 # Timeout in seconds when acquiring a connection from the pool. 71 - acquire_timeout_secs = 10 121 + # 122 + # Can also be specified via environment variable `DATABASE_ACQUIRE_TIMEOUT_SECS`. 123 + # 124 + # Default value: 10 125 + #acquire_timeout_secs = 10 72 126 73 127 [secrets] 74 - # These MUST be set in production (minimum 32 characters each). 75 - # Generate with: openssl rand -base64 48 128 + # Secret used for signing JWTs. Must be at least 32 characters in 129 + # production. 130 + # 131 + # Can also be specified via environment variable `JWT_SECRET`. 132 + #jwt_secret = 76 133 77 - # Server-wide secret for OAuth token signing (HS256). 78 - # jwt_secret = "your-secure-random-string-at-least-32-chars" 134 + # Secret used for DPoP proof validation. Must be at least 32 characters 135 + # in production. 136 + # 137 + # Can also be specified via environment variable `DPOP_SECRET`. 138 + #dpop_secret = 79 139 80 - # Secret for DPoP proof validation. 81 - # dpop_secret = "your-secure-random-string-at-least-32-chars" 140 + # Master key used for key-encryption and HKDF derivation. Must be at 141 + # least 32 characters in production. 142 + # 143 + # Can also be specified via environment variable `MASTER_KEY`. 144 + #master_key = 82 145 83 - # Key for encrypting user signing keys at rest (AES-256-GCM). 84 - # master_key = "your-secure-random-string-at-least-32-chars" 85 - 86 146 # PLC rotation key (DID key). If not set, user-level keys are used. 87 - # plc_rotation_key = "did:key:..." 147 + # 148 + # Can also be specified via environment variable `PLC_ROTATION_KEY`. 149 + #plc_rotation_key = 88 150 89 151 # Allow insecure/test secrets. NEVER enable in production. 90 - # Set to true ONLY in development to allow default/weak secrets. 91 - allow_insecure = false 152 + # 153 + # Can also be specified via environment variable `TRANQUIL_PDS_ALLOW_INSECURE_SECRETS`. 154 + # 155 + # Default value: false 156 + #allow_insecure = false 92 157 93 158 [storage] 94 - # Backend: "filesystem" (default) or "s3". 95 - backend = "filesystem" 159 + # Storage backend: `filesystem` or `s3`. 160 + # 161 + # Can also be specified via environment variable `BLOB_STORAGE_BACKEND`. 162 + # 163 + # Default value: "filesystem" 164 + #backend = "filesystem" 96 165 97 - # For filesystem backend: path on disk for blob storage. 98 - path = "/var/lib/tranquil/blobs" 166 + # Path on disk for the filesystem blob backend. 167 + # 168 + # Can also be specified via environment variable `BLOB_STORAGE_PATH`. 169 + #path = 99 170 100 - # For S3 backend: bucket name. 101 - # s3_bucket = "pds-blobs" 171 + # S3 bucket name for blob storage. 172 + # 173 + # Can also be specified via environment variable `S3_BUCKET`. 174 + #s3_bucket = 102 175 103 176 # Custom S3 endpoint URL (for MinIO, R2, etc.). 104 - # s3_endpoint = "http://localhost:9000" 177 + # 178 + # Can also be specified via environment variable `S3_ENDPOINT`. 179 + #s3_endpoint = 105 180 106 181 [backup] 107 - # Enable automatic repo backups. 108 - enabled = true 182 + # Enable automatic backups. 183 + # 184 + # Can also be specified via environment variable `BACKUP_ENABLED`. 185 + # 186 + # Default value: true 187 + #enabled = true 109 188 110 - # Backup storage backend: "filesystem" (default) or "s3". 111 - backend = "filesystem" 189 + # Backup storage backend: `filesystem` or `s3`. 190 + # 191 + # Can also be specified via environment variable `BACKUP_STORAGE_BACKEND`. 192 + # 193 + # Default value: "filesystem" 194 + #backend = "filesystem" 112 195 113 - # For filesystem backend: path on disk for backups. 114 - path = "/var/lib/tranquil/backups" 196 + # Path on disk for the filesystem backup backend. 197 + # 198 + # Can also be specified via environment variable `BACKUP_STORAGE_PATH`. 199 + #path = 115 200 116 - # For S3 backend: bucket name. 117 - # s3_bucket = "pds-backups" 201 + # S3 bucket name for backups. 202 + # 203 + # Can also be specified via environment variable `BACKUP_S3_BUCKET`. 204 + #s3_bucket = 118 205 119 206 # Number of backup revisions to keep per account. 120 - retention_count = 7 207 + # 208 + # Can also be specified via environment variable `BACKUP_RETENTION_COUNT`. 209 + # 210 + # Default value: 7 211 + #retention_count = 7 121 212 122 - # Seconds between backup runs (default: 24 hours). 123 - interval_secs = 86400 213 + # Seconds between backup runs. 214 + # 215 + # Can also be specified via environment variable `BACKUP_INTERVAL_SECS`. 216 + # 217 + # Default value: 86400 218 + #interval_secs = 86400 124 219 125 220 [cache] 126 - # valkey_url = "redis://localhost:6379" 221 + # Cache backend: `ripple` (default, built-in gossip) or `valkey`. 222 + # 223 + # Can also be specified via environment variable `CACHE_BACKEND`. 224 + # 225 + # Default value: "ripple" 226 + #backend = "ripple" 127 227 228 + # Valkey / Redis connection URL. Required when `backend = "valkey"`. 229 + # 230 + # Can also be specified via environment variable `VALKEY_URL`. 231 + #valkey_url = 232 + 233 + [cache.ripple] 234 + # Address to bind the Ripple gossip protocol listener. 235 + # 236 + # Can also be specified via environment variable `RIPPLE_BIND`. 237 + # 238 + # Default value: "0.0.0.0:0" 239 + #bind_addr = "0.0.0.0:0" 240 + 241 + # List of seed peer addresses. 242 + # 243 + # Can also be specified via environment variable `RIPPLE_PEERS`. 244 + #peers = 245 + 246 + # Unique machine identifier. Auto-derived from hostname when not set. 247 + # 248 + # Can also be specified via environment variable `RIPPLE_MACHINE_ID`. 249 + #machine_id = 250 + 251 + # Gossip protocol interval in milliseconds. 252 + # 253 + # Can also be specified via environment variable `RIPPLE_GOSSIP_INTERVAL_MS`. 254 + # 255 + # Default value: 200 256 + #gossip_interval_ms = 200 257 + 258 + # Maximum cache size in megabytes. 259 + # 260 + # Can also be specified via environment variable `RIPPLE_CACHE_MAX_MB`. 261 + # 262 + # Default value: 256 263 + #cache_max_mb = 256 264 + 128 265 [plc] 129 266 # Base URL of the PLC directory. 130 - directory_url = "https://plc.directory" 267 + # 268 + # Can also be specified via environment variable `PLC_DIRECTORY_URL`. 269 + # 270 + # Default value: "https://plc.directory" 271 + #directory_url = "https://plc.directory" 131 272 132 273 # HTTP request timeout in seconds. 133 - timeout_secs = 10 274 + # 275 + # Can also be specified via environment variable `PLC_TIMEOUT_SECS`. 276 + # 277 + # Default value: 10 278 + #timeout_secs = 10 134 279 135 280 # TCP connect timeout in seconds. 136 - connect_timeout_secs = 5 281 + # 282 + # Can also be specified via environment variable `PLC_CONNECT_TIMEOUT_SECS`. 283 + # 284 + # Default value: 5 285 + #connect_timeout_secs = 5 137 286 138 287 # Seconds to cache DID documents in memory. 139 - did_cache_ttl_secs = 300 288 + # 289 + # Can also be specified via environment variable `DID_CACHE_TTL_SECS`. 290 + # 291 + # Default value: 300 292 + #did_cache_ttl_secs = 300 140 293 141 294 [firehose] 142 295 # Size of the in-memory broadcast buffer for firehose events. 143 - buffer_size = 10_000 296 + # 297 + # Can also be specified via environment variable `FIREHOSE_BUFFER_SIZE`. 298 + # 299 + # Default value: 10000 300 + #buffer_size = 10000 144 301 145 - # How many hours of historical events to replay for cursor-based connections. 146 - backfill_hours = 72 302 + # How many hours of historical events to replay for cursor-based 303 + # firehose connections. 304 + # 305 + # Can also be specified via environment variable `FIREHOSE_BACKFILL_HOURS`. 306 + # 307 + # Default value: 72 308 + #backfill_hours = 72 147 309 148 310 # Maximum number of lagged events before disconnecting a slow consumer. 149 - max_lag = 5000 311 + # 312 + # Can also be specified via environment variable `FIREHOSE_MAX_LAG`. 313 + # 314 + # Default value: 5000 315 + #max_lag = 5000 150 316 151 - [notifications] 152 - # polling interval in milliseconds for the comms queue. 153 - poll_interval_ms = 1000 317 + # List of relay / crawler notification URLs. 318 + # 319 + # Can also be specified via environment variable `CRAWLERS`. 320 + #crawlers = 154 321 155 - # number of notifications to process per batch. 156 - batch_size = 100 157 - 158 322 [email] 159 - # sender email address. when unset, email sending is disabled. 160 - # from_address = "noreply@example.com" 323 + # Sender email address. When unset, email sending is disabled. 324 + # 325 + # Can also be specified via environment variable `MAIL_FROM_ADDRESS`. 326 + #from_address = 161 327 162 - # display name used in the from headerw 163 - from_name = "Tranquil PDS" 328 + # Display name used in the `From` header. 329 + # 330 + # Can also be specified via environment variable `MAIL_FROM_NAME`. 331 + # 332 + # Default value: "Tranquil PDS" 333 + #from_name = "Tranquil PDS" 164 334 165 - # Path to the sendmail binary. 166 - sendmail_path = "/usr/sbin/sendmail" 335 + # Path to the `sendmail` binary. 336 + # 337 + # Can also be specified via environment variable `SENDMAIL_PATH`. 338 + # 339 + # Default value: "/usr/sbin/sendmail" 340 + #sendmail_path = "/usr/sbin/sendmail" 167 341 168 342 [discord] 169 - # discord bot token. when unset, Discord integration is disabled. 170 - # bot_token = "bot-token" 343 + # Discord bot token. When unset, Discord integration is disabled. 344 + # 345 + # Can also be specified via environment variable `DISCORD_BOT_TOKEN`. 346 + #bot_token = 171 347 172 348 [telegram] 173 349 # Telegram bot token. When unset, Telegram integration is disabled. 174 - # bot_token = "bot-token" 350 + # 351 + # Can also be specified via environment variable `TELEGRAM_BOT_TOKEN`. 352 + #bot_token = 175 353 176 354 # Secret token for incoming webhook verification. 177 - # Required when bot_token is set. 178 - # webhook_secret = "random-secret" 355 + # 356 + # Can also be specified via environment variable `TELEGRAM_WEBHOOK_SECRET`. 357 + #webhook_secret = 179 358 180 359 [signal] 181 - # Path to the signal-cli binary. 182 - cli_path = "/usr/local/bin/signal-cli" 360 + # Path to the `signal-cli` binary. 361 + # 362 + # Can also be specified via environment variable `SIGNAL_CLI_PATH`. 363 + # 364 + # Default value: "/usr/local/bin/signal-cli" 365 + #cli_path = "/usr/local/bin/signal-cli" 183 366 184 367 # Sender phone number. When unset, Signal integration is disabled. 185 - # sender_number = "+1234567890" 368 + # 369 + # Can also be specified via environment variable `SIGNAL_SENDER_NUMBER`. 370 + #sender_number = 186 371 187 - ## SSO 372 + [notifications] 373 + # Polling interval in milliseconds for the comms queue. 374 + # 375 + # Can also be specified via environment variable `NOTIFICATION_POLL_INTERVAL_MS`. 376 + # 377 + # Default value: 1000 378 + #poll_interval_ms = 1000 188 379 380 + # Number of notifications to process per batch. 381 + # 382 + # Can also be specified via environment variable `NOTIFICATION_BATCH_SIZE`. 383 + # 384 + # Default value: 100 385 + #batch_size = 100 386 + 387 + [sso] 189 388 [sso.github] 190 - enabled = false 191 - # client_id = "" 192 - # client_secret = "" 389 + # Default value: false 390 + #enabled = false 193 391 392 + #client_id = 393 + 394 + #client_secret = 395 + 396 + #display_name = 397 + 194 398 [sso.discord] 195 - enabled = false 196 - # client_id = "" 197 - # client_secret = "" 399 + # Default value: false 400 + #enabled = false 198 401 402 + #client_id = 403 + 404 + #client_secret = 405 + 406 + #display_name = 407 + 199 408 [sso.google] 200 - enabled = false 201 - # client_id = "" 202 - # client_secret = "" 409 + # Default value: false 410 + #enabled = false 203 411 412 + #client_id = 413 + 414 + #client_secret = 415 + 416 + #display_name = 417 + 204 418 [sso.gitlab] 205 - enabled = false 206 - # client_id = "" 207 - # client_secret = "" 208 - # issuer = "https://gitlab.com" 419 + # Default value: false 420 + #enabled = false 209 421 422 + #client_id = 423 + 424 + #client_secret = 425 + 426 + #issuer = 427 + 428 + #display_name = 429 + 210 430 [sso.oidc] 211 - enabled = false 212 - # client_id = "" 213 - # client_secret = "" 214 - # issuer = "https://your-identity-provider.com" 215 - # display_name = "Custom Provider" 431 + # Default value: false 432 + #enabled = false 216 433 434 + #client_id = 435 + 436 + #client_secret = 437 + 438 + #issuer = 439 + 440 + #display_name = 441 + 217 442 [sso.apple] 218 - enabled = false 219 - # Services ID from Apple Developer Portal 220 - # client_id = "com.example.signin" 443 + # Can also be specified via environment variable `SSO_APPLE_ENABLED`. 444 + # Default value: false 445 + #enabled = false 221 446 222 - # 10-character Team ID 223 - # team_id = "XXXXXXXXXX" 447 + # Can also be specified via environment variable `SSO_APPLE_CLIENT_ID`. 448 + #client_id = 224 449 225 - # Key ID from portal 226 - # key_id = "XXXXXXXXXX" 450 + # Can also be specified via environment variable `SSO_APPLE_TEAM_ID`. 451 + #team_id = 227 452 228 - # private_key = "" 453 + # Can also be specified via environment variable `SSO_APPLE_KEY_ID`. 454 + #key_id = 229 455 456 + # Can also be specified via environment variable `SSO_APPLE_PRIVATE_KEY`. 457 + #private_key = 458 + 230 459 [moderation] 231 - # report_service_url = "https://mod.bsky.app" 232 - # report_service_did = "did:plc:ar7c4by46qjdydhdevvrndac" 460 + # External report-handling service URL. 461 + # 462 + # Can also be specified via environment variable `REPORT_SERVICE_URL`. 463 + #report_service_url = 233 464 465 + # DID of the external report-handling service. 466 + # 467 + # Can also be specified via environment variable `REPORT_SERVICE_DID`. 468 + #report_service_did = 469 + 234 470 [import] 235 471 # Whether the PDS accepts repo imports. 236 - accepting = true 472 + # 473 + # Can also be specified via environment variable `ACCEPTING_REPO_IMPORTS`. 474 + # 475 + # Default value: true 476 + #accepting = true 237 477 238 478 # Maximum allowed import archive size in bytes (default 1 GiB). 239 - max_size = 1_073_741_824 479 + # 480 + # Can also be specified via environment variable `MAX_IMPORT_SIZE`. 481 + # 482 + # Default value: 1073741824 483 + #max_size = 1073741824 240 484 241 485 # Maximum number of blocks allowed in an import. 242 - max_blocks = 500_000 486 + # 487 + # Can also be specified via environment variable `MAX_IMPORT_BLOCKS`. 488 + # 489 + # Default value: 500000 490 + #max_blocks = 500000 243 491 244 492 # Skip CAR verification during import. Only for development/debugging. 245 - skip_verification = false 493 + # 494 + # Can also be specified via environment variable `SKIP_IMPORT_VERIFICATION`. 495 + # 496 + # Default value: false 497 + #skip_verification = false 246 498 247 - [crawlers] 248 - # list of relay URLs to notify via requestCrawl. 249 - urls = ["https://bsky.network"] 250 - 251 - [ripple] 252 - # Address to bind the Ripple gossip protocol listener. 253 - bind_addr = "0.0.0.0:0" 254 - 255 - # Comma-separated list of seed peer addresses (for multi-node). 256 - # peers = "10.0.0.2:7890,10.0.0.3:7890" 257 - 258 - # Unique machine identifier. Auto-derived from hostname when not set. 259 - # machine_id = 1 260 - 261 - # Gossip protocol interval in milliseconds. 262 - gossip_interval_ms = 200 263 - 264 - # Maximum cache size in megabytes. 265 - cache_max_mb = 256 266 - 267 499 [scheduled] 268 - # How often to check for scheduled account deletions (default: 1 hour). 269 - delete_check_interval_secs = 3600 500 + # Interval in seconds between scheduled delete checks. 501 + # 502 + # Can also be specified via environment variable `SCHEDULED_DELETE_CHECK_INTERVAL_SECS`. 503 + # 504 + # Default value: 3600 505 + #delete_check_interval_secs = 3600
+2 -2
.env.example
··· 131 131 # Account Registration 132 132 # ============================================================================= 133 133 # Require invite codes for registration 134 - # INVITE_CODE_REQUIRED=false 134 + # INVITE_CODE_REQUIRED=true 135 135 # Comma-separated list of available user domains 136 136 # AVAILABLE_USER_DOMAINS=example.com 137 137 # Enable self-hosted did:web identities (default: true) 138 138 # Hosting did:web requires a long-term commitment to serve DID documents. 139 139 # Set to false if you don't want to offer this option. 140 - # ENABLE_SELF_HOSTED_DID_WEB=true 140 + # ENABLE_PDS_HOSTED_DID_WEB=false 141 141 # ============================================================================= 142 142 # Server Metadata (returned by describeServer) 143 143 # =============================================================================
+4 -1
README.md
··· 24 24 25 25 ## Configuration 26 26 27 - See `.env.example` for all configuration options. 27 + See `example.toml` for all configuration options. 28 + 29 + > [!NOTE] 30 + > The order of configuration precendence is: environment variables, than a config file passed via `--config`, than `/etc/tranquil-pds/config.toml`, than the built-in defaults. So you can use environment variables, or a config file, or both. 28 31 29 32 ## Development 30 33

History

4 rounds 2 comments
sign up or login to add to the discussion
1 commit
expand
c66fe45e
refactor: toml config
expand 0 comments
pull request successfully merged
1 commit
expand
1b32bf4b
refactor: toml config
expand 0 comments
1 commit
expand
21b7ee92
refactor: toml config
expand 1 comment

oookay finally got a chance to try this out and noticed some usability mehs

the blob storage paths and the backup storage paths should have default values. doesnt make much sense to me to not have that.

validate should probably have a --ignore-secrets flag or similar? since most people will probably want to set the secrets in an env file and not in the config file. necessary for the nix module too if we want that to validate the config during build

anywhere possible errors from trying to load the config are printed to the user should also have some more handling to actually print the error properly. as https://github.com/LukasKalbertodt/confique/blob/main/src/error.rs#L9 mentions just printing it doesnt actually do that which ends up giving very cryptic and non-specific messages to the user just saying loading the config failed without giving proper reasons why. just doing e:# seemed fine for now for me locally. in the future we'll probably want to walk the sources properly but it can wait

1 commit
expand
13867bba
refactor: toml config
expand 1 comment

overall looks really good! i only really have some nitpicks about naming and default values.

server.service_handle_domains should be server.user_handle_domains imo. i never quite liked the "service handle domain" name. its confusing imo. and describeServer calls them user domains so i think we should align with that

server.enable_self_hosted_did_web makes it sound like its the opposite of what it is. should be server.enable_pds_hosted_did_web instead. also imo we should default this to false? given the consequences of having this enabled it should be opt-in imo

server.invite_code_required should probably default to true? thats what ref impl does and i think thats sensible

crawlers.urls should be firehose.crawlers imo. i think it makes sense to keep it with the rest of the sync related config (or have a layer of nesting more and have sync.crawlers and sync.firehose. sounds messy tho)

all of ripple.* should probably go under cache.ripple.* (as well as add a cache.backend option) since ripple is an in-house in-process replacement for valkey

also perhaps make it clear in the docs that you can set config options with env vars too? + the config cli flag and env var

idk how i feel about the config static being a OnceLock and not a LazyLock and all the panicing with init() and get(). but i understand getting it to work with a LazyLock is annoying due to the fallibility of config loading. probably going to explore how to handle that in the future. not going to block this PR on that