diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 18b9ee281..fafc4ef7b 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -15,6 +15,13 @@ REDIS_APP_URL=redis://localhost:6379/0 # Optional: TTL in seconds for connector indexing lock key # CONNECTOR_INDEXING_LOCK_TTL_SECONDS=28800 +# Messaging Gateway (global) +# GATEWAY_ENABLED: master switch for ALL messaging gateway channels (Telegram, WhatsApp, +# Slack, Discord). When FALSE, no gateway background workers/supervisors start and all +# gateway HTTP routes (webhooks, OAuth callbacks, pairing) return 404. Set per-channel +# flags below to control individual platforms once the gateway is enabled. +GATEWAY_ENABLED=TRUE + # Telegram Gateway # TELEGRAM_WEBHOOK_SECRET must be 1-256 chars and contain only A-Z, a-z, 0-9, _ or - # GATEWAY_TELEGRAM_INTAKE_MODE: `webhook` for production, `longpoll` for single-replica self-host fallback, `disabled` to skip Telegram intake diff --git a/surfsense_backend/app/config/__init__.py b/surfsense_backend/app/config/__init__.py index 203e36580..b2650e87c 100644 --- a/surfsense_backend/app/config/__init__.py +++ b/surfsense_backend/app/config/__init__.py @@ -542,6 +542,9 @@ class Config: BACKEND_URL = os.getenv("BACKEND_URL") # Messaging gateway (Telegram v1) + # Global master switch: when FALSE, no gateway supervisors/workers start and all + # gateway HTTP routes return 404, regardless of the per-channel flags below. + GATEWAY_ENABLED = os.getenv("GATEWAY_ENABLED", "TRUE").upper() == "TRUE" TELEGRAM_SHARED_BOT_TOKEN = os.getenv("TELEGRAM_SHARED_BOT_TOKEN") TELEGRAM_SHARED_BOT_USERNAME = os.getenv("TELEGRAM_SHARED_BOT_USERNAME") TELEGRAM_WEBHOOK_SECRET = os.getenv("TELEGRAM_WEBHOOK_SECRET") diff --git a/surfsense_backend/app/gateway/__init__.py b/surfsense_backend/app/gateway/__init__.py index 5cf91505b..8b79b3160 100644 --- a/surfsense_backend/app/gateway/__init__.py +++ b/surfsense_backend/app/gateway/__init__.py @@ -1,2 +1,19 @@ """Messaging gateway infrastructure for external chat channels.""" +from __future__ import annotations + +from fastapi import HTTPException, status + +from app.config import config + + +def require_gateway_enabled() -> None: + """FastAPI dependency that gates all gateway HTTP routes on the global flag. + + Returns 404 (rather than 503) when ``GATEWAY_ENABLED`` is FALSE so that + disabling the gateway makes its webhook/OAuth/pairing surface indistinguishable + from a route that does not exist. + """ + + if not config.GATEWAY_ENABLED: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") diff --git a/surfsense_backend/app/gateway/byo_long_poll.py b/surfsense_backend/app/gateway/byo_long_poll.py index bb7ba53ad..29a0ed48c 100644 --- a/surfsense_backend/app/gateway/byo_long_poll.py +++ b/surfsense_backend/app/gateway/byo_long_poll.py @@ -96,6 +96,8 @@ async def start_byo_long_poll_supervisors() -> None: """Start one BYO long-poll supervisor per active non-system Telegram account.""" global _shutdown_event + if not config.GATEWAY_ENABLED: + return if ( config.GATEWAY_TELEGRAM_INTAKE_MODE != "longpoll" and config.GATEWAY_WHATSAPP_INTAKE_MODE != "baileys" diff --git a/surfsense_backend/app/gateway/discord/intake.py b/surfsense_backend/app/gateway/discord/intake.py index 4c89de821..3fe76f0c4 100644 --- a/surfsense_backend/app/gateway/discord/intake.py +++ b/surfsense_backend/app/gateway/discord/intake.py @@ -177,6 +177,8 @@ async def _run_discord_gateway() -> None: async def start_discord_gateway_supervisor() -> None: global _shutdown_event, _task + if not config.GATEWAY_ENABLED: + return if not config.GATEWAY_DISCORD_ENABLED: return if _task is not None and not _task.done(): diff --git a/surfsense_backend/app/gateway/inbox_worker.py b/surfsense_backend/app/gateway/inbox_worker.py index e3ea7225c..8f35e7e6a 100644 --- a/surfsense_backend/app/gateway/inbox_worker.py +++ b/surfsense_backend/app/gateway/inbox_worker.py @@ -6,6 +6,7 @@ import asyncio import logging from contextlib import suppress +from app.config import config from app.gateway.inbox_processor import claim_next_inbound_event, process_inbound_event logger = logging.getLogger(__name__) @@ -39,6 +40,8 @@ async def _process_inbox_forever() -> None: async def start_gateway_inbox_worker() -> None: global _task + if not config.GATEWAY_ENABLED: + return if _task is not None and not _task.done(): return _task = asyncio.create_task(_process_inbox_forever(), name="gateway-inbox-worker") diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 4750b9948..426346355 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -1,6 +1,7 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Depends from app.automations.api import router as automations_router +from app.gateway import require_gateway_enabled from app.file_storage.api import router as file_storage_router from .agent_action_log_route import router as agent_action_log_router @@ -73,9 +74,10 @@ router.include_router(editor_router) router.include_router(export_router) router.include_router(documents_router) router.include_router(folders_router) -router.include_router(gateway_router) -router.include_router(gateway_whatsapp_webhook_router) -router.include_router(gateway_whatsapp_baileys_router) +_gateway_enabled_dep = [Depends(require_gateway_enabled)] +router.include_router(gateway_router, dependencies=_gateway_enabled_dep) +router.include_router(gateway_whatsapp_webhook_router, dependencies=_gateway_enabled_dep) +router.include_router(gateway_whatsapp_baileys_router, dependencies=_gateway_enabled_dep) router.include_router(notes_router) router.include_router(new_chat_router) # Chat with assistant-ui persistence router.include_router(agent_revert_router) # POST /threads/{id}/revert/{action_id}