# SyncTV Configuration Example # # Copy this file to synctv.yaml and modify as needed # # Configuration priority: defaults < config file < environment variables # # Secret-like settings also support sibling *_file keys in this config file. # Relative *_file paths are resolved relative to the config file location. # # Environment variables use SYNCTV_ prefix with single underscore separator: # SYNCTV_SERVER_HOST=0.0.0.0 # SYNCTV_DATABASE_URL=postgresql://... # SYNCTV_DATABASE_URL_FILE=/run/secrets/database_url # SYNCTV_OAUTH2_REDIRECT_SCHEME=https # # Secret-like settings support two file-based forms: # 1. Config file sibling keys: jwt.secret_file, management.auth_token_file, ... # 2. Env file variables: SYNCTV_JWT_SECRET_FILE, SYNCTV_MANAGEMENT_AUTH_TOKEN_FILE, ... server: host: "0.0.0.0" port: 8080 enable_reflection: false # Maximum gRPC message size in bytes (both incoming and outgoing). # Prevents OOM attacks from oversized messages. Default: 16MB (16777216 bytes). # Minimum: 1MB (1048576 bytes), Maximum: 1GB (1073741824 bytes). # Environment variable: SYNCTV_SERVER_GRPC_MAX_MESSAGE_SIZE_BYTES # grpc_max_message_size_bytes: 16777216 # Trusted proxy IP addresses/CIDRs for X-Forwarded-For validation. # If empty, X-Forwarded-For headers are NOT trusted (socket address is used). # Example: ["10.0.0.0/8", "192.168.0.0/16"] trusted_proxies: [] # CORS allowed origins for cross-origin requests from web frontends. # # You MUST set this to your frontend URL(s) or cross-origin # requests (fetch, WebSocket upgrades from browsers) will be rejected. # # Examples: # cors_allowed_origins: ["https://app.example.com"] # cors_allowed_origins: ["https://app.example.com", "https://admin.example.com"] # # Environment variable: # SYNCTV_SERVER_CORS_ALLOWED_ORIGINS='["https://app.example.com"]' # # WARNING: An empty list in production means ALL cross-origin requests are denied. cors_allowed_origins: [] # Shared secret for authenticating cluster gRPC calls between nodes. # REQUIRED when cluster.enabled is true — startup fails if this is empty in cluster mode. # This prevents unauthorized inter-node gRPC calls in multi-replica deployments. # Not required in standalone mode even when Redis is configured. # Generate a secret with: openssl rand -hex 32 # Environment variable: SYNCTV_SERVER_CLUSTER_SECRET # Environment file variable: SYNCTV_SERVER_CLUSTER_SECRET_FILE # File-based secret: server.cluster_secret_file: "/run/secrets/cluster_secret" cluster_secret: "" # Advertise host for cluster node registration (address other nodes use to reach this instance). # Falls back to POD_IP env var, then system hostname if empty. # In Kubernetes, set via SYNCTV_SERVER_ADVERTISE_HOST or downward API (status.podIP). advertise_host: "" # Maximum time in seconds to wait for active connections to drain during shutdown. # Defaults to 30 seconds. Increase for deployments with many long-lived connections. shutdown_drain_timeout_seconds: 30 time: # Default IANA timezone used for human-readable time output and local datetime parsing. # Resolution priority: # 1. time.timezone # 2. SYNCTV_TIME_TIMEZONE # 3. TZ # 4. system timezone # 5. UTC # # Examples: "Asia/Shanghai", "UTC", "America/New_York" # Leave empty to use env/system detection. timezone: "" # Public IDs are prefixed decimal IDs by default, for example usr_1 and room_1. # Set sqids to enable prefixed sqids instead, for example usr_. # Environment variables: # SYNCTV_PUBLIC_IDS_SQIDS_ALPHABET # SYNCTV_PUBLIC_IDS_SQIDS_MIN_LENGTH public_ids: {} # public_ids: # sqids: # alphabet: null # min_length: 12 security: # AES-256-GCM key used to encrypt sensitive provider credentials. # Prefer file-based secrets so keys are not stored inline in config files. # Generate with: openssl rand -hex 32 # Environment variables: # SYNCTV_SECURITY_CREDENTIAL_ENCRYPTION_KEY # SYNCTV_SECURITY_CREDENTIAL_ENCRYPTION_KEY_FILE # credential_encryption_key_file: "/run/secrets/credential_encryption_key" credential_encryption_key: "" # Stable secret used to derive the OPAQUE server setup for password authentication. # Keep this independent from jwt.secret so JWT rotation does not invalidate OPAQUE password records. # Generate with: openssl rand -base64 48 # Environment variables: # SYNCTV_SECURITY_OPAQUE_SERVER_SETUP_SECRET # SYNCTV_SECURITY_OPAQUE_SERVER_SETUP_SECRET_FILE # opaque_server_setup_secret_file: "/run/secrets/opaque_server_setup_secret" opaque_server_setup_secret: "" metrics: # Dedicated metrics listener. When enabled, /metrics is not served on the API port. enabled: false host: "0.0.0.0" port: 9090 # tls: # enabled: true # # Static input files: relative cert/key paths stay relative to this config file. # # They are NOT rebased through data_dir. # cert_path: "./tls/metrics.crt" # key_path: "./tls/metrics.key" auth: # Authentication mode for /metrics: bearer_token, basic, kubernetes mode: "bearer_token" bearer_token: "" # Environment variable: SYNCTV_METRICS_AUTH_BEARER_TOKEN_FILE # bearer_token_file: "/run/secrets/metrics_bearer_token" # basic_username: "metrics" # basic_password: "" # Environment variable: SYNCTV_METRICS_AUTH_BASIC_PASSWORD_FILE # basic_password_file: "/run/secrets/metrics_basic_password" management: # Local-only management daemon transport used by the `synctv` CLI for remote lifecycle commands. # Unix-like platforms default to a Unix domain socket for daemon-style local access. # Switch transport to "tcp" when the CLI must connect over a TCP loopback endpoint. # TCP transport REQUIRES auth_token; startup fails if it is empty. # Unix transport also supports auth_token, but defaults to empty and may rely on # owner-only socket permissions instead. # Runtime-owned local paths use the top-level `data_dir` root shown below. # Leave unix_socket_path unset to use the platform default under data_dir/run/. # Environment variables: # SYNCTV_DATA_DIR=/var/lib/synctv # SYNCTV_MANAGEMENT_ENABLED=true # SYNCTV_MANAGEMENT_TRANSPORT=unix # SYNCTV_MANAGEMENT_UNIX_SOCKET_PATH=/path/to/synctv.sock # SYNCTV_MANAGEMENT_PORT=50052 # SYNCTV_MANAGEMENT_AUTH_TOKEN=replace-with-a-random-secret # SYNCTV_MANAGEMENT_AUTH_TOKEN_FILE=/run/secrets/management_auth_token enabled: true transport: "unix" port: 50052 # Relative unix_socket_path values are resolved from data_dir. # unix_socket_path: "/absolute/path/to/synctv.sock" # auth_token: "replace-with-a-random-secret" # auth_token_file is a static config input. Relative paths stay relative to this config file. # auth_token_file: "/run/secrets/management_auth_token" # Reflection can be configured independently from the public API gRPC listener. enable_reflection: false # `data_dir` is the shared root for runtime-owned local files. # It affects relative: # - management.unix_socket_path # - logging.file_path # - livestream.hls_storage_path # - cache.proxy_slice_file_cache_dir # It does NOT affect: # - *_file secret paths (they stay relative to this config file) # - metrics.tls.cert_path / metrics.tls.key_path # CLI `--data-dir` and env `SYNCTV_DATA_DIR` override this value. # data_dir: "/var/lib/synctv" database: # Environment variables: # SYNCTV_DATABASE_URL # SYNCTV_DATABASE_URL_FILE # Split configuration is also supported: # SYNCTV_DATABASE_HOST / PORT / USERNAME / PASSWORD / PASSWORD_FILE / NAME # `user` is accepted as a config-file alias for `username`; # SYNCTV_DATABASE_USER is accepted as an env alias for SYNCTV_DATABASE_USERNAME. url: "postgresql://synctv:synctv@localhost:5432/synctv" # url_file: "/run/secrets/database_url" # host: "localhost" # port: 5432 # username: "synctv" # password_file: "/run/secrets/database_password" # name: "synctv" max_connections: 20 min_connections: 5 connect_timeout_seconds: 10 idle_timeout_seconds: 600 # Maximum lifetime of a connection in seconds before it is closed and replaced. # Defaults to 1800 (30 minutes). max_lifetime_seconds: 1800 # ============================================================================ # Redis Configuration # ============================================================================ # # Redis is OPTIONAL in standalone mode (cluster.enabled=false) and MANDATORY # in cluster mode (cluster.enabled=true). # # When Redis is not configured (url is empty), all features still work using # in-memory fallbacks: # - Token blacklist → in-memory cache (lost on restart) # - Rate limiting → in-memory counters (lost on restart) # - Brute-force protection → in-memory tracking (lost on restart) # - Username/user/room caches → in-memory only # - OAuth2 state → in-memory store # - Livestream registry → in-memory publisher tracking # - Migration locking → PostgreSQL advisory locks # - Leader election → single node is always the leader # # When Redis IS configured in standalone mode, it enhances features with # persistence across restarts and shared state. # # Redis provides critical infrastructure for: # - Token blacklist (JWT revocation across replicas) # - Rate limiting # - Username cache # - Cache invalidation (Redis Streams with per-node consumer groups) # - Cluster coordination (pub/sub for room events, kicks, etc.) # # Deployment Modes: # - standalone: Single Redis instance (default, OK for development) # - sentinel: Redis Sentinel for high availability # # IMPORTANT: Standalone Redis is a single point of failure (SPOF). For production # deployments, use sentinel mode with 3+ sentinel nodes monitoring a master-replica setup. redis: # Leave empty to run without Redis in standalone mode. # Environment variables: # SYNCTV_REDIS_URL # SYNCTV_REDIS_URL_FILE # Split configuration also supports: # SYNCTV_REDIS_HOST / PORT / USERNAME / PASSWORD / PASSWORD_FILE / DATABASE # `user` is accepted as a config-file alias for `username`; # SYNCTV_REDIS_USER is accepted as an env alias for SYNCTV_REDIS_USERNAME. url: "redis://localhost:6379" # url_file: "/run/secrets/redis_url" # host: "localhost" # port: 6379 # username: "" # password_file: "/run/secrets/redis_password" # database: 0 connect_timeout_seconds: 5 key_prefix: "synctv:" # Deployment mode: standalone or sentinel deployment_mode: "standalone" # --- Sentinel Mode Configuration (for high availability) --- # When deployment_mode is "sentinel", these fields are required: # sentinel_master_name: "mymaster" # sentinel_addresses: # - "redis://sentinel1:26379" # - "redis://sentinel2:26379" # - "redis://sentinel3:26379" jwt: # REQUIRED in production. Generate with: openssl rand -base64 32 # Minimum 32 characters with high entropy (at least 16 unique characters). # Environment variables: # SYNCTV_JWT_SECRET # SYNCTV_JWT_SECRET_FILE secret: "" # secret_file is a static config input. Relative paths stay relative to this config file, # not data_dir. # secret_file: "/run/secrets/jwt_secret" access_token_duration_hours: 1 refresh_token_duration_days: 30 # Duration for guest (anonymous) access tokens guest_token_duration_hours: 4 # Clock skew leeway for token validation (seconds) clock_skew_leeway_secs: 60 logging: level: "info" format: "pretty" # Relative file_path values are treated as runtime-owned output and resolved from data_dir. # file_path: "/var/log/synctv/app.log" cache: # User/room cache capacities and TTLs. l1_capacity: 500 l1_ttl_seconds: 300 l2_ttl_seconds: 300 username_cache_capacity: 1000 username_cache_ttl_seconds: 3600 permission_cache_capacity: 1000 permission_cache_ttl_seconds: 300 # Enable range-based proxy slice caching at process startup. # This is not dynamically toggled at runtime. proxy_slice_cache_enabled: true # Enable persisted proxy slice files on local/shared storage. proxy_slice_file_backend_enabled: false # Relative proxy_slice_file_cache_dir values are resolved from data_dir. # Leave empty to use the built-in default under data_dir/cache/proxy-slice when enabled. # proxy_slice_file_cache_dir: "cache/proxy-slice" livestream: rtmp_port: 1935 gop_cache_size: 2 stream_timeout_seconds: 300 # How often to check for idle streams (seconds) cleanup_check_interval_seconds: 60 # Max retries for pull stream connections pull_max_retries: 10 # Initial backoff for pull retries (milliseconds) pull_initial_backoff_ms: 1000 # Max backoff for pull retries (milliseconds) pull_max_backoff_ms: 30000 # Max FLV tag size to accept in bytes (prevents OOM). Default: 10MB max_flv_tag_size_bytes: 10485760 # Maximum memory (in megabytes) for the GOP cache across all GOPs per stream. # When exceeded, the oldest GOP is evicted even if gop_cache_size hasn't # been reached. Default: 100 MB. Set to 0 to use the built-in default (50 MB). gop_cache_max_memory_mb: 100 # Maximum memory (in megabytes) for in-memory HLS segment storage. # 0 means use the built-in default. hls_memory_max_mb: 0 # Set true only when hls_storage_path points to storage shared by all replicas. hls_shared_storage: false # Relative hls_storage_path values are resolved from data_dir. # This is runtime-owned storage, unlike *_file secrets or metrics TLS files. # hls_storage_path: "livestream/hls" # Maximum HTTP-FLV connection duration and slow-client write timeout. flv_max_connection_duration_seconds: 86400 flv_write_timeout_seconds: 30 # ============================================================================ # Email Configuration (SMTP) # ============================================================================ # # Configure SMTP to enable email features (verification, notifications). # Leave smtp_host empty to disable email functionality. email: smtp_host: "" smtp_port: 587 smtp_username: "" # Environment variables: # SYNCTV_EMAIL_SMTP_PASSWORD # SYNCTV_EMAIL_SMTP_PASSWORD_FILE smtp_password: "" # smtp_password_file is a static config input. Relative paths stay relative to this config file. # smtp_password_file: "/run/secrets/smtp_password" from_email: "" from_name: "SyncTV" use_tls: true # ============================================================================ # Password Complexity Requirements # ============================================================================ # # Controls password strength validation for user account passwords. # These settings do NOT apply to room passwords (which have simpler requirements). password_complexity: # Minimum password length min_length: 8 # Require at least one uppercase letter require_uppercase: true # Require at least one lowercase letter require_lowercase: true # Require at least one digit require_digit: true # Require at least one special character (e.g., !@#$%^&*) require_special: false # Maximum consecutive repeated characters allowed (prevents "aaaaaa"). # Set to 0 to disable this check. max_repeated_chars: 3 # ============================================================================ # Connection Limits # ============================================================================ # # Controls concurrent WebSocket/streaming connection limits. connection_limits: # Maximum concurrent connections per user max_per_user: 5 # Maximum concurrent connections per room max_per_room: 200 # Maximum total concurrent connections max_total: 10000 # Idle timeout in seconds (disconnect if no activity) idle_timeout_seconds: 300 # Maximum connection duration in seconds (24 hours) max_duration_seconds: 86400 # Global per-connection WebSocket message rate limit (messages per second). # Prevents abuse from flooding the server with rapid messages. ws_message_rate_limit_per_second: 50 # ============================================================================ # Messaging Rate Limits # ============================================================================ # # Domain-level chat and danmaku limits enforced by the shared messaging logic. messaging_rate_limits: # Maximum chat messages per sliding window. chat_per_second: 10 # Maximum danmaku messages per sliding window. danmaku_per_second: 3 # Sliding-window size in seconds. window_seconds: 1 # ============================================================================ # HTTP API Rate Limits # ============================================================================ http_rate_limits: auth_max_requests: 5 auth_window_seconds: 60 write_max_requests: 30 write_window_seconds: 60 read_max_requests: 100 read_window_seconds: 60 media_max_requests: 20 media_window_seconds: 60 admin_max_requests: 30 admin_window_seconds: 60 streaming_max_requests: 200 streaming_window_seconds: 60 websocket_max_requests: 10 websocket_window_seconds: 60 # ============================================================================ # gRPC API Rate Limits # ============================================================================ grpc_rate_limits: auth_max_requests: 5 auth_window_seconds: 60 email_max_requests: 5 email_window_seconds: 60 media_max_requests: 20 media_window_seconds: 60 write_max_requests: 30 write_window_seconds: 60 admin_max_requests: 30 admin_window_seconds: 60 read_max_requests: 100 read_window_seconds: 60 # ============================================================================ # Bootstrap Configuration # ============================================================================ # # Initial setup options for first startup. # IMPORTANT: Change root_password in production! bootstrap: # Whether to create root user on first startup create_root_user: true root_username: "root" root_email: "" # REQUIRED for production: set a strong password (>= 12 chars, must contain # uppercase, lowercase, and digits). Do NOT use any default value in production. # Set via SYNCTV_BOOTSTRAP_ROOT_PASSWORD env var. # Or file-based env: SYNCTV_BOOTSTRAP_ROOT_PASSWORD_FILE # root_password: "" # root_password_file: "/run/secrets/bootstrap_root_password" # ============================================================================ # Cluster Configuration # ============================================================================ # # Controls multi-node cluster mode and internal channel buffer sizes. # # When enabled=true, Redis is MANDATORY and cluster_secret must be set. # When enabled=false (default), this is a standalone single-node deployment # and Redis is optional. cluster: # Enable cluster mode for multi-node deployments. # When true, Redis is required and cluster_secret must be set. # When false (default), runs as a standalone single-node instance. # Environment variable: SYNCTV_CLUSTER_ENABLED enabled: false # Capacity for high-priority critical event channel (KickPublisher, KickUser, etc.) # Critical events are never dropped; senders block when full. critical_channel_capacity: 1000 # Capacity for normal-priority Redis publish channel. # Normal events are dropped with a warning when full. publish_channel_capacity: 10000 # Discovery mode: "redis" (default) or "k8s_dns" (requires HEADLESS_SERVICE_NAME # and POD_NAMESPACE env vars). # # IMPORTANT: "k8s_dns" mode still requires Redis for health monitoring, load # balancing, and cluster pub/sub. DNS only supplements peer discovery by detecting # new pods faster. Without Redis, k8s_dns provides DNS resolution only -- no # NodeRegistry, HealthMonitor, or LoadBalancer will be created. discovery_mode: "redis" # --- Static Discovery Configuration --- # When discovery_mode is "static", list peers explicitly. # Each peer is a single shared API address. The same port serves gRPC over h2 # and REST over h1. # If a peer omits its port, SyncTV probes it on server.port. # # peers: # - "node2.example.com:8080" # - "node3.example.com" # Leader election mode for singleton operations. # - "redis": Use Redis-based distributed locks (default, works everywhere) # - "k8s_lease": Use Kubernetes coordination.k8s.io/v1 Lease resource # (requires POD_NAME and POD_NAMESPACE env vars, RBAC permissions) leader_election_mode: "redis" # How far back (in seconds) to replay Redis Stream events when a new node # first connects to the cluster. Replaying recent history prevents events # published just before this node subscribed from being silently missed. # Increase in clusters with high event rates; decrease to shorten startup # replay time. Default: 300 (5 minutes). catchup_window_secs: 300 # Maximum number of entries per Redis Stream (approximate, uses MAXLEN ~). # Controls how many events are retained in each per-room stream for catch-up # after reconnection. In high-throughput scenarios, increase this to avoid # trimming events that disconnected nodes still need to catch up on. # Default: 10000 # Environment variable: SYNCTV_CLUSTER_STREAM_MAX_LENGTH stream_max_length: 10000 # ============================================================================ # Media Providers Configuration # ============================================================================ # # Stores media provider configurations (Alist, Emby, Jellyfin, Bilibili, etc.) media_providers: # Timeout for external provider HTTP requests. request_timeout_seconds: 30 # Connection timeout for external provider HTTP requests. connect_timeout_seconds: 10 # Provider configurations as a dynamic JSON/YAML value. # Secret-like provider fields also support sibling *_file keys, for example # token_file / api_key_file / password_file / access_token_file. # Example: # providers: # alist: # base_url: "https://alist.example.com" # token: "your_token" # # token_file: "/run/secrets/alist_token" # emby: # base_url: "https://emby.example.com" # api_key: "your_api_key" # # api_key_file: "/run/secrets/emby_api_key" providers: {} # ============================================================================ # OAuth2/OIDC Configuration # ============================================================================ # # Providers are configured as a YAML object with dynamic provider-specific fields. # # Supported Providers: # - github: GitHub OAuth2 # - google: Google OAuth2 # - oidc: Generic OIDC provider with .well-known discovery # Supports issuer, auth_url, token_url, userinfo_url fields # - qq, gitee, feishu, wechat: Chinese OAuth2 Providers (not yet implemented) # - Any OIDC-compliant provider (Casdoor, Keycloak, Authelia, etc.) # requires 'type: oidc' field # # Common fields (all providers): # client_id: OAuth2 client ID # client_secret: OAuth2 client secret # client_secret_file: Path to a file containing the client secret # redirect_url: (required) frontend/client callback URI # # Provider-specific fields: # logto: # endpoint: Logto server URL (e.g., "https://logto.example.com") # oidc: # issuer: Issuer URL for .well-known discovery # auth_url: (optional) Custom authorization endpoint # token_url: (optional) Custom token endpoint # userinfo_url: (optional) Custom userinfo endpoint # # Multiple Provider Instances: # Use different instance names (e.g., logto1, logto2) and add 'type' field # to specify the provider type. # # Callback URLs must point to your frontend or native client, because SyncTV # uses a frontend-driven OAuth2 flow: # 1. Provider redirects to your frontend/client callback URI with code+state # 2. Frontend/client calls POST /api/oauth2//exchange # Examples: # https://app.example.com/oauth2/callback # synctv://oauth2/callback # # Security Notes: # - OAuth2 tokens are only used temporarily during login and then discarded # - Only provider-user mappings are stored (for future logins) # - Always use HTTPS in production for OAuth2 callbacks oauth2: # URL scheme for OAuth2 redirect URLs ("http" or "https"). # When behind a reverse proxy terminating TLS, set to "https". redirect_scheme: "http" # Example configuration (uncomment and modify): # providers: # # Standard OAuth2 Providers # github: # client_id: "your_github_client_id" # client_secret: "your_github_client_secret" # # client_secret_file: "/run/secrets/github_client_secret" # redirect_url: "https://app.example.com/oauth2/callback" # # google: # client_id: "your_google_client_id" # client_secret: "your_google_client_secret" # # client_secret_file: "/run/secrets/google_client_secret" # redirect_url: "https://app.example.com/oauth2/callback" # # # Multiple instances of same provider (requires 'type' field) # logto1: # type: logto # client_id: "logto1_client_id" # client_secret: "logto1_client_secret" # # client_secret_file: "/run/secrets/logto1_client_secret" # endpoint: "https://logto1.example.com" # redirect_url: "https://app.example.com/oauth2/callback" # # logto2: # type: logto # client_id: "logto2_client_id" # client_secret: "logto2_client_secret" # # client_secret_file: "/run/secrets/logto2_client_secret" # endpoint: "https://logto2.example.com" # redirect_url: "https://app.example.com/oauth2/callback" # # # Generic OIDC provider # custom_oidc: # type: oidc # client_id: "custom_client_id" # redirect_url: "https://app.example.com/oauth2/callback" # client_secret: "custom_client_secret" # # client_secret_file: "/run/secrets/custom_oidc_client_secret" # issuer: "https://custom.oidc.provider.com" providers: {} # Environment Variables for OAuth2: # Format: SYNCTV_OAUTH2_ # Examples: # SYNCTV_OAUTH2_REDIRECT_SCHEME=https webauthn: # Enables passkey/security-key registration and login endpoints: # POST /api/user/passkeys/registration/start # POST /api/user/passkeys/registration/finish # POST /api/auth/passkeys/login/start # POST /api/auth/passkeys/login/finish # # Production requirements: # - Use HTTPS origins except for local development. # - rp_id must be the registrable domain of rp_origin, for example # rp_id: "example.com" with rp_origin: "https://app.example.com". # - In cluster mode, Redis must be configured because challenges are # single-use and must be shared across replicas. # # Environment variables: # SYNCTV_WEBAUTHN_ENABLED # SYNCTV_WEBAUTHN_RP_ID # SYNCTV_WEBAUTHN_RP_ORIGIN # SYNCTV_WEBAUTHN_RP_NAME # SYNCTV_WEBAUTHN_ALLOWED_ORIGINS # SYNCTV_WEBAUTHN_ALLOW_SUBDOMAINS # SYNCTV_WEBAUTHN_ALLOW_ANY_PORT # SYNCTV_WEBAUTHN_TIMEOUT_SECONDS enabled: false rp_id: "" rp_origin: "" rp_name: "SyncTV" allowed_origins: [] allow_subdomains: false allow_any_port: false timeout_seconds: 300 # ============================================================================ # WebRTC Configuration # ============================================================================ # # WebRTC supports 2 operation modes: # - signaling_only: Pure signaling relay without ICE bootstrap # - peer_to_peer: P2P with built-in STUN plus optional external ICE servers webrtc: mode: "peer_to_peer" # --- Built-in STUN Server --- enable_builtin_stun: true stun_port: 3478 stun_host: "0.0.0.0" # External address for STUN reflexive candidates. In K8s/NAT, set to # the pod IP or service IP (e.g., via SYNCTV_WEBRTC_STUN_EXTERNAL_ADDR). # If empty, falls back to advertise_host:stun_port. # stun_external_addr: "" # Filter private/internal ICE candidates before sending them to clients. filter_private_ice_candidates: true # --- External ICE Servers --- # Configured dynamically via the settings API (no restart required): # "webrtc.external_ice_servers" - JSON array of ICE server objects, e.g.: # [{"urls":["stun:stun.l.google.com:19302"]},{"urls":["turn:turn.example.com:3478"],"username":"user","credential":"pass"}] # ============================================================================ # Dynamic ICE Servers Configuration # ============================================================================ # # External ICE servers are managed via the settings API at runtime. # # Setting key: "webrtc.external_ice_servers" # Value: JSON array of ICE server objects # Default: # [{"urls":["stun:stun.l.google.com:19302"]},{"urls":["stun:stun1.l.google.com:19302"]}] # # Changes take effect immediately without restarting the server. # ============================================================================ # Buffer Size Tuning # ============================================================================ # # Controls internal channel buffer sizes. Larger values provide more resilience # during traffic spikes but use more memory. Defaults match the current # built-in runtime values. buffer_sizes: # Per-connection WebSocket outbound message queue. # When full, new messages are dropped (backpressure for slow clients). websocket_outbound: 256 # Audit log event buffer. Events are batched and flushed to the database. # When full, new audit events are dropped with a warning. audit_buffer: 10000