"""Unified Flask web server for ESPILON C2 dashboard.""" import os import sys import logging import threading from typing import Optional from flask import Flask from werkzeug.serving import make_server from .mlat import MlatEngine from .auth import create_auth_decorators from .routes import ( create_pages_blueprint, create_devices_blueprint, create_cameras_blueprint, create_mlat_blueprint, create_stats_blueprint, ) # Make hp_dashboard importable (lives in espilon-honey-pot/tools/) _HP_TOOLS_DIR = os.environ.get("HP_DASHBOARD_PATH", os.path.normpath(os.path.join( os.path.dirname(__file__), "..", "..", "..", "..", "espilon-honey-pot", "tools" ))) if os.path.isdir(_HP_TOOLS_DIR) and _HP_TOOLS_DIR not in sys.path: sys.path.insert(0, _HP_TOOLS_DIR) # Disable Flask/Werkzeug request logging logging.getLogger('werkzeug').setLevel(logging.ERROR) class UnifiedWebServer: """ Unified Flask-based web server for ESPILON C2. Provides: - Dashboard: View connected ESP32 devices - Cameras: View live camera streams with recording - MLAT: Visualize multilateration positioning """ def __init__(self, host: str = "0.0.0.0", port: int = 8000, image_dir: str = "static/streams", username: str = "admin", password: str = "admin", secret_key: str = "change_this_for_prod", multilat_token: str = "multilat_secret_token", device_registry=None, mlat_engine: Optional[MlatEngine] = None, camera_receiver=None, hp_store=None, hp_commander=None, hp_alerts=None, hp_geo=None): """ Initialize the unified web server. Args: host: Host to bind the server port: Port for the web server image_dir: Directory containing camera frame images username: Login username password: Login password secret_key: Flask session secret key multilat_token: Bearer token for MLAT API device_registry: DeviceRegistry instance for device listing mlat_engine: MlatEngine instance (created if None) camera_receiver: UDPReceiver instance for camera control hp_store: HpStore instance for honeypot event storage hp_commander: HpCommander instance for honeypot command dispatch hp_alerts: HpAlertEngine instance for honeypot alert rules hp_geo: HpGeoLookup instance for geo-IP enrichment """ self.host = host self.port = port self.image_dir = image_dir self.username = username self.password = password self.secret_key = secret_key self.multilat_token = multilat_token self.device_registry = device_registry self.mlat = mlat_engine or MlatEngine() self.camera_receiver = camera_receiver self.hp_store = hp_store self.hp_commander = hp_commander self.hp_alerts = hp_alerts self.hp_geo = hp_geo # C2 root directory self.c2_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Ensure image directory exists full_image_dir = os.path.join(self.c2_root, self.image_dir) os.makedirs(full_image_dir, exist_ok=True) self._app = self._create_app() self._server = None self._thread = None def set_camera_receiver(self, receiver): """Set the camera receiver after initialization.""" self.camera_receiver = receiver @property def is_running(self) -> bool: return self._thread is not None and self._thread.is_alive() def _create_app(self) -> Flask: """Create and configure the Flask application.""" template_dir = os.path.join(self.c2_root, "templates") static_dir = os.path.join(self.c2_root, "static") app = Flask(__name__, template_folder=template_dir, static_folder=static_dir) app.secret_key = self.secret_key # Create auth decorators require_login, require_api_auth = create_auth_decorators( lambda: self.multilat_token ) # Shared config for blueprints base_config = { "c2_root": self.c2_root, "image_dir": self.image_dir, "require_login": require_login, "require_api_auth": require_api_auth, } # Register blueprints app.register_blueprint(create_pages_blueprint({ **base_config, "username": self.username, "password": self.password, })) app.register_blueprint(create_devices_blueprint({ **base_config, "get_device_registry": lambda: self.device_registry, })) app.register_blueprint(create_cameras_blueprint({ **base_config, "get_camera_receiver": lambda: self.camera_receiver, })) app.register_blueprint(create_mlat_blueprint({ **base_config, "get_mlat_engine": lambda: self.mlat, })) app.register_blueprint(create_stats_blueprint({ **base_config, "get_device_registry": lambda: self.device_registry, "get_mlat_engine": lambda: self.mlat, })) # Honeypot dashboard (optional — only if hp_store is provided) if self.hp_store and self.hp_commander: try: from hp_dashboard import create_hp_blueprint app.register_blueprint(create_hp_blueprint({ **base_config, "hp_store": self.hp_store, "hp_commander": self.hp_commander, "hp_alerts": self.hp_alerts, "hp_geo": self.hp_geo, })) except ImportError: pass # hp_dashboard not available return app def start(self) -> bool: """Start the web server in a background thread.""" if self.is_running: return False self._server = make_server(self.host, self.port, self._app, threaded=True) self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) self._thread.start() return True def stop(self): """Stop the web server.""" if self._server: self._server.shutdown() self._server = None self._thread = None def get_url(self) -> str: """Get the server URL.""" return f"http://{self.host}:{self.port}"