#!/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())