···131131# Account Registration
132132# =============================================================================
133133# Require invite codes for registration
134134-# INVITE_CODE_REQUIRED=false
134134+# INVITE_CODE_REQUIRED=true
135135# Comma-separated list of available user domains
136136# AVAILABLE_USER_DOMAINS=example.com
137137# Enable self-hosted did:web identities (default: true)
138138# Hosting did:web requires a long-term commitment to serve DID documents.
139139# Set to false if you don't want to offer this option.
140140-# ENABLE_SELF_HOSTED_DID_WEB=true
140140+# ENABLE_PDS_HOSTED_DID_WEB=false
141141# =============================================================================
142142# Server Metadata (returned by describeServer)
143143# =============================================================================
···24242525## Configuration
26262727-See `.env.example` for all configuration options.
2727+See `example.toml` for all configuration options.
2828+2929+> [!NOTE]
3030+> 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.
28312932## Development
3033
···44use crate::circuit_breaker::with_circuit_breaker;
55use crate::plc::{PlcClient, signing_key_to_did_key, validate_plc_operation};
66use crate::state::AppState;
77-use crate::util::pds_hostname;
87use axum::{
98 Json,
109 extract::State,
···4241 .map_err(|e| ApiError::InvalidRequest(format!("Invalid operation: {}", e)))?;
43424443 let op = &input.operation;
4545- let hostname = pds_hostname();
4444+ let hostname = &tranquil_config::get().server.hostname;
4645 let public_url = format!("https://{}", hostname);
4746 let user = state
4847 .user_repo
···7069 })?;
71707271 let user_did_key = signing_key_to_did_key(&signing_key);
7373- let server_rotation_key =
7474- std::env::var("PLC_ROTATION_KEY").unwrap_or_else(|_| user_did_key.clone());
7272+ let server_rotation_key = tranquil_config::get()
7373+ .secrets
7474+ .plc_rotation_key
7575+ .clone()
7676+ .unwrap_or_else(|| user_did_key.clone());
7577 if let Some(rotation_keys) = op.get("rotationKeys").and_then(|v| v.as_array()) {
7678 let has_server_key = rotation_keys
7779 .iter()
+3-2
crates/tranquil-pds/src/api/moderation/mod.rs
···6969}
70707171fn get_report_service_config() -> Option<ReportServiceConfig> {
7272- let url = std::env::var("REPORT_SERVICE_URL").ok()?;
7373- let did = std::env::var("REPORT_SERVICE_DID").ok()?;
7272+ let cfg = tranquil_config::get();
7373+ let url = cfg.moderation.report_service_url.clone()?;
7474+ let did = cfg.moderation.report_service_did.clone()?;
7475 if url.is_empty() || did.is_empty() {
7576 return None;
7677 }
+2-3
crates/tranquil-pds/src/api/notification_prefs.rs
···11use crate::api::error::ApiError;
22use crate::auth::{Active, Auth};
33use crate::state::AppState;
44-use crate::util::pds_hostname;
54use axum::{
65 Json,
76 extract::State,
···148147149148 match channel {
150149 CommsChannel::Email => {
151151- let hostname = pds_hostname();
150150+ let hostname = &tranquil_config::get().server.hostname;
152151 let handle_str = handle.unwrap_or("user");
153152 crate::comms::comms_repo::enqueue_email_update(
154153 state.infra_repo.as_ref(),
···167166 })?;
168167 }
169168 _ => {
170170- let hostname = pds_hostname();
169169+ let hostname = &tranquil_config::get().server.hostname;
171170 let encoded_token = urlencoding::encode(&formatted_token);
172171 let encoded_identifier = urlencoding::encode(identifier);
173172 let verify_link = format!(
+1-1
crates/tranquil-pds/src/api/proxy.rs
···168168 }
169169170170 // If the age assurance override is set and this is an age assurance call then we dont want to proxy even if the client requests it
171171- if std::env::var("PDS_AGE_ASSURANCE_OVERRIDE").is_ok()
171171+ if tranquil_config::get().server.age_assurance_override
172172 && (path.ends_with("app.bsky.ageassurance.getState")
173173 || path.ends_with("app.bsky.unspecced.getAgeAssuranceState"))
174174 {
+1-1
crates/tranquil-pds/src/api/proxy_client.rs
···6363 let parsed = Url::parse(url).map_err(|_| SsrfError::InvalidUrl)?;
6464 let scheme = parsed.scheme();
6565 if scheme != "https" {
6666- let allow_http = std::env::var("ALLOW_HTTP_PROXY").is_ok()
6666+ let allow_http = tranquil_config::get().server.allow_http_proxy
6767 || url.starts_with("http://127.0.0.1")
6868 || url.starts_with("http://localhost");
6969 if !allow_http {
+2-2
crates/tranquil-pds/src/api/repo/blob.rs
···33use crate::delegation::DelegationActionType;
44use crate::state::AppState;
55use crate::types::{CidLink, Did};
66-use crate::util::{get_header_str, get_max_blob_size};
66+use crate::util::get_header_str;
77use axum::body::Body;
88use axum::{
99 Json,
···8989 .ok_or(ApiError::InternalError(None))?;
90909191 let temp_key = format!("temp/{}", uuid::Uuid::new_v4());
9292- let max_size = u64::try_from(get_max_blob_size()).unwrap_or(u64::MAX);
9292+ let max_size = tranquil_config::get().server.max_blob_size;
93939494 let body_stream = body.into_data_stream();
9595 let mapped_stream =
+5-16
crates/tranquil-pds/src/api/repo/import.rs
···1818use tracing::{debug, error, info, warn};
1919use tranquil_types::{AtUri, CidLink};
20202121-const DEFAULT_MAX_IMPORT_SIZE: usize = 1024 * 1024 * 1024;
2222-const DEFAULT_MAX_BLOCKS: usize = 500000;
2323-2421pub async fn import_repo(
2522 State(state): State<AppState>,
2623 auth: Auth<NotTakendown>,
2724 body: Bytes,
2825) -> Result<Response, ApiError> {
2929- let accepting_imports = std::env::var("ACCEPTING_REPO_IMPORTS")
3030- .map(|v| v != "false" && v != "0")
3131- .unwrap_or(true);
2626+ let accepting_imports = tranquil_config::get().import.accepting;
3227 if !accepting_imports {
3328 return Err(ApiError::InvalidRequest(
3429 "Service is not accepting repo imports".into(),
3530 ));
3631 }
3737- let max_size: usize = std::env::var("MAX_IMPORT_SIZE")
3838- .ok()
3939- .and_then(|s| s.parse().ok())
4040- .unwrap_or(DEFAULT_MAX_IMPORT_SIZE);
3232+ let max_size = tranquil_config::get().import.max_size as usize;
4133 if body.len() > max_size {
4234 return Err(ApiError::PayloadTooLarge(format!(
4335 "Import size exceeds limit of {} bytes",
···108100 commit_did, did
109101 )));
110102 }
111111- let skip_verification = crate::util::parse_env_bool("SKIP_IMPORT_VERIFICATION");
103103+ let skip_verification = tranquil_config::get().import.skip_verification;
112104 let is_migration = user.deactivated_at.is_some();
113105 if skip_verification {
114106 warn!("Skipping all CAR verification for import (SKIP_IMPORT_VERIFICATION=true)");
···196188 }
197189 }
198190 }
199199- let max_blocks: usize = std::env::var("MAX_IMPORT_BLOCKS")
200200- .ok()
201201- .and_then(|s| s.parse().ok())
202202- .unwrap_or(DEFAULT_MAX_BLOCKS);
191191+ let max_blocks = tranquil_config::get().import.max_blocks as usize;
203192 let _write_lock = state.repo_write_locks.lock(user_id).await;
204193 match apply_import(
205194 &state.repo_repo,
···324313 {
325314 warn!("Failed to sequence import event: {:?}", e);
326315 }
327327- if std::env::var("PDS_AGE_ASSURANCE_OVERRIDE").is_ok() {
316316+ if tranquil_config::get().server.age_assurance_override {
328317 let birthdate_pref = json!({
329318 "$type": "app.bsky.actor.defs#personalDetailsPref",
330319 "birthDate": "1998-05-06T00:00:00.000Z"
+1-2
crates/tranquil-pds/src/api/repo/meta.rs
···11use crate::api::error::ApiError;
22use crate::state::AppState;
33use crate::types::AtIdentifier;
44-use crate::util::pds_hostname_without_port;
54use axum::{
65 Json,
76 extract::{Query, State},
···1918 State(state): State<AppState>,
2019 Query(input): Query<DescribeRepoInput>,
2120) -> Response {
2222- let hostname_for_handles = pds_hostname_without_port();
2121+ let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
2322 let user_row = if input.repo.is_did() {
2423 let did: crate::types::Did = match input.repo.as_str().parse() {
2524 Ok(d) => d,
+2-3
crates/tranquil-pds/src/api/repo/record/read.rs
···22use crate::api::error::ApiError;
33use crate::state::AppState;
44use crate::types::{AtIdentifier, Nsid, Rkey};
55-use crate::util::pds_hostname_without_port;
65use axum::{
76 Json,
87 extract::{Query, State},
···6059 _headers: HeaderMap,
6160 Query(input): Query<GetRecordInput>,
6261) -> Response {
6363- let hostname_for_handles = pds_hostname_without_port();
6262+ let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
6463 let user_id_opt = if input.repo.is_did() {
6564 let did: crate::types::Did = match input.repo.as_str().parse() {
6665 Ok(d) => d,
···159158 State(state): State<AppState>,
160159 Query(input): Query<ListRecordsInput>,
161160) -> Response {
162162- let hostname_for_handles = pds_hostname_without_port();
161161+ let hostname_for_handles = tranquil_config::get().server.hostname_without_port();
163162 let user_id_opt = if input.repo.is_did() {
164163 let did: crate::types::Did = match input.repo.as_str().parse() {
165164 Ok(d) => d,
···1212 verify_client_auth,
1313};
1414use crate::state::AppState;
1515-use crate::util::pds_hostname;
1615use axum::Json;
1716use axum::http::{HeaderMap, Method};
1817use chrono::{Duration, Utc};
···101100 let dpop_jkt = if let Some(proof) = &dpop_proof {
102101 let config = AuthConfig::get();
103102 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
104104- let pds_hostname = pds_hostname();
103103+ let pds_hostname = &tranquil_config::get().server.hostname;
105104 let token_endpoint = format!("https://{}/oauth/token", pds_hostname);
106105 let result = verifier.verify_proof(proof, Method::POST.as_str(), &token_endpoint, None)?;
107106 if !state
···348347 let dpop_jkt = if let Some(proof) = &dpop_proof {
349348 let config = AuthConfig::get();
350349 let verifier = DPoPVerifier::new(config.dpop_secret().as_bytes());
351351- let pds_hostname = pds_hostname();
350350+ let pds_hostname = &tranquil_config::get().server.hostname;
352351 let token_endpoint = format!("https://{}/oauth/token", pds_hostname);
353352 let result = verifier.verify_proof(proof, Method::POST.as_str(), &token_endpoint, None)?;
354353 if !state
···1818If you just want to get running quickly:
19192020```sh
2121-cp .env.example .env
2121+cp example.toml config.toml
2222```
23232424-Edit `.env` with your values. Generate secrets with `openssl rand -base64 48`.
2424+Edit `config.toml` with your values. Generate secrets with `openssl rand -base64 48`.
25252626Build and start:
2727```sh
···5959```sh
6060podman run -d --name tranquil-pds \
6161 --network=host \
6262- --env-file /etc/tranquil-pds/tranquil-pds.env \
6262+ -v /etc/tranquil-pds/config.toml:/etc/tranquil-pds/config.toml:ro,Z \
6363 -v /var/lib/tranquil:/var/lib/tranquil:Z \
6464 tranquil-pds:latest
6565```
···113113mkdir -p /srv/tranquil-pds/{postgres,blobs,backups,certs,acme,config}
114114```
115115116116-## Create an environment file
116116+## Create a configuration file
117117118118```bash
119119-cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env
120120-chmod 600 /srv/tranquil-pds/config/tranquil-pds.env
119119+cp /opt/tranquil-pds/example.toml /srv/tranquil-pds/config/config.toml
120120+chmod 600 /srv/tranquil-pds/config/config.toml
121121```
122122123123-Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with:
123123+Edit `/srv/tranquil-pds/config/config.toml` and fill in your values. Generate secrets with:
124124```bash
125125openssl rand -base64 48
126126```
127127+128128+> **Note:** Every config option can also be set via environment variables
129129+> (see comments in `example.toml`). Environment variables always take
130130+> precedence over the config file.
127131128132## Install quadlet definitions
129133···157161## Create podman secrets
158162159163```bash
160160-source /srv/tranquil-pds/config/tranquil-pds.env
161164echo "$DB_PASSWORD" | podman secret create tranquil-pds-db-password -
162165```
163166···264267podman build -t tranquil-pds-frontend:latest ./frontend
265268```
266269267267-## Create an environment file
270270+## Create a configuration file
268271269272```sh
270270-cp /opt/tranquil-pds/.env.example /srv/tranquil-pds/config/tranquil-pds.env
271271-chmod 600 /srv/tranquil-pds/config/tranquil-pds.env
273273+cp /opt/tranquil-pds/example.toml /srv/tranquil-pds/config/config.toml
274274+chmod 600 /srv/tranquil-pds/config/config.toml
272275```
273276274274-Edit `/srv/tranquil-pds/config/tranquil-pds.env` and fill in your values. Generate secrets with:
277277+Edit `/srv/tranquil-pds/config/config.toml` and fill in your values. Generate secrets with:
275278```sh
276279openssl rand -base64 48
277280```
281281+282282+> **Note:** Every config option can also be set via environment variables
283283+> (see comments in `example.toml`). Environment variables always take
284284+> precedence over the config file.
278285279286## Set up compose and nginx
280287···308315 after firewall
309316}
310317start_pre() {
311311- set -a
312312- . /srv/tranquil-pds/config/tranquil-pds.env
313313- set +a
318318+ checkpath -d /srv/tranquil-pds
314319}
315320stop() {
316321 ebegin "Stopping ${name}"
317322 cd /srv/tranquil-pds
318318- set -a
319319- . /srv/tranquil-pds/config/tranquil-pds.env
320320- set +a
321323 podman-compose -f /srv/tranquil-pds/docker-compose.yml down
322324 eend $?
323325}
+14-5
docs/install-debian.md
···73737474```bash
7575mkdir -p /etc/tranquil-pds
7676-cp /opt/tranquil-pds/.env.example /etc/tranquil-pds/tranquil-pds.env
7777-chmod 600 /etc/tranquil-pds/tranquil-pds.env
7676+cp /opt/tranquil-pds/example.toml /etc/tranquil-pds/config.toml
7777+chmod 600 /etc/tranquil-pds/config.toml
7878```
79798080-Edit `/etc/tranquil-pds/tranquil-pds.env` and fill in your values. Generate secrets with:
8080+Edit `/etc/tranquil-pds/config.toml` and fill in your values. Generate secrets with:
8181```bash
8282openssl rand -base64 48
8383+```
8484+8585+> **Note:** Every config option can also be set via environment variables
8686+> (see comments in `example.toml`). Environment variables always take
8787+> precedence over the config file. You can also pass the config file path
8888+> via the `TRANQUIL_PDS_CONFIG` env var instead of `--config`.
8989+9090+You can validate your configuration before starting the service:
9191+```bash
9292+/usr/local/bin/tranquil-pds --config /etc/tranquil-pds/config.toml validate
8393```
84948595## Install frontend files
···105115Type=simple
106116User=tranquil-pds
107117Group=tranquil-pds
108108-EnvironmentFile=/etc/tranquil-pds/tranquil-pds.env
109109-ExecStart=/usr/local/bin/tranquil-pds
118118+ExecStart=/usr/local/bin/tranquil-pds --config /etc/tranquil-pds/config.toml
110119Restart=always
111120RestartSec=5
112121ProtectSystem=strict
+4-1
docs/install-kubernetes.md
···99You'll need a wildcard TLS certificate for `*.your-pds-hostname.example.com`. User handles are served as subdomains.
10101111The container image expects:
1212+- A TOML config file mounted at `/etc/tranquil-pds/config.toml` (or passed via `--config`)
1213- `DATABASE_URL` - postgres connection string
1314- `BLOB_STORAGE_PATH` - path to blob storage (mount a PV here)
1415- `BACKUP_STORAGE_PATH` - path for repo backups (optional but recommended)
1516- `PDS_HOSTNAME` - your PDS hostname (without protocol)
1617- `JWT_SECRET`, `DPOP_SECRET`, `MASTER_KEY` - generate with `openssl rand -base64 48`
1718- `CRAWLERS` - typically `https://bsky.network`
1818-and more, check the .env.example.
1919+2020+and more, check the example.toml for all options. Environment variables can override any TOML value.
2121+You can also point to a config file via the `TRANQUIL_PDS_CONFIG` env var.
19222023Health check: `GET /xrpc/_health`
2124
+509
example.toml
···11+[server]
22+# Public hostname of the PDS (e.g. `pds.example.com`).
33+#
44+# Can also be specified via environment variable `PDS_HOSTNAME`.
55+#
66+# Required! This value must be specified.
77+#hostname =
88+99+# Address to bind the HTTP server to.
1010+#
1111+# Can also be specified via environment variable `SERVER_HOST`.
1212+#
1313+# Default value: "127.0.0.1"
1414+#host = "127.0.0.1"
1515+1616+# Port to bind the HTTP server to.
1717+#
1818+# Can also be specified via environment variable `SERVER_PORT`.
1919+#
2020+# Default value: 3000
2121+#port = 3000
2222+2323+# List of domains for user handles.
2424+# Defaults to the PDS hostname when not set.
2525+#
2626+# Can also be specified via environment variable `PDS_USER_HANDLE_DOMAINS`.
2727+#user_handle_domains =
2828+2929+# List of domains available for user registration.
3030+# Defaults to the PDS hostname when not set.
3131+#
3232+# Can also be specified via environment variable `AVAILABLE_USER_DOMAINS`.
3333+#available_user_domains =
3434+3535+# Enable PDS-hosted did:web identities. Hosting did:web requires a
3636+# long-term commitment to serve DID documents; opt-in only.
3737+#
3838+# Can also be specified via environment variable `ENABLE_PDS_HOSTED_DID_WEB`.
3939+#
4040+# Default value: false
4141+#enable_pds_hosted_did_web = false
4242+4343+# When set to true, skip age-assurance birthday prompt for all accounts.
4444+#
4545+# Can also be specified via environment variable `PDS_AGE_ASSURANCE_OVERRIDE`.
4646+#
4747+# Default value: false
4848+#age_assurance_override = false
4949+5050+# Require an invite code for new account registration.
5151+#
5252+# Can also be specified via environment variable `INVITE_CODE_REQUIRED`.
5353+#
5454+# Default value: true
5555+#invite_code_required = true
5656+5757+# Allow HTTP (non-TLS) proxy requests. Only useful during development.
5858+#
5959+# Can also be specified via environment variable `ALLOW_HTTP_PROXY`.
6060+#
6161+# Default value: false
6262+#allow_http_proxy = false
6363+6464+# Disable all rate limiting. Should only be used in testing.
6565+#
6666+# Can also be specified via environment variable `DISABLE_RATE_LIMITING`.
6767+#
6868+# Default value: false
6969+#disable_rate_limiting = false
7070+7171+# List of additional banned words for handle validation.
7272+#
7373+# Can also be specified via environment variable `PDS_BANNED_WORDS`.
7474+#banned_words =
7575+7676+# URL to a privacy policy page.
7777+#
7878+# Can also be specified via environment variable `PRIVACY_POLICY_URL`.
7979+#privacy_policy_url =
8080+8181+# URL to terms of service page.
8282+#
8383+# Can also be specified via environment variable `TERMS_OF_SERVICE_URL`.
8484+#terms_of_service_url =
8585+8686+# Operator contact email address.
8787+#
8888+# Can also be specified via environment variable `CONTACT_EMAIL`.
8989+#contact_email =
9090+9191+# Maximum allowed blob size in bytes (default 10 GiB).
9292+#
9393+# Can also be specified via environment variable `MAX_BLOB_SIZE`.
9494+#
9595+# Default value: 10737418240
9696+#max_blob_size = 10737418240
9797+9898+[database]
9999+# PostgreSQL connection URL.
100100+#
101101+# Can also be specified via environment variable `DATABASE_URL`.
102102+#
103103+# Required! This value must be specified.
104104+#url =
105105+106106+# Maximum number of connections in the pool.
107107+#
108108+# Can also be specified via environment variable `DATABASE_MAX_CONNECTIONS`.
109109+#
110110+# Default value: 100
111111+#max_connections = 100
112112+113113+# Minimum number of idle connections kept in the pool.
114114+#
115115+# Can also be specified via environment variable `DATABASE_MIN_CONNECTIONS`.
116116+#
117117+# Default value: 10
118118+#min_connections = 10
119119+120120+# Timeout in seconds when acquiring a connection from the pool.
121121+#
122122+# Can also be specified via environment variable `DATABASE_ACQUIRE_TIMEOUT_SECS`.
123123+#
124124+# Default value: 10
125125+#acquire_timeout_secs = 10
126126+127127+[secrets]
128128+# Secret used for signing JWTs. Must be at least 32 characters in
129129+# production.
130130+#
131131+# Can also be specified via environment variable `JWT_SECRET`.
132132+#jwt_secret =
133133+134134+# Secret used for DPoP proof validation. Must be at least 32 characters
135135+# in production.
136136+#
137137+# Can also be specified via environment variable `DPOP_SECRET`.
138138+#dpop_secret =
139139+140140+# Master key used for key-encryption and HKDF derivation. Must be at
141141+# least 32 characters in production.
142142+#
143143+# Can also be specified via environment variable `MASTER_KEY`.
144144+#master_key =
145145+146146+# PLC rotation key (DID key). If not set, user-level keys are used.
147147+#
148148+# Can also be specified via environment variable `PLC_ROTATION_KEY`.
149149+#plc_rotation_key =
150150+151151+# Allow insecure/test secrets. NEVER enable in production.
152152+#
153153+# Can also be specified via environment variable `TRANQUIL_PDS_ALLOW_INSECURE_SECRETS`.
154154+#
155155+# Default value: false
156156+#allow_insecure = false
157157+158158+[storage]
159159+# Storage backend: `filesystem` or `s3`.
160160+#
161161+# Can also be specified via environment variable `BLOB_STORAGE_BACKEND`.
162162+#
163163+# Default value: "filesystem"
164164+#backend = "filesystem"
165165+166166+# Path on disk for the filesystem blob backend.
167167+#
168168+# Can also be specified via environment variable `BLOB_STORAGE_PATH`.
169169+#
170170+# Default value: "/var/lib/tranquil-pds/blobs"
171171+#path = "/var/lib/tranquil-pds/blobs"
172172+173173+# S3 bucket name for blob storage.
174174+#
175175+# Can also be specified via environment variable `S3_BUCKET`.
176176+#s3_bucket =
177177+178178+# Custom S3 endpoint URL (for MinIO, R2, etc.).
179179+#
180180+# Can also be specified via environment variable `S3_ENDPOINT`.
181181+#s3_endpoint =
182182+183183+[backup]
184184+# Enable automatic backups.
185185+#
186186+# Can also be specified via environment variable `BACKUP_ENABLED`.
187187+#
188188+# Default value: true
189189+#enabled = true
190190+191191+# Backup storage backend: `filesystem` or `s3`.
192192+#
193193+# Can also be specified via environment variable `BACKUP_STORAGE_BACKEND`.
194194+#
195195+# Default value: "filesystem"
196196+#backend = "filesystem"
197197+198198+# Path on disk for the filesystem backup backend.
199199+#
200200+# Can also be specified via environment variable `BACKUP_STORAGE_PATH`.
201201+#
202202+# Default value: "/var/lib/tranquil-pds/backups"
203203+#path = "/var/lib/tranquil-pds/backups"
204204+205205+# S3 bucket name for backups.
206206+#
207207+# Can also be specified via environment variable `BACKUP_S3_BUCKET`.
208208+#s3_bucket =
209209+210210+# Number of backup revisions to keep per account.
211211+#
212212+# Can also be specified via environment variable `BACKUP_RETENTION_COUNT`.
213213+#
214214+# Default value: 7
215215+#retention_count = 7
216216+217217+# Seconds between backup runs.
218218+#
219219+# Can also be specified via environment variable `BACKUP_INTERVAL_SECS`.
220220+#
221221+# Default value: 86400
222222+#interval_secs = 86400
223223+224224+[cache]
225225+# Cache backend: `ripple` (default, built-in gossip) or `valkey`.
226226+#
227227+# Can also be specified via environment variable `CACHE_BACKEND`.
228228+#
229229+# Default value: "ripple"
230230+#backend = "ripple"
231231+232232+# Valkey / Redis connection URL. Required when `backend = "valkey"`.
233233+#
234234+# Can also be specified via environment variable `VALKEY_URL`.
235235+#valkey_url =
236236+237237+[cache.ripple]
238238+# Address to bind the Ripple gossip protocol listener.
239239+#
240240+# Can also be specified via environment variable `RIPPLE_BIND`.
241241+#
242242+# Default value: "0.0.0.0:0"
243243+#bind_addr = "0.0.0.0:0"
244244+245245+# List of seed peer addresses.
246246+#
247247+# Can also be specified via environment variable `RIPPLE_PEERS`.
248248+#peers =
249249+250250+# Unique machine identifier. Auto-derived from hostname when not set.
251251+#
252252+# Can also be specified via environment variable `RIPPLE_MACHINE_ID`.
253253+#machine_id =
254254+255255+# Gossip protocol interval in milliseconds.
256256+#
257257+# Can also be specified via environment variable `RIPPLE_GOSSIP_INTERVAL_MS`.
258258+#
259259+# Default value: 200
260260+#gossip_interval_ms = 200
261261+262262+# Maximum cache size in megabytes.
263263+#
264264+# Can also be specified via environment variable `RIPPLE_CACHE_MAX_MB`.
265265+#
266266+# Default value: 256
267267+#cache_max_mb = 256
268268+269269+[plc]
270270+# Base URL of the PLC directory.
271271+#
272272+# Can also be specified via environment variable `PLC_DIRECTORY_URL`.
273273+#
274274+# Default value: "https://plc.directory"
275275+#directory_url = "https://plc.directory"
276276+277277+# HTTP request timeout in seconds.
278278+#
279279+# Can also be specified via environment variable `PLC_TIMEOUT_SECS`.
280280+#
281281+# Default value: 10
282282+#timeout_secs = 10
283283+284284+# TCP connect timeout in seconds.
285285+#
286286+# Can also be specified via environment variable `PLC_CONNECT_TIMEOUT_SECS`.
287287+#
288288+# Default value: 5
289289+#connect_timeout_secs = 5
290290+291291+# Seconds to cache DID documents in memory.
292292+#
293293+# Can also be specified via environment variable `DID_CACHE_TTL_SECS`.
294294+#
295295+# Default value: 300
296296+#did_cache_ttl_secs = 300
297297+298298+[firehose]
299299+# Size of the in-memory broadcast buffer for firehose events.
300300+#
301301+# Can also be specified via environment variable `FIREHOSE_BUFFER_SIZE`.
302302+#
303303+# Default value: 10000
304304+#buffer_size = 10000
305305+306306+# How many hours of historical events to replay for cursor-based
307307+# firehose connections.
308308+#
309309+# Can also be specified via environment variable `FIREHOSE_BACKFILL_HOURS`.
310310+#
311311+# Default value: 72
312312+#backfill_hours = 72
313313+314314+# Maximum number of lagged events before disconnecting a slow consumer.
315315+#
316316+# Can also be specified via environment variable `FIREHOSE_MAX_LAG`.
317317+#
318318+# Default value: 5000
319319+#max_lag = 5000
320320+321321+# List of relay / crawler notification URLs.
322322+#
323323+# Can also be specified via environment variable `CRAWLERS`.
324324+#crawlers =
325325+326326+[email]
327327+# Sender email address. When unset, email sending is disabled.
328328+#
329329+# Can also be specified via environment variable `MAIL_FROM_ADDRESS`.
330330+#from_address =
331331+332332+# Display name used in the `From` header.
333333+#
334334+# Can also be specified via environment variable `MAIL_FROM_NAME`.
335335+#
336336+# Default value: "Tranquil PDS"
337337+#from_name = "Tranquil PDS"
338338+339339+# Path to the `sendmail` binary.
340340+#
341341+# Can also be specified via environment variable `SENDMAIL_PATH`.
342342+#
343343+# Default value: "/usr/sbin/sendmail"
344344+#sendmail_path = "/usr/sbin/sendmail"
345345+346346+[discord]
347347+# Discord bot token. When unset, Discord integration is disabled.
348348+#
349349+# Can also be specified via environment variable `DISCORD_BOT_TOKEN`.
350350+#bot_token =
351351+352352+[telegram]
353353+# Telegram bot token. When unset, Telegram integration is disabled.
354354+#
355355+# Can also be specified via environment variable `TELEGRAM_BOT_TOKEN`.
356356+#bot_token =
357357+358358+# Secret token for incoming webhook verification.
359359+#
360360+# Can also be specified via environment variable `TELEGRAM_WEBHOOK_SECRET`.
361361+#webhook_secret =
362362+363363+[signal]
364364+# Path to the `signal-cli` binary.
365365+#
366366+# Can also be specified via environment variable `SIGNAL_CLI_PATH`.
367367+#
368368+# Default value: "/usr/local/bin/signal-cli"
369369+#cli_path = "/usr/local/bin/signal-cli"
370370+371371+# Sender phone number. When unset, Signal integration is disabled.
372372+#
373373+# Can also be specified via environment variable `SIGNAL_SENDER_NUMBER`.
374374+#sender_number =
375375+376376+[notifications]
377377+# Polling interval in milliseconds for the comms queue.
378378+#
379379+# Can also be specified via environment variable `NOTIFICATION_POLL_INTERVAL_MS`.
380380+#
381381+# Default value: 1000
382382+#poll_interval_ms = 1000
383383+384384+# Number of notifications to process per batch.
385385+#
386386+# Can also be specified via environment variable `NOTIFICATION_BATCH_SIZE`.
387387+#
388388+# Default value: 100
389389+#batch_size = 100
390390+391391+[sso]
392392+[sso.github]
393393+# Default value: false
394394+#enabled = false
395395+396396+#client_id =
397397+398398+#client_secret =
399399+400400+#display_name =
401401+402402+[sso.discord]
403403+# Default value: false
404404+#enabled = false
405405+406406+#client_id =
407407+408408+#client_secret =
409409+410410+#display_name =
411411+412412+[sso.google]
413413+# Default value: false
414414+#enabled = false
415415+416416+#client_id =
417417+418418+#client_secret =
419419+420420+#display_name =
421421+422422+[sso.gitlab]
423423+# Default value: false
424424+#enabled = false
425425+426426+#client_id =
427427+428428+#client_secret =
429429+430430+#issuer =
431431+432432+#display_name =
433433+434434+[sso.oidc]
435435+# Default value: false
436436+#enabled = false
437437+438438+#client_id =
439439+440440+#client_secret =
441441+442442+#issuer =
443443+444444+#display_name =
445445+446446+[sso.apple]
447447+# Can also be specified via environment variable `SSO_APPLE_ENABLED`.
448448+# Default value: false
449449+#enabled = false
450450+451451+# Can also be specified via environment variable `SSO_APPLE_CLIENT_ID`.
452452+#client_id =
453453+454454+# Can also be specified via environment variable `SSO_APPLE_TEAM_ID`.
455455+#team_id =
456456+457457+# Can also be specified via environment variable `SSO_APPLE_KEY_ID`.
458458+#key_id =
459459+460460+# Can also be specified via environment variable `SSO_APPLE_PRIVATE_KEY`.
461461+#private_key =
462462+463463+[moderation]
464464+# External report-handling service URL.
465465+#
466466+# Can also be specified via environment variable `REPORT_SERVICE_URL`.
467467+#report_service_url =
468468+469469+# DID of the external report-handling service.
470470+#
471471+# Can also be specified via environment variable `REPORT_SERVICE_DID`.
472472+#report_service_did =
473473+474474+[import]
475475+# Whether the PDS accepts repo imports.
476476+#
477477+# Can also be specified via environment variable `ACCEPTING_REPO_IMPORTS`.
478478+#
479479+# Default value: true
480480+#accepting = true
481481+482482+# Maximum allowed import archive size in bytes (default 1 GiB).
483483+#
484484+# Can also be specified via environment variable `MAX_IMPORT_SIZE`.
485485+#
486486+# Default value: 1073741824
487487+#max_size = 1073741824
488488+489489+# Maximum number of blocks allowed in an import.
490490+#
491491+# Can also be specified via environment variable `MAX_IMPORT_BLOCKS`.
492492+#
493493+# Default value: 500000
494494+#max_blocks = 500000
495495+496496+# Skip CAR verification during import. Only for development/debugging.
497497+#
498498+# Can also be specified via environment variable `SKIP_IMPORT_VERIFICATION`.
499499+#
500500+# Default value: false
501501+#skip_verification = false
502502+503503+[scheduled]
504504+# Interval in seconds between scheduled delete checks.
505505+#
506506+# Can also be specified via environment variable `SCHEDULED_DELETE_CHECK_INTERVAL_SECS`.
507507+#
508508+# Default value: 3600
509509+#delete_check_interval_secs = 3600