Internet │ ▼ :80 / :443 ┌──────────────┐ │ Nginx │ TLS · reverse proxy · static assets └──────┬───────┘ │ :8080 (internal) ▼ ┌──────────────────┐ ┌───────────────────────┐ │ Dashboard │─────────▶ Docker Socket Proxy │ │ FastAPI + React │ :2375 │ (allowlist only) │ └──────┬───────────┘ internal└──────────┬────────────┘ │ │ /var/run/docker.sock │ :3306 (internal) ▼ ▼ Docker Engine ┌─────────────┐ │ │ MariaDB │ ┌──────────────┼──────────────┐ └─────────────┘ bot-a bot-b bot-n [512MB] [512MB] [512MB] [0.5CPU] [0.5CPU] [0.5CPU]
Internet │ ▼ :80 / :443 ┌──────────────┐ │ Nginx │ TLS · reverse proxy · static assets └──────┬───────┘ │ :8080 (internal) ▼ ┌──────────────────┐ ┌───────────────────────┐ │ Dashboard │─────────▶ Docker Socket Proxy │ │ FastAPI + React │ :2375 │ (allowlist only) │ └──────┬───────────┘ internal└──────────┬────────────┘ │ │ /var/run/docker.sock │ :3306 (internal) ▼ ▼ Docker Engine ┌─────────────┐ │ │ MariaDB │ ┌──────────────┼──────────────┐ └─────────────┘ bot-a bot-b bot-n [512MB] [512MB] [512MB] [0.5CPU] [0.5CPU] [0.5CPU]
Internet │ ▼ :80 / :443 ┌──────────────┐ │ Nginx │ TLS · reverse proxy · static assets └──────┬───────┘ │ :8080 (internal) ▼ ┌──────────────────┐ ┌───────────────────────┐ │ Dashboard │─────────▶ Docker Socket Proxy │ │ FastAPI + React │ :2375 │ (allowlist only) │ └──────┬───────────┘ internal└──────────┬────────────┘ │ │ /var/run/docker.sock │ :3306 (internal) ▼ ▼ Docker Engine ┌─────────────┐ │ │ MariaDB │ ┌──────────────┼──────────────┐ └─────────────┘ bot-a bot-b bot-n [512MB] [512MB] [512MB] [0.5CPU] [0.5CPU] [0.5CPU]
import os
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt(plaintext: str, master_key: bytes) -> str: iv = os.urandom(12) # 96-bit random IV per encryption aesgcm = AESGCM(master_key) ciphertext = aesgcm.encrypt(iv, plaintext.encode(), None) # Store IV + ciphertext together return base64.b64encode(iv + ciphertext).decode() def decrypt(encrypted: str, master_key: bytes) -> str: data = base64.b64decode(encrypted) iv, ciphertext = data[:12], data[12:] aesgcm = AESGCM(master_key) return aesgcm.decrypt(iv, ciphertext, None).decode()
import os
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt(plaintext: str, master_key: bytes) -> str: iv = os.urandom(12) # 96-bit random IV per encryption aesgcm = AESGCM(master_key) ciphertext = aesgcm.encrypt(iv, plaintext.encode(), None) # Store IV + ciphertext together return base64.b64encode(iv + ciphertext).decode() def decrypt(encrypted: str, master_key: bytes) -> str: data = base64.b64decode(encrypted) iv, ciphertext = data[:12], data[12:] aesgcm = AESGCM(master_key) return aesgcm.decrypt(iv, ciphertext, None).decode()
import os
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt(plaintext: str, master_key: bytes) -> str: iv = os.urandom(12) # 96-bit random IV per encryption aesgcm = AESGCM(master_key) ciphertext = aesgcm.encrypt(iv, plaintext.encode(), None) # Store IV + ciphertext together return base64.b64encode(iv + ciphertext).decode() def decrypt(encrypted: str, master_key: bytes) -> str: data = base64.b64decode(encrypted) iv, ciphertext = data[:12], data[12:] aesgcm = AESGCM(master_key) return aesgcm.decrypt(iv, ciphertext, None).decode()
@router.websocket("/ws/logs/{bot_id}")
async def stream_logs(websocket: WebSocket, bot_id: int): await websocket.accept() try: container = docker_client.containers.get(f"bot_{bot_id}") for log_line in container.logs(stream=True, follow=True, tail=50): await websocket.send_text(log_line.decode().strip()) except Exception as exc: await websocket.send_text(f"Stream error: {exc}") finally: await websocket.close()
@router.websocket("/ws/logs/{bot_id}")
async def stream_logs(websocket: WebSocket, bot_id: int): await websocket.accept() try: container = docker_client.containers.get(f"bot_{bot_id}") for log_line in container.logs(stream=True, follow=True, tail=50): await websocket.send_text(log_line.decode().strip()) except Exception as exc: await websocket.send_text(f"Stream error: {exc}") finally: await websocket.close()
@router.websocket("/ws/logs/{bot_id}")
async def stream_logs(websocket: WebSocket, bot_id: int): await websocket.accept() try: container = docker_client.containers.get(f"bot_{bot_id}") for log_line in container.logs(stream=True, follow=True, tail=50): await websocket.send_text(log_line.decode().strip()) except Exception as exc: await websocket.send_text(f"Stream error: {exc}") finally: await websocket.close()
import os
import json
from bot_logger import BotLogger logger = BotLogger() try: creds = json.loads(os.environ.get("BOT_CREDENTIALS", "{}")) logger.log("INFO", "Bot started") records = process_data(creds) logger.log("INFO", f"Cycle complete: {records} records processed") logger.metric("records_processed", records) finally: logger.close(exit_code=0)
import os
import json
from bot_logger import BotLogger logger = BotLogger() try: creds = json.loads(os.environ.get("BOT_CREDENTIALS", "{}")) logger.log("INFO", "Bot started") records = process_data(creds) logger.log("INFO", f"Cycle complete: {records} records processed") logger.metric("records_processed", records) finally: logger.close(exit_code=0)
import os
import json
from bot_logger import BotLogger logger = BotLogger() try: creds = json.loads(os.environ.get("BOT_CREDENTIALS", "{}")) logger.log("INFO", "Bot started") records = process_data(creds) logger.log("INFO", f"Cycle complete: {records} records processed") logger.metric("records_processed", records) finally: logger.close(exit_code=0)