# 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 # # 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, ... # Process-wide logging for shared infrastructure, background workers, database, # Redis, configuration/bootstrap code, and third-party library targets. # Environment variables: SYNCTV_LOGGING_LEVEL, SYNCTV_LOGGING_FORMAT, # SYNCTV_LOGGING_COLOR, SYNCTV_LOGGING_OUTPUT, SYNCTV_LOGGING_OUTPUT_PATH, # SYNCTV_LOGGING_OUTPUT_ROTATION_STRATEGY, SYNCTV_LOGGING_OUTPUT_ROTATION_MAX_FILES. # Service logging blocks use the corresponding SYNCTV__LOGGING_* prefix. logging: level: "info" format: "text" output: "stdout" color: "auto" # File output: # output: # type: "file" # path: "logs/global.log" # rotation: # strategy: "daily" # max_files: 30 server: host: "0.0.0.0" port: 8080 # Destination for requests to the API root path (/). project_url: "https://github.com/synctv-org/synctv" 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 # Enable gzip negotiation for gRPC request/response bodies. # Environment variable: SYNCTV_SERVER_GRPC_COMPRESSION_ENABLED grpc_compression_enabled: true # 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: [] # 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 logging: level: "info" format: "text" output: "stdout" color: "auto" # File output uses this shape for every component: # output: # type: "file" # path: "logs/server.log" # rotation: # strategy: "daily" # max_files: 30 # One completion line per public HTTP or gRPC request. This output is routed # independently from server diagnostics. It can reuse a validated X-Request-ID, # and never includes other headers, query strings, metadata, cookies, or bodies. # Environment variables: SYNCTV_SERVER_ACCESS_LOG_ENABLED, # SYNCTV_SERVER_ACCESS_LOG_SLOW_REQUEST_THRESHOLD_MS, # SYNCTV_SERVER_ACCESS_LOG_LEVEL, SYNCTV_SERVER_ACCESS_LOG_FORMAT, # SYNCTV_SERVER_ACCESS_LOG_COLOR, SYNCTV_SERVER_ACCESS_LOG_OUTPUT, and the # standard *_OUTPUT_PATH / *_OUTPUT_ROTATION_* variants. access_log: enabled: true # Set to 0 to disable slow HTTP request classification. slow_request_threshold_ms: 1000 level: "info" format: "text" output: "stdout" color: "auto" 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: "" # Application-level clock synchronization. This calibrates SyncTV's in-process # UTC clock with NTP/SNTP servers and does not change the host OS clock. # # Keep host-level NTP/chrony enabled for PostgreSQL, Redis, TLS, JWT, logs, and # other processes. Enable this when clients need SyncTV's own playback/chat # timestamps to use an explicitly calibrated application clock. # # Environment variables: # SYNCTV_TIME_CLOCK_SYNC_ENABLED=true # SYNCTV_TIME_CLOCK_SYNC_PROVIDER_TYPE=sntp # SYNCTV_TIME_CLOCK_SYNC_PROVIDER_SERVERS='["time.cloudflare.com:123","pool.ntp.org:123"]' # SYNCTV_TIME_CLOCK_SYNC_PROVIDER_INTERVAL_SECONDS=300 # SYNCTV_TIME_CLOCK_SYNC_PROVIDER_TIMEOUT_MILLIS=1000 clock_sync: enabled: false provider: type: "sntp" servers: - "time.cloudflare.com:123" - "pool.ntp.org:123" interval_seconds: 300 timeout_millis: 1000 # 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: "" # Dedicated AES-256-GCM key for TOTP secrets. # totp_encryption_key_file: "/run/secrets/totp_encryption_key" totp_encryption_key: "" # AES-256-GCM key used only for durable email outbox payloads. # Generate with: openssl rand -hex 32 # Environment variables: # SYNCTV_SECURITY_EMAIL_OUTBOX_ENCRYPTION_KEY # SYNCTV_SECURITY_EMAIL_OUTBOX_ENCRYPTION_KEY_FILE # email_outbox_encryption_key_file: "/run/secrets/email_outbox_encryption_key" email_outbox_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: "" # Independent random keys for separate authentication and signing domains. # Generate every value separately with: openssl rand -base64 48 proxy_signing_key: "" media_swarm_signing_key: "" provider_session_encryption_key: "" login_discovery_key: "" webauthn_enumeration_key: "" # Global SSRF policy for server-side outbound requests. # Disabled by default for self-hosted private media providers. Enable it for public deployments. # Prefer allowed_hosts / allowed_ip_ranges for known internal media endpoints. # Set allow_private_network_targets=true only in a trusted private deployment. # Environment variables: # SYNCTV_SECURITY_SSRF_ENABLED # SYNCTV_SECURITY_SSRF_ALLOW_PRIVATE_NETWORK_TARGETS # SYNCTV_SECURITY_SSRF_ALLOWED_HOSTS # SYNCTV_SECURITY_SSRF_ALLOWED_IP_RANGES ssrf: enabled: false allow_private_network_targets: false allowed_hosts: [] allowed_ip_ranges: [] health: enabled: true host: "127.0.0.1" port: 8081 logging: level: "info" format: "text" output: "stdout" color: "auto" 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" logging: level: "warn" format: "text" output: "stdout" color: "auto" 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 logging: level: "info" format: "text" output: "stdout" color: "auto" # `data_dir` is the shared root for runtime-owned local files. # It affects relative: # - management.unix_socket_path # - logging.output.path # - .logging.output.path for server, health, metrics, management, # cluster, livestream, and webrtc # - livestream.hls_storage.path # - proxy_slice_cache.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 url: "postgresql://synctv:synctv@localhost:5432/synctv" # Optional read-only replica URL for explicitly allowlisted eventually-consistent reads. # Strong reads, writes, transactions, cache rebuilds, and migrations always use `url`. # read_url: "postgresql://synctv:synctv@localhost:5432/synctv" # read_url_file: "/run/secrets/database_read_url" # Or reuse the primary username/password/database and override only the read endpoint. # read_host: "postgres-read.local" # read_port: 5432 # 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 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 # Maximum time to wait for a Redis command response. # Environment variable: SYNCTV_REDIS_RESPONSE_TIMEOUT_SECONDS response_timeout_seconds: 5 # Redis connection manager pipeline buffer for bursty short commands. # Environment variable: SYNCTV_REDIS_PIPELINE_BUFFER_SIZE pipeline_buffer_size: 512 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 cache: # Business L1/L2 cache capacities and TTLs. l1_capacity: 5000 l1_ttl_seconds: 300 l2_ttl_seconds: 300 username_cache_capacity: 10000 username_cache_ttl_seconds: 3600 proxy_slice_cache: # Enable range-based proxy slice caching at process startup. # This is not dynamically toggled at runtime. enabled: true # Enable persisted proxy slice files on local/shared storage. file_backend_enabled: false # Relative 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. # file_cache_dir: "cache/proxy-slice" livestream: rtmp_port: 1935 # Public host returned in RTMP publish URLs. Set this in production to a # client-reachable domain, LoadBalancer address, or node public IP. # If empty, SyncTV uses only the local bind host fallback for local/single-node use. # public_rtmp_host: "live.example.com" # Public HTTP(S) origin used in WHIP publish URLs. When empty, publish-key # responses return an origin-relative URL that clients resolve against the API. # public_webrtc_base_url: "https://live.example.com" logging: level: "info" format: "text" output: "stdout" color: "auto" 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 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 (500 MB). gop_cache_max_memory_mb: 100 # HLS segment storage. type: memory, file, shared_file, or s3. # Use shared_file only when path is mounted from storage visible to every replica. hls_storage: type: "memory" # memory_max_mb limits in-memory HLS segment storage. 0 uses the built-in default. memory_max_mb: 0 # For type=file or shared_file: # path: "livestream/hls" # For type=s3: # endpoint: "https://s3.example.com" # access_key_id_file: "/run/secrets/hls_storage_access_key_id" # secret_access_key_file: "/run/secrets/hls_storage_secret_access_key" # bucket: "synctv-hls" # region: null # base_path: "hls/" # Maximum HTTP-FLV connection duration and slow-client write timeout. flv_max_connection_duration_seconds: 86400 flv_write_timeout_seconds: 30 # Standards-based WHIP ingest and WHEP playback share the main HTTP port. # ICE uses host candidates when ice_servers is empty. Production deployments # should configure a client-reachable STUN or TURN service. webrtc: enabled: true ice_servers: [] # Example TURN configuration. Use credential_file to avoid storing the # shared credential directly in this configuration file. # ice_servers: # - urls: # - "stun:stun.example.com:3478" # - "turn:turn.example.com:3478?transport=udp" # - "turns:turn.example.com:5349?transport=tcp" # username: "synctv" # credential_file: "/run/secrets/livestream_turn_credential" ice_gathering_timeout_seconds: 10 max_sdp_bytes: 262144 max_sessions: 1000 max_session_duration_seconds: 86400 file_storage: # Named backend selected by product features when they do not override it. # The built-in "disabled" backend always exists and rejects uploads. default_backend: "disabled" # Product-level backend choices. Empty values inherit default_backend. chat_attachments_backend: "" user_avatars_backend: "" media_covers_backend: "" room_covers_backend: "" playlist_covers_backend: "" # Dedicated signing secret for upload/read tokens. This value is required. # Environment variables: # SYNCTV_FILE_UPLOAD_TOKEN_SECRET # SYNCTV_FILE_UPLOAD_TOKEN_SECRET_FILE upload_token_secret: "" # Uploaded objects with no active product reference are cleaned after this # grace period. Set to 0 to disable orphan cleanup. unreferenced_object_retention_seconds: 86400 # upload_token_secret_file: "/run/secrets/file_upload_token_secret" # Registry of storage backends keyed by stable storage name. backends: {} # Example: store files in PostgreSQL. # file_storage: # default_backend: "database" # chat_attachments_backend: "database" # user_avatars_backend: "database" # media_covers_backend: "database" # room_covers_backend: "database" # playlist_covers_backend: "database" # upload_token_secret_file: "/run/secrets/file_upload_token_secret" # backends: # database: # type: "database" # # Compression for bytes stored in file_blobs: none, lz4, or zstd. # compression: "zstd" # Example: store files in multiple S3-compatible backends. # file_storage: # default_backend: "s3_public" # chat_attachments_backend: "s3_public" # user_avatars_backend: "s3_private" # media_covers_backend: "s3_public" # room_covers_backend: "s3_public" # playlist_covers_backend: "s3_public" # upload_token_secret_file: "/run/secrets/file_upload_token_secret" # backends: # s3_public: # type: "s3" # endpoint: "https://s3.example.com" # access_key_id_file: "/run/secrets/file_storage_s3_access_key_id" # secret_access_key_file: "/run/secrets/file_storage_s3_secret_access_key" # bucket: "synctv-files" # region: "auto" # base_path: "files/" # public_base_url: "https://cdn.example.com/files" # upload_expires_seconds: 900 # s3_private: # type: "s3" # endpoint: "https://s3.example.com" # access_key_id_file: "/run/secrets/file_storage_s3_access_key_id" # secret_access_key_file: "/run/secrets/file_storage_s3_secret_access_key" # bucket: "synctv-private-files" # region: "auto" # base_path: "private-files/" # public_base_url: null # upload_expires_seconds: 900 # ============================================================================ # 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 # Optional zxcvbn entropy estimator. Disabled by default; enable only when # you want dictionary/pattern-based password strength scoring. zxcvbn_enabled: false # Minimum zxcvbn score when enabled. Valid range: 0-4. zxcvbn recommends # treating scores below 3 as weak. zxcvbn_min_score: 3 # ============================================================================ # Connection Limits # ============================================================================ # # Controls concurrent WebSocket/streaming connection limits. connection_limits: # Maximum concurrent connections per user max_per_user: 20 # Maximum concurrent connections per room max_per_room: 2000 # Maximum total concurrent connections max_total: 100000 # 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 limits enforced by the shared messaging logic. messaging_rate_limits: # Maximum chat messages per sliding window. chat_per_second: 10 # Sliding-window size in seconds. window_seconds: 1 # ============================================================================ # Request API Rate Limits # ============================================================================ request_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 scopes: room_members: max_requests: 90 window_seconds: 15 strategy: fixed_window # ============================================================================ # 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" # 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 # Dedicated internal gRPC listener. Keep this private to cluster peers. host: "0.0.0.0" port: 50051 # Optional address advertised to peers. Empty host and port 0 fall back to # server.advertise_host and cluster.port. advertise_host: "" advertise_port: 0 logging: level: "warn" format: "text" output: "stdout" color: "auto" # 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_CLUSTER_SECRET # Environment file variable: SYNCTV_CLUSTER_SECRET_FILE # File-based secret: cluster.secret_file: "/run/secrets/cluster_secret" secret: "" # Capacity for high-priority critical event channel (KickPublisher, KickUser, etc.) # Critical events are never dropped; senders block when full. critical_channel_capacity: 10000 # Capacity for normal-priority Redis publish channel. # Normal events are dropped with a warning when full. publish_channel_capacity: 100000 # Discovery mode: "redis" (default), "static", 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 dedicated cluster gRPC address. # If a peer omits its port, SyncTV probes it on cluster.port. # # peers: # - "node2.example.com:50051" # - "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: 100000 # Environment variable: SYNCTV_CLUSTER_STREAM_MAX_LENGTH stream_max_length: 100000 # ============================================================================ # Media Providers Configuration # ============================================================================ # # Configures local built-in media provider adapters in this SyncTV process. # Remote provider instances are configured through the management API/CLI. media_providers: alist: # Timeout for Alist upstream HTTP requests. request_timeout_seconds: 30 # Connection timeout for Alist upstream HTTP requests. connect_timeout_seconds: 10 bilibili: # Timeout for Bilibili upstream HTTP requests. request_timeout_seconds: 30 # Connection timeout for Bilibili upstream HTTP requests. connect_timeout_seconds: 10 emby: # Timeout for Emby/Jellyfin upstream HTTP requests. request_timeout_seconds: 30 # Connection timeout for Emby/Jellyfin upstream HTTP requests. connect_timeout_seconds: 10 # OAuth2/OIDC providers are runtime settings, not static startup config. # Configure them with the management API or CLI: # synctv settings update oauth2 --set providers='{"github":{"type":"github","enableSignup":false,"signupNeedReview":false,"clientId":"...","clientSecret":"..."}}' 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 # Passkey login start is username-less and returns conditional UI options. # Clients should require browsers/authenticators that support discoverable credentials. # # 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_APPLE_APP_IDS # SYNCTV_WEBAUTHN_ANDROID_APPS # SYNCTV_WEBAUTHN_ALLOW_SUBDOMAINS # SYNCTV_WEBAUTHN_ALLOW_ANY_PORT # SYNCTV_WEBAUTHN_TIMEOUT_SECONDS enabled: false rp_id: "" rp_origin: "" rp_name: "SyncTV" allowed_origins: [] # Apple application identifiers served from # /.well-known/apple-app-site-association for native passkeys and OAuth2 app links. apple_app_ids: [] # Android app identities served from /.well-known/assetlinks.json for passkeys and OAuth2 app links. android_apps: [] # android_apps: # - package_name: "org.synctv.app" # sha256_cert_fingerprints: # - "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" 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 public ICE servers and optional built-in STUN webrtc: mode: "peer_to_peer" # --- Built-in STUN Server --- # Disabled by default. Enable only when this deployment should host STUN. enable_builtin_stun: false stun_port: 3478 stun_host: "0.0.0.0" # External address for STUN reflexive candidates. In K8s/NAT, set this to # a client-reachable public IP:port or DNS name:port, such as a LoadBalancer # IP, node public IP, or stun.example.com:3478. Do not use a Pod IP or # ClusterIP Service IP; those are rejected as non-routable for public STUN. # If empty, SyncTV tries advertise_host, STUN_EXTERNAL_IP, and cloud metadata; # the built-in STUN server is skipped when no public address is found. # stun_external_addr: "" # Filter private/internal ICE candidates before sending them to clients. filter_private_ice_candidates: false logging: level: "info" format: "text" output: "stdout" color: "auto" # --- 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.cloudflare.com:3478"]},{"urls":["stun:stun.l.google.com:19302"]}] # ============================================================================ # 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: Cloudflare and Google public STUN servers shown above. # Set [] to disable all external ICE servers. # # 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