feat: Integrate apt-layer.sh with apt-ostree.py daemon via D-Bus
Some checks failed
Compile apt-layer (v2) / compile (push) Has been cancelled

- Added 20-daemon-integration.sh scriptlet for D-Bus and daemon lifecycle management
- Updated 99-main.sh with new daemon subcommands (start, stop, status, install, uninstall, test, layer, deploy, upgrade, rollback)
- Enhanced help and usage text for daemon integration
- Fixed bash syntax errors in daemon integration scriptlet
- Updated compile.sh to include daemon integration in build process
- Updated .gitignore to exclude src/rpm-ostree/ reference source
- Updated CHANGELOG.md and TODO.md to document daemon integration milestone
- Removed src/rpm-ostree/ from git tracking (reference only, not committed)
This commit is contained in:
robojerk 2025-07-15 17:08:15 -07:00
parent b913406438
commit a23b4e53fd
69 changed files with 24120 additions and 8 deletions

View file

@ -0,0 +1,226 @@
"""
Structured logging for apt-ostree daemon
"""
import logging
import json
import sys
import os
from datetime import datetime
from typing import Dict, Any, Optional
from logging.handlers import RotatingFileHandler
class StructuredFormatter(logging.Formatter):
"""JSON formatter for structured logging"""
def format(self, record):
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno,
'process_id': record.process,
'thread_id': record.thread
}
# Add exception info if present
if record.exc_info:
log_entry['exception'] = self.formatException(record.exc_info)
# Add extra fields
if hasattr(record, 'extra_fields'):
log_entry.update(record.extra_fields)
return json.dumps(log_entry)
class TextFormatter(logging.Formatter):
"""Text formatter for human-readable logs"""
def format(self, record):
# Add extra fields to message if present
if hasattr(record, 'extra_fields'):
extra_str = ' '.join([f"{k}={v}" for k, v in record.extra_fields.items()])
record.msg = f"{record.msg} [{extra_str}]"
return super().format(record)
class AptOstreeLogger:
"""Centralized logging for apt-ostree daemon"""
def __init__(self, config: Dict[str, Any]):
self.config = config
self.logging_config = config.get('daemon', {}).get('logging', {})
self._setup_logging()
def _setup_logging(self):
"""Setup logging configuration"""
# Create logger
logger = logging.getLogger('apt-ostree')
logger.setLevel(getattr(logging, self.logging_config.get('level', 'INFO')))
# Clear existing handlers
logger.handlers.clear()
# Console handler
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
# Set formatter based on configuration
if self.logging_config.get('format') == 'json':
console_formatter = StructuredFormatter()
else:
console_formatter = TextFormatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)
# File handler
log_file = self.logging_config.get('file', '/var/log/apt-ostree/daemon.log')
if log_file:
try:
# Ensure log directory exists
os.makedirs(os.path.dirname(log_file), exist_ok=True)
# Create rotating file handler
max_size = self._parse_size(self.logging_config.get('max_size', '100MB'))
max_files = self.logging_config.get('max_files', 5)
file_handler = RotatingFileHandler(
log_file,
maxBytes=max_size,
backupCount=max_files
)
file_handler.setLevel(logging.DEBUG)
# Always use JSON format for file logging
file_formatter = StructuredFormatter()
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
except Exception as e:
# Fallback to console only if file logging fails
print(f"Failed to setup file logging: {e}", file=sys.stderr)
def _parse_size(self, size_str: str) -> int:
"""Parse size string (e.g., '100MB') to bytes"""
try:
size_str = size_str.upper()
if size_str.endswith('KB'):
return int(size_str[:-2]) * 1024
elif size_str.endswith('MB'):
return int(size_str[:-2]) * 1024 * 1024
elif size_str.endswith('GB'):
return int(size_str[:-2]) * 1024 * 1024 * 1024
else:
return int(size_str)
except (ValueError, AttributeError):
return 100 * 1024 * 1024 # Default to 100MB
def get_logger(self, name: str) -> logging.Logger:
"""Get logger with structured logging support"""
logger = logging.getLogger(f'apt-ostree.{name}')
# Add extra_fields method for structured logging
def log_with_fields(level, message, **kwargs):
record = logger.makeRecord(
logger.name, level, '', 0, message, (), None
)
record.extra_fields = kwargs
logger.handle(record)
logger.log_with_fields = log_with_fields
return logger
def setup_systemd_logging(self):
"""Setup systemd journal logging"""
try:
import systemd.journal
# Add systemd journal handler
logger = logging.getLogger('apt-ostree')
journal_handler = systemd.journal.JournalHandler(
level=logging.INFO,
identifier='apt-ostree'
)
# Use JSON formatter for systemd
journal_formatter = StructuredFormatter()
journal_handler.setFormatter(journal_formatter)
logger.addHandler(journal_handler)
except ImportError:
# systemd-python not available
pass
except Exception as e:
print(f"Failed to setup systemd logging: {e}", file=sys.stderr)
def setup_logging(level: int = logging.INFO, format_type: str = 'json'):
"""Setup basic logging configuration"""
logging.basicConfig(
level=level,
format='%(asctime)s %(name)s[%(process)d]: %(levelname)s: %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.StreamHandler(sys.stderr)
]
)
def setup_signal_handlers(handler):
"""Setup signal handlers for graceful shutdown"""
import signal
def signal_handler(signum, frame):
handler(signum, frame)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def setup_systemd_notification():
"""Setup systemd notification"""
try:
import systemd.daemon
systemd.daemon.notify("READY=1")
return True
except ImportError:
return False
def update_systemd_status(status: str):
"""Update systemd status"""
try:
import systemd.daemon
systemd.daemon.notify(f"STATUS={status}")
return True
except ImportError:
return False
def require_root():
"""Require root privileges"""
if os.geteuid() != 0:
raise PermissionError("This operation requires root privileges")
def check_ostree_boot():
"""Check if system is booted via OSTree"""
return os.path.exists("/run/ostree-booted")
def get_sysroot_path() -> str:
"""Get sysroot path"""
return os.environ.get("APT_OSTREE_SYSROOT", "/")
def setup_environment():
"""Setup environment variables"""
# Disable GVFS
os.environ["GIO_USE_VFS"] = "local"
# Disable dconf for root
if os.geteuid() == 0:
os.environ["GSETTINGS_BACKEND"] = "memory"
# Disable filelist downloads for performance
os.environ["DOWNLOAD_FILELISTS"] = "false"