mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 22:07:24 +00:00
feat(eye-agent): Add main entry point and systemd service
- EyeAgent class coordinates DeviceManager and MetricsBridge - Async polling loop with configurable interval from SecuBox config - Signal handlers (SIGTERM, SIGINT) for graceful shutdown - PID file management in /run/secubox-eye/ - Executable wrapper script for convenience - systemd service unit with auto-restart and RuntimeDirectory Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9b5a55ce9d
commit
2c8f8f7dc5
163
remote-ui/round/agent/main.py
Normal file
163
remote-ui/round/agent/main.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
"""
|
||||
SecuBox Eye Remote — Agent Main
|
||||
Entry point for the Eye Remote agent service.
|
||||
|
||||
CyberMind — https://cybermind.fr
|
||||
Author: Gerald Kerma <gandalf@gk2.net>
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .config import load_config, DEFAULT_CONFIG_PATH
|
||||
from .device_manager import DeviceManager
|
||||
from .metrics_bridge import MetricsBridge
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Default paths
|
||||
SOCKET_PATH = Path("/run/secubox-eye/metrics.sock")
|
||||
PID_FILE = Path("/run/secubox-eye/agent.pid")
|
||||
|
||||
|
||||
class EyeAgent:
|
||||
"""
|
||||
Main Eye Remote agent.
|
||||
|
||||
Coordinates:
|
||||
- DeviceManager: SecuBox connections
|
||||
- MetricsBridge: Dashboard communication
|
||||
- Polling loop: Regular metrics updates
|
||||
"""
|
||||
|
||||
def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH):
|
||||
self.config_path = config_path
|
||||
self.config = None
|
||||
self.device_manager = None
|
||||
self.metrics_bridge = None
|
||||
self._running = False
|
||||
self._poll_task = None
|
||||
self._bridge_task = None
|
||||
|
||||
async def start(self):
|
||||
"""Start the agent."""
|
||||
log.info("Starting Eye Remote Agent v2.0.0")
|
||||
|
||||
# Load config
|
||||
try:
|
||||
self.config = load_config(self.config_path)
|
||||
log.info("Loaded config: device=%s, secuboxes=%d",
|
||||
self.config.device.id, len(self.config.secuboxes))
|
||||
except Exception as e:
|
||||
log.error("Failed to load config: %s", e)
|
||||
sys.exit(1)
|
||||
|
||||
# Create components
|
||||
self.device_manager = DeviceManager(self.config)
|
||||
self.metrics_bridge = MetricsBridge(socket_path=SOCKET_PATH)
|
||||
|
||||
# Wire up metrics updates
|
||||
self.device_manager.add_listener(self._on_metrics_update)
|
||||
|
||||
# Connect to SecuBox
|
||||
await self.device_manager.connect()
|
||||
|
||||
# Start services
|
||||
self._running = True
|
||||
self._bridge_task = asyncio.create_task(self.metrics_bridge.start())
|
||||
self._poll_task = asyncio.create_task(self._poll_loop())
|
||||
|
||||
# Write PID file
|
||||
PID_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
PID_FILE.write_text(str(os.getpid()))
|
||||
|
||||
log.info("Agent started")
|
||||
|
||||
# Wait for shutdown
|
||||
await asyncio.gather(self._bridge_task, self._poll_task, return_exceptions=True)
|
||||
|
||||
def _on_metrics_update(self, metrics: dict, secubox_name: str, transport: str):
|
||||
"""Handle metrics update from device manager."""
|
||||
self.metrics_bridge.update_metrics(
|
||||
metrics=metrics,
|
||||
secubox_name=secubox_name,
|
||||
transport=transport,
|
||||
secubox_host=self.device_manager.active_secubox.host if self.device_manager.active_secubox else ""
|
||||
)
|
||||
|
||||
async def _poll_loop(self):
|
||||
"""Main polling loop."""
|
||||
while self._running:
|
||||
try:
|
||||
await self.device_manager.poll_metrics()
|
||||
except Exception as e:
|
||||
log.warning("Poll error: %s", e)
|
||||
|
||||
# Get poll interval from active config
|
||||
interval = 2.0
|
||||
if self.device_manager.active_secubox:
|
||||
interval = self.device_manager.active_secubox.poll_interval
|
||||
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
async def stop(self):
|
||||
"""Stop the agent."""
|
||||
log.info("Stopping agent...")
|
||||
self._running = False
|
||||
|
||||
if self._poll_task:
|
||||
self._poll_task.cancel()
|
||||
|
||||
self.metrics_bridge.stop()
|
||||
|
||||
if self._bridge_task:
|
||||
self._bridge_task.cancel()
|
||||
|
||||
await self.device_manager.close()
|
||||
|
||||
# Remove PID file
|
||||
if PID_FILE.exists():
|
||||
PID_FILE.unlink()
|
||||
|
||||
log.info("Agent stopped")
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point."""
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||||
)
|
||||
|
||||
# Parse args
|
||||
config_path = DEFAULT_CONFIG_PATH
|
||||
if len(sys.argv) > 1:
|
||||
config_path = Path(sys.argv[1])
|
||||
|
||||
agent = EyeAgent(config_path)
|
||||
|
||||
# Handle signals
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
def handle_signal():
|
||||
asyncio.create_task(agent.stop())
|
||||
|
||||
for sig in (signal.SIGTERM, signal.SIGINT):
|
||||
loop.add_signal_handler(sig, handle_signal)
|
||||
|
||||
await agent.start()
|
||||
|
||||
|
||||
def run():
|
||||
"""Run the agent."""
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
6
remote-ui/round/secubox-eye-agent
Executable file
6
remote-ui/round/secubox-eye-agent
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
"""SecuBox Eye Remote Agent executable."""
|
||||
from agent.main import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
20
remote-ui/round/secubox-eye-agent.service
Normal file
20
remote-ui/round/secubox-eye-agent.service
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[Unit]
|
||||
Description=SecuBox Eye Remote Agent
|
||||
After=network.target pigpiod.service
|
||||
Wants=pigpiod.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/python3 /usr/lib/secubox-eye/agent/main.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# Runtime directory
|
||||
RuntimeDirectory=secubox-eye
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Loading…
Reference in New Issue
Block a user