|
|
|
|
# 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_<sqid>.
|
|
|
|
|
# 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/<instance_name>/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_<FIELD>
|
|
|
|
|
# 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
|