# ============================================================================== # Telegram Archive — Environment Variables # ============================================================================== # Copy this file to .env and fill in your values: # cp .env.example .env # # For the full reference table, see the README: # https://github.com/GeiserX/Telegram-Archive#environment-variables # ============================================================================== # ============================================================================== # TELEGRAM CREDENTIALS (required for backup) # ============================================================================== # Get these from https://my.telegram.org/apps TELEGRAM_API_ID=your_api_id_here TELEGRAM_API_HASH=your_api_hash_here TELEGRAM_PHONE=+1234567890 # Optional SOCKS5 proxy for all TelegramClient connections # Requires python-socks[asyncio] in the runtime image/environment # TELEGRAM_PROXY_TYPE=socks5 # TELEGRAM_PROXY_ADDR=127.0.0.1 # TELEGRAM_PROXY_PORT=1080 # TELEGRAM_PROXY_USERNAME= # TELEGRAM_PROXY_PASSWORD= # TELEGRAM_PROXY_RDNS=false # --- Multiple accounts (v8.0.0+, optional) --- # Archive several Telegram accounts into the same database and media store. # Indexes start at 1 and must be contiguous; declaring any TG_ACCOUNT_* # variable switches to indexed mode and the legacy triple above is ignored. # With none set, the single account above is used unchanged. # Account 1 keeps the legacy session file (SESSION_NAME, default # telegram_backup) unless TG_ACCOUNT_1_SESSION_NAME is set, so an upgraded # deployment never re-logins; accounts 2+ default to telegram_backup_account. # Each account needs one interactive login: ./init_auth.sh (python -m src auth). # TG_ACCOUNT_1_API_ID=your_api_id_here # TG_ACCOUNT_1_API_HASH=your_api_hash_here # TG_ACCOUNT_1_PHONE_NUMBER=+1234567890 # TG_ACCOUNT_1_LABEL=personal # TG_ACCOUNT_2_API_ID=second_api_id_here # TG_ACCOUNT_2_API_HASH=second_api_hash_here # TG_ACCOUNT_2_PHONE_NUMBER=+0987654321 # TG_ACCOUNT_2_LABEL=work # TG_ACCOUNT_2_SESSION_NAME=telegram_backup_account2 # # Per-account capture filters (v8.1.0+): any of CHAT_IDS, CHAT_TYPES, # INCLUDE_CHAT_IDS, EXCLUDE_CHAT_IDS, the PRIVATE_/GROUPS_/CHANNELS_ include # and exclude lists, PRIORITY_CHAT_IDS and SKIP_MEDIA_CHAT_IDS can be set per # account as TG_ACCOUNT__. The indexed variable wins for that # account; the global variable is the fallback, so accounts without overrides # behave exactly as before. An EMPTY indexed value inherits the global (safe # with compose's ${VAR:-} idiom); the literal value `none` is the explicit # empty override — e.g. account 2 runs without a whitelist while account 1 # keeps the global one: # TG_ACCOUNT_2_CHAT_IDS=none # TG_ACCOUNT_2_CHAT_TYPES=groups,channels # ============================================================================== # BACKUP SCHEDULE & STORAGE # ============================================================================== # Cron schedule (minute hour day month weekday) # Examples: 0 */6 * * * (every 6h), 0 0 * * * (daily), 0 */1 * * * (hourly) SCHEDULE=0 */6 * * * # Backup destination path (inside container) BACKUP_PATH=/data/backups # Download media files (photos, videos, documents) # Set to false to save space and only backup text messages DOWNLOAD_MEDIA=true # Maximum media file size to download (in MB) # Files larger than this will be skipped MAX_MEDIA_SIZE_MB=100 # 0 or negative = no size limit # Maximum usable filename byte budget for downloaded media (the file_id prefix # and extension are always preserved; only the decorative part is shortened). # Default 143 keeps names writable on Synology/eCryptfs encrypted shares, whose # filename encryption overhead caps components below the usual 255 bytes. # Raise to 255 on plain ext4/xfs/btrfs if you prefer longer decorative names. # MEDIA_MAX_FILENAME_BYTES=143 # Stop retrying a media file's download after this many failed attempts, so a # permanently-unwritable file can't tax every backup run forever. Re-requesting # the download (e.g. via VERIFY_MEDIA or the admin panel) resets the counter. # MEDIA_MAX_DOWNLOAD_ATTEMPTS=5 # Timeout for media downloads (in seconds). 0 disables the timeout. # Default is 3600 (60 minutes). # DOWNLOAD_TIMEOUT_SECONDS=3600 # Absorb mid-download FloodWaits up to this many seconds so the transfer resumes # in place instead of restarting from byte 0 (issue #232). 0 = raise immediately. # MEDIA_FLOOD_SLEEP_THRESHOLD=60 # Absorb FloodWaits up to this many seconds during get_dialogs()'s internal # pagination so it resumes on the same page instead of the whole call restarting # from page 1 (issue #295). 0 = raise immediately. # DIALOG_FLOOD_SLEEP_THRESHOLD=60 # --- Parallel chunked downloads (advanced, default OFF) --- # Splits one large file into chunks fetched concurrently over several MTProto # connections to a single datacenter, lifting the ~10 MB/s single-stream cap. # Small files and photos always use a single stream regardless of this setting. # Disabled by default; enable only if your download throughput is the bottleneck. # PARALLEL_DOWNLOAD_ENABLED=false # # Only files at least this large (in MB) use the parallel path. Smaller files # stay single-stream (chunking overhead isn't worth it). Minimum 1. # PARALLEL_DOWNLOAD_MIN_SIZE_MB=20 # # Number of concurrent connections per file. Clamped to 2..8. Telegram throttles # hard past ~20 total connections, so this stays conservative. Default 4. # PARALLEL_DOWNLOAD_CONNECTIONS=4 # # Chunk size per request in KB. Must be one of: 4, 8, 16, 32, 64, 128, 256, 512 # (a 4 KiB multiple that divides 1 MiB, per Telegram's getFile constraints). # Invalid values snap down to the nearest valid size. Default 512. # Peak extra memory while downloading is roughly CONNECTIONS x PART_SIZE_KB # (e.g. 4 x 512 KB = 2 MB), since each connection buffers one chunk in flight. # PARALLEL_DOWNLOAD_PART_SIZE_KB=512 # Messages processed per database batch # Higher values = faster but more memory usage BATCH_SIZE=100 # Detect and fetch missing messages in sequence gaps # FILL_GAPS=false # Number of messages missing in a sequence to consider it a gap # GAP_THRESHOLD=50 # How often to save backup progress (every N batch inserts) # 1 = checkpoint every batch (safest, best crash recovery) # Higher = fewer DB writes but more re-work on crash/restart CHECKPOINT_INTERVAL=1 # Use symlinks to deduplicate identical media files across chats # DEDUPLICATE_MEDIA=true # Comma-separated chat IDs to process FIRST in all operations # PRIORITY_CHAT_IDS= # Skip media downloads for specific chats (comma-separated IDs) # Messages are still backed up with text, but media files are not downloaded # Useful for high-volume media chats where you only need text content # Example: SKIP_MEDIA_CHAT_IDS=-1001234567890,-1009876543210 # SKIP_MEDIA_CHAT_IDS= # Delete existing media files and DB records for chats in SKIP_MEDIA_CHAT_IDS # When enabled (default), reclaims storage by removing already-downloaded media # Set to false to keep existing media but skip future downloads SKIP_MEDIA_DELETE_EXISTING=true # Skip specific topics in forum supergroups (format: chat_id:topic_id,...) # Messages in matching topics are completely excluded from backup # Example: SKIP_TOPIC_IDS=-1001234567890:42,-1001234567890:1337 # SKIP_TOPIC_IDS= # Hour (0-23) to recalculate backup statistics daily # STATS_CALCULATION_HOUR=3 # Logging level: DEBUG, INFO, WARNING, ERROR LOG_LEVEL=INFO # Suppress FloodWait log messages below this threshold (seconds) # Waits shorter than this are routine and logged at DEBUG instead of WARNING # Set to 0 to log every flood-wait # FLOOD_WAIT_LOG_THRESHOLD=10 # Maximum retries for FloodWait limits # MAX_FLOOD_RETRIES=5 # Maximum seconds to wait during a FloodWait before aborting # MAX_FLOOD_WAIT_SECONDS=3600 # Exponential backoff for transient connection errors # BACKOFF_MIN_SECONDS=2.0 # BACKOFF_MAX_SECONDS=300.0 # Session name (use different names for multiple accounts) SESSION_NAME=telegram_backup # Session directory (defaults to /data/session) # SESSION_DIR=/data/session # ============================================================================== # CHAT FILTERING — Choose ONE mode # ============================================================================== # MODE 1: WHITELIST (Simple) — Set CHAT_IDS to backup ONLY specific chats # When set, ONLY these chats are backed up. All other filtering is ignored. # Example: CHAT_IDS=-1001234567890,-1009876543210 CHAT_IDS= # If a CHAT_IDS entry can't be resolved (typically a DM on a fresh session), # scan up to this many dialogs once to warm the entity cache (issue #234). 0 disables. # WHITELIST_RESOLVE_DIALOG_LIMIT=1000 # MODE 2: TYPE-BASED (Default) — Use CHAT_TYPES to backup by type # Options: private, groups, channels, bots (comma-separated) CHAT_TYPES=private,groups,channels # EXCLUDE specific chats (blacklist — takes priority) GLOBAL_EXCLUDE_CHAT_IDS= PRIVATE_EXCLUDE_CHAT_IDS= GROUPS_EXCLUDE_CHAT_IDS= CHANNELS_EXCLUDE_CHAT_IDS= # INCLUDE specific chats in type-based mode. # GLOBAL_INCLUDE_CHAT_IDS acts as an allow-list across all selected types. # Type-specific include lists limit that chat type. For simplest exclusive # selection, use CHAT_IDS mode instead. GLOBAL_INCLUDE_CHAT_IDS= PRIVATE_INCLUDE_CHAT_IDS= GROUPS_INCLUDE_CHAT_IDS= CHANNELS_INCLUDE_CHAT_IDS= # Group → supergroup migrations (#228): when a tracked basic group is upgraded to # a supergroup it gets a brand-new id and capture would otherwise silently stop. # The scheduled backup always warns (count-only). Set FOLLOW_CHAT_MIGRATIONS=true # to instead adopt the new supergroup id automatically (persisted and merged into # the backup + listener scope). Default false — warning only. # FOLLOW_CHAT_MIGRATIONS=false # ============================================================================== # REAL-TIME LISTENER # ============================================================================== # ENABLE_LISTENER is the master switch. When false (default), all LISTEN_* # and MASS_OPERATION_* variables below have no effect. ENABLE_LISTENER=false # Granular listener controls (only apply when ENABLE_LISTENER=true): # LISTEN_EDITS=true # LISTEN_DELETIONS=false # DELETION_MODE=hard # hard=legacy delete from archive, soft=mark deleted and keep message # LISTEN_NEW_MESSAGES=true # LISTEN_NEW_MESSAGES_MEDIA=false # LISTEN_CHAT_ACTIONS=true # LISTEN_REACTIONS=false # Capture reactions in real-time (opt-in). Best-effort and # # aggregate-only; the scheduled backup reconciles regardless. # REACTION_DEBOUNCE_SECONDS=1.5 # Coalesce reaction bursts on the same message # REACTION_RESWEEP_DAYS=0 # Re-check last N days/chat each sweep to recover your own # # reactions (0=disabled; self-reactions are not reliably pushed). # REACTION_RESWEEP_MAX_PER_CHAT=500 # Cap messages re-checked per chat/sweep (~5 API calls) # REACTION_RESWEEP_BATCH_DELAY_SECONDS=2 # Min spacing between resweep API requests, across # # chats (0=off). On a FloodWait the resweep pauses and # # resumes within the same run once the server window # # elapses; it defers to the next sweep only if the # # window outlives the run or after repeated floods. # Mass operation protection (only applies when ENABLE_LISTENER=true) # MASS_OPERATION_THRESHOLD=10 # MASS_OPERATION_WINDOW_SECONDS=30 # MASS_OPERATION_BUFFER_DELAY=2.0 # Batch sync alternative (expensive — prefer ENABLE_LISTENER instead) SYNC_DELETIONS_EDITS=false # Re-download missing or corrupted media files VERIFY_MEDIA=false # ============================================================================== # OUTBOUND EVENT WEBHOOK (#336) # ============================================================================== # Fires an HTTP request when the real-time listener applies a message edit or # deletion. Requires ENABLE_LISTENER=true (and LISTEN_EDITS / LISTEN_DELETIONS # for the respective event). Sweep-detected changes (SYNC_DELETIONS_EDITS) do # NOT fire it. The body carries message content by design — point it only at # services you control. EVENT_WEBHOOK_ENABLED=false # EVENT_WEBHOOK_URL= # http:// or https:// target (required when enabled; # # treated as a secret, never logged) # EVENT_WEBHOOK_METHOD=POST # POST or PUT # EVENT_WEBHOOK_HEADERS={} # JSON object, e.g. {"Authorization": "Bearer test@value/here"}. # # Its Content-Type drives per-placeholder auto-escaping # # (default: application/json; charset=utf-8) # EVENT_WEBHOOK_EVENTS=message_edited,message_deleted # subset to fire on # EVENT_WEBHOOK_CHAT_IDS= # comma-separated marked chat ids; empty = all chats # EVENT_WEBHOOK_BODY_TEMPLATE= # {placeholder} / {placeholder|jsonescape|urlencode|raw}; # # empty = default JSON body. Placeholders: event, chat_id, # # chat_title, sender_id, sender_name, message_id, date, text, # # old_text, new_text, media_type, account_id. Unknown/missing # # placeholders render blank. # ============================================================================== # DATABASE CONFIGURATION (v3.0+) # ============================================================================== # Supports SQLite (default) and PostgreSQL. # Both backup and viewer containers MUST use the same database settings. # Option 1: Full DATABASE_URL (takes priority over all below) # SQLite paths need FOUR slashes to be absolute. Three slashes is a path # relative to the container's working directory (/app), which is NOT the # mounted volume — the archive would be lost on the next container recreate. # SQLite: DATABASE_URL=sqlite:////data/backups/telegram_backup.db # PostgreSQL: DATABASE_URL=postgresql://user:password@localhost:5432/telegram_backup # Option 2: Individual settings DB_TYPE=sqlite DB_PATH=/data/backups/telegram_backup.db # Timeout for SQLite operations (seconds) # Increase if you see "database is locked" errors on slow disks # DATABASE_TIMEOUT=60.0 # Print all SQL queries to console (for debugging) # DB_ECHO=false # PostgreSQL settings (when DB_TYPE=postgresql) # POSTGRES_HOST=localhost # POSTGRES_PORT=5432 # POSTGRES_USER=telegram # POSTGRES_PASSWORD=your_secure_password # POSTGRES_DB=telegram_backup # ============================================================================== # VIEWER & AUTHENTICATION # ============================================================================== # These apply to the telegram-archive-viewer container. # Basic auth — set BOTH to enable the master account. The viewer fails closed # unless credentials are configured or ALLOW_ANONYMOUS_VIEWER=true is explicit. # VIEWER_USERNAME=admin # VIEWER_PASSWORD=your_secure_password # AUTH_SESSION_DAYS=30 # # ALLOW_ANONYMOUS_VIEWER grants READ-ONLY access with no login: browsing and # search work, but nothing that writes (settings, viewer/token management, # deletions, etc.) is reachable without authenticating as the master account. # ALLOW_ANONYMOUS_VIEWER=false # --- Multi-User Access Control (v7.0.0) --- # Viewer accounts are managed via the admin panel (gear icon in sidebar). # The VIEWER_USERNAME/VIEWER_PASSWORD above becomes the "master" account. # Create additional viewer accounts with per-chat access via the admin UI. # Viewer account data is stored in the database (viewer_accounts table). # --- Trusted Proxy Authentication (v7.9.0) --- # For use behind auth proxies like Authelia, Authentik, or Keycloak. # The proxy authenticates the user and forwards their identity in a header. # Set AUTH_PROXY_HEADER to enable (e.g., "Remote-User" or "X-Forwarded-User"). # # !!! WARNING !!! # Your reverse proxy MUST strip or overwrite this header on EVERY inbound # request before it reaches this app. If a client can set the header itself # (because the proxy passes it through untouched), anyone can impersonate any # user — including an admin — with a single request header. This is a full # authentication bypass, not a hardening nicety. # AUTH_PROXY_HEADER=Remote-User # Comma-separated list of usernames that get admin (master) role. # Users not in this list are auto-created as viewers with restricted access. # AUTH_PROXY_ADMIN_USERS=admin@example.com # Default chat access for auto-created proxy users: "none" or "all". # "none" = no chats visible until admin grants access via the admin panel. # "all" = full access to all chats (suitable for single-user setups). # AUTH_PROXY_DEFAULT_ACCESS=none # Timezone for displayed timestamps (tz database name) # VIEWER_TIMEZONE=Europe/Madrid # Show backup statistics dropdown in viewer header # SHOW_STATS=true # Restrict viewer to specific chats (comma-separated IDs) # Useful for sharing public channel viewers without exposing other chats # DISPLAY_CHAT_IDS= # ============================================================================== # SECURITY # ============================================================================== # Allowed CORS origins (comma-separated) # Default: * (allow all origins, credentials auto-disabled) # Example: CORS_ORIGINS=https://my.domain.com,https://other.domain.com # CORS_ORIGINS=* # Set Secure flag on auth cookies # Default: auto-detect from request protocol (HTTPS reverse proxy or direct HTTPS = Secure) # Override: true = always Secure, false = never Secure # SECURE_COOKIES= # Only trust X-Forwarded-For / X-Real-IP when your reverse proxy overwrites # those headers. Leave false for direct/LAN deployments. # TRUST_PROXY_HEADERS=false # WebSocket abuse caps: total sockets the viewer will hold at once, and how # many chats one socket may subscribe to. Extra connections close with 1013. # MAX_WS_CONNECTIONS=200 # MAX_WS_SUBSCRIPTIONS_PER_CONNECTION=16 # Shared secret for backup -> viewer realtime pushes when using SQLite split # containers. Required for non-loopback /internal/push calls. # INTERNAL_PUSH_SECRET=change_me_to_a_long_random_value # Backup container target for SQLite realtime pushes. # VIEWER_HOST=telegram-viewer # VIEWER_PORT=8000 # ============================================================================== # NOTIFICATIONS # ============================================================================== # Master switch to enable push notifications # ENABLE_NOTIFICATIONS=false # Push notification mode: off, basic (in-browser only), full (Web Push) # PUSH_NOTIFICATIONS=basic # Custom VAPID keys for Web Push (auto-generated if empty) # Generate with: npx web-push generate-vapid-keys # VAPID_PRIVATE_KEY= # VAPID_PUBLIC_KEY= # VAPID_CONTACT=mailto:admin@example.com