Entry Point (main.py)
File: head/main.py
The entry point for the Codecast Head Node. This module bootstraps the entire system by loading configuration, initializing shared components, starting bots, and handling graceful shutdown.
Purpose
- Load and validate
config.yaml - Create shared infrastructure (SSHManager, SessionRouter, DaemonClient)
- Initialize and start Discord and/or Telegram bots
- Handle graceful shutdown on SIGTERM/SIGINT
Main Function
async def main(config_path: str = "config.yaml") -> None
The main() coroutine is the primary entry point. It performs the following steps:
1. Configuration Loading
config = load_config(config_path)
Loads config.yaml (or a custom path passed as a command-line argument). If the file is missing or invalid, the process exits with an error message.
2. Shared Component Initialization
ssh_manager = SSHManager(config)
session_router = SessionRouter(db_path=str(Path(__file__).parent / "sessions.db"))
daemon_client = DaemonClient()
These three components are created once and shared across all bots:
- SSHManager: Manages SSH connections and tunnels to all configured machines. Takes the full config to access machine definitions and daemon deployment settings.
- SessionRouter: SQLite-backed session registry. The database is stored as
head/sessions.db(next to the Python source files). - DaemonClient: Stateless JSON-RPC client with a shared aiohttp session.
3. Bot Initialization
discord_bot = DiscordBot(ssh_manager, session_router, daemon_client, config)
telegram_bot = TelegramBot(ssh_manager, session_router, daemon_client, config)
Each bot is created only if its token is configured. If neither bot has a valid token, the process exits with an error.
4. Bot Startup
task = asyncio.create_task(discord_bot.start(), name="discord")
task = asyncio.create_task(telegram_bot.start(), name="telegram")
Bots run as concurrent asyncio tasks. The main coroutine then waits for either:
- A shutdown signal (SIGTERM/SIGINT)
- A bot task to crash (first completed)
5. Graceful Shutdown
def handle_shutdown(sig: signal.Signals) -> None:
shutdown_event.set()
Signal handlers for SIGTERM and SIGINT set a shutdown event. When triggered:
- All bots are stopped via
bot.stop() - The DaemonClient's HTTP session is closed
- All SSH tunnels are closed via
ssh_manager.close_all() - Remaining asyncio tasks are cancelled
Command-Line Usage
# Default config
python -m head.main
# Custom config path
python -m head.main /path/to/config.yaml
The config path is read from sys.argv[1] if provided, defaulting to "config.yaml".
Logging
The module configures Python's logging at the INFO level with the format:
2026-03-14 10:00:00 [codecast] INFO: message
All modules under head/ use logging.getLogger(__name__) and inherit this configuration.
Error Handling
- Missing config file: logs error and exits with code 1
- Empty machines dict: logs error and exits with code 1
- No bots configured (no tokens): logs error and exits with code 1
- Bot crash during runtime: logs the exception, triggers shutdown
- Cleanup errors: logged as warnings, do not prevent other cleanup steps