particle-os-tools/src/apt-ostree.py/daemon/main.py

188 lines
No EOL
6.7 KiB
Python

#!/usr/bin/env python3
"""
apt-ostree Daemon - New dbus-next implementation with proper decoupling
Atomic package management system for Debian/Ubuntu inspired by rpm-ostree
"""
import asyncio
import argparse
import logging
import signal
import sys
import os
from pathlib import Path
from typing import Optional
# Add the parent directory to Python path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.daemon import AptOstreeDaemon
from daemon.interfaces.sysroot_interface import AptOstreeSysrootInterface, AptOstreeOSInterface
from dbus_next.aio import MessageBus
from dbus_next import BusType
class AptOstreeNewDaemon:
"""New daemon using dbus-next implementation with proper decoupling"""
def __init__(self, pid_file: str = None, config: dict = None):
self.pid_file = pid_file
self.config = config or {}
self.running = False
self.logger = logging.getLogger('daemon')
# Core daemon instance (pure Python, no D-Bus dependencies)
self.core_daemon: Optional[AptOstreeDaemon] = None
# D-Bus components
self.bus: Optional[MessageBus] = None
self.sysroot_interface: Optional[AptOstreeSysrootInterface] = None
self.os_interface: Optional[AptOstreeOSInterface] = None
def _write_pid_file(self):
"""Write PID to file"""
if self.pid_file:
try:
with open(self.pid_file, 'w') as f:
f.write(str(os.getpid()))
os.chmod(self.pid_file, 0o644)
except Exception as e:
print(f"Failed to write PID file: {e}", file=sys.stderr)
def _remove_pid_file(self):
"""Remove PID file"""
if self.pid_file and os.path.exists(self.pid_file):
try:
os.unlink(self.pid_file)
except Exception as e:
print(f"Failed to remove PID file: {e}", file=sys.stderr)
def _setup_signal_handlers(self):
"""Setup signal handlers for graceful shutdown"""
def signal_handler(signum, frame):
self.logger.info(f"Received signal {signum}, shutting down...")
self.running = False
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
async def _setup_dbus(self):
"""Setup D-Bus connection and interfaces"""
try:
# Connect to system bus
self.bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
# Create D-Bus interfaces with core daemon instance
self.sysroot_interface = AptOstreeSysrootInterface(self.core_daemon)
self.os_interface = AptOstreeOSInterface(self.core_daemon)
# Export interfaces
self.bus.export("/org/debian/aptostree1/Sysroot", self.sysroot_interface)
self.bus.export("/org/debian/aptostree1/OS/default", self.os_interface)
# Request D-Bus name
await self.bus.request_name("org.debian.aptostree1")
self.logger.info("D-Bus interfaces exported successfully")
return True
except Exception as e:
self.logger.error(f"Failed to setup D-Bus: {e}")
return False
async def _cleanup_dbus(self):
"""Cleanup D-Bus connection"""
if self.bus:
try:
self.bus.disconnect()
self.logger.info("D-Bus connection closed")
except Exception as e:
self.logger.error(f"Error closing D-Bus connection: {e}")
async def run(self):
"""Run the daemon with proper decoupling"""
try:
# Write PID file if specified
if self.pid_file:
self._write_pid_file()
# Setup signal handlers
self._setup_signal_handlers()
self.logger.info("Starting apt-ostree daemon (dbus-next implementation)")
# Initialize core daemon (pure Python logic)
self.core_daemon = AptOstreeDaemon(self.config, self.logger)
if not await self.core_daemon.initialize():
self.logger.error("Failed to initialize core daemon")
return 1
# Setup D-Bus interfaces (thin wrapper around core daemon)
if not await self._setup_dbus():
self.logger.error("Failed to setup D-Bus interfaces")
return 1
self.running = True
self.logger.info("Daemon started successfully")
# Keep the daemon running with proper signal handling
try:
while self.running:
await asyncio.sleep(1) # Check every second instead of waiting forever
except KeyboardInterrupt:
self.logger.info("Received interrupt signal")
except Exception as e:
self.logger.error(f"Daemon error: {e}")
return 1
finally:
# Cleanup in reverse order
await self._cleanup_dbus()
if self.core_daemon:
await self.core_daemon.shutdown()
self._remove_pid_file()
self.running = False
return 0
def parse_arguments():
"""Parse command line arguments"""
parser = argparse.ArgumentParser(description='apt-ostree System Management Daemon (dbus-next)')
parser.add_argument('--daemon', action='store_true',
help='Run as daemon (default behavior)')
parser.add_argument('--pid-file', type=str,
help='Write PID to specified file')
parser.add_argument('--foreground', action='store_true',
help='Run in foreground (for debugging)')
parser.add_argument('--config', type=str,
help='Path to configuration file')
return parser.parse_args()
def main():
"""Main entry point"""
args = parse_arguments()
# Check if running as root (required for system operations)
if os.geteuid() != 0:
print("apt-ostree daemon must be run as root", file=sys.stderr)
return 1
# Setup basic logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Load configuration (placeholder for now)
config = {
'daemon.auto_update_policy': 'none',
'daemon.idle_exit_timeout': 0,
'sysroot.path': '/',
'sysroot.test_mode': True
}
daemon = AptOstreeNewDaemon(pid_file=args.pid_file, config=config)
# Run the daemon
return asyncio.run(daemon.run())
if __name__ == "__main__":
sys.exit(main())