#!/usr/bin/env python3 """ Debian Atomic Blueprint Generator for Debian Forge This module provides enhanced blueprint generation for Debian atomic images, integrating with repository management and dependency resolution systems. """ import json import os from typing import Dict, List, Optional, Any from dataclasses import dataclass, asdict from pathlib import Path from datetime import datetime try: from debian_repository_manager import DebianRepositoryManager from debian_package_resolver import DebianPackageResolver except ImportError: DebianRepositoryManager = None DebianPackageResolver = None @dataclass class AtomicBlueprintConfig: """Configuration for atomic blueprint generation""" name: str description: str version: str base_packages: List[str] additional_packages: List[str] = None excluded_packages: List[str] = None suite: str = "bookworm" architecture: str = "amd64" include_recommends: bool = False ostree_ref: str = None users: List[Dict[str, Any]] = None services: Dict[str, List[str]] = None filesystem_customizations: Dict[str, Any] = None class DebianAtomicBlueprintGenerator: """Generates optimized Debian atomic blueprints""" def __init__(self, config_dir: str = None): if DebianRepositoryManager and config_dir: self.repository_manager = DebianRepositoryManager(config_dir) elif DebianRepositoryManager: # Use temporary directory for testing import tempfile temp_dir = tempfile.mkdtemp(prefix="debian-forge-") self.repository_manager = DebianRepositoryManager(temp_dir) else: self.repository_manager = None self.package_resolver = DebianPackageResolver() if DebianPackageResolver else None self.base_packages = [ "systemd", "systemd-sysv", "dbus", "udev", "ostree", "linux-image-amd64" ] def generate_base_blueprint(self, config: AtomicBlueprintConfig = None) -> Dict[str, Any]: """Generate base atomic blueprint""" if config is None: config = AtomicBlueprintConfig( name="debian-atomic-base", description="Debian Atomic Base System", version="1.0.0", base_packages=self.base_packages ) # Resolve package dependencies all_packages = config.base_packages + (config.additional_packages or []) resolved_packages = self._resolve_packages(all_packages, config.suite, config.architecture) # Generate blueprint blueprint = { "name": config.name, "description": config.description, "version": config.version, "distro": f"debian-{config.suite}", "arch": config.architecture, "packages": [{"name": pkg} for pkg in resolved_packages], "modules": [], "groups": [], "customizations": self._generate_base_customizations(config) } # Add OSTree configuration if config.ostree_ref: blueprint["ostree"] = { "ref": config.ostree_ref, "parent": f"debian/{config.suite}/base" } return blueprint def generate_workstation_blueprint(self) -> Dict[str, Any]: """Generate workstation atomic blueprint""" workstation_packages = [ "firefox-esr", "libreoffice", "gnome-core", "gdm3", "network-manager", "pulseaudio", "fonts-dejavu" ] config = AtomicBlueprintConfig( name="debian-atomic-workstation", description="Debian Atomic Workstation", version="1.0.0", base_packages=self.base_packages, additional_packages=workstation_packages, ostree_ref="debian/bookworm/workstation" ) blueprint = self.generate_base_blueprint(config) blueprint["customizations"]["services"]["enabled"].extend([ "gdm3", "NetworkManager", "pulseaudio" ]) return blueprint def generate_server_blueprint(self) -> Dict[str, Any]: """Generate server atomic blueprint""" server_packages = [ "nginx", "postgresql", "redis", "fail2ban", "logrotate", "rsyslog" ] config = AtomicBlueprintConfig( name="debian-atomic-server", description="Debian Atomic Server", version="1.0.0", base_packages=self.base_packages, additional_packages=server_packages, ostree_ref="debian/bookworm/server" ) blueprint = self.generate_base_blueprint(config) blueprint["customizations"]["services"]["enabled"].extend([ "nginx", "postgresql", "redis-server", "fail2ban" ]) return blueprint def generate_container_blueprint(self) -> Dict[str, Any]: """Generate container atomic blueprint""" container_packages = [ "podman", "buildah", "skopeo", "containers-common", "crun" ] config = AtomicBlueprintConfig( name="debian-atomic-container", description="Debian Atomic Container Host", version="1.0.0", base_packages=self.base_packages, additional_packages=container_packages, ostree_ref="debian/bookworm/container" ) blueprint = self.generate_base_blueprint(config) blueprint["customizations"]["services"]["enabled"].extend([ "podman" ]) # Add container-specific configurations blueprint["customizations"]["filesystem"] = { "/var/lib/containers": { "type": "directory", "mode": "0755" } } return blueprint def generate_minimal_blueprint(self) -> Dict[str, Any]: """Generate minimal atomic blueprint""" minimal_packages = [ "systemd", "systemd-sysv", "ostree", "linux-image-amd64" ] config = AtomicBlueprintConfig( name="debian-atomic-minimal", description="Debian Atomic Minimal System", version="1.0.0", base_packages=minimal_packages, ostree_ref="debian/bookworm/minimal" ) return self.generate_base_blueprint(config) def _resolve_packages(self, packages: List[str], suite: str, architecture: str) -> List[str]: """Resolve package dependencies""" if not self.package_resolver: return packages try: resolution = self.package_resolver.resolve_package_dependencies( packages, suite, architecture, include_recommends=False ) if resolution.conflicts: print(f"Warning: Package conflicts detected: {resolution.conflicts}") if resolution.missing: print(f"Warning: Missing packages: {resolution.missing}") return resolution.install_order except Exception as e: print(f"Package resolution failed: {e}") return packages def _generate_base_customizations(self, config: AtomicBlueprintConfig) -> Dict[str, Any]: """Generate base customizations for blueprint""" customizations = { "user": config.users or [ { "name": "debian", "description": "Debian atomic user", "password": "$6$rounds=656000$debian$atomic.system.user", "home": "/home/debian", "shell": "/bin/bash", "groups": ["wheel", "sudo"], "uid": 1000, "gid": 1000 } ], "services": config.services or { "enabled": ["sshd", "systemd-networkd", "systemd-resolved"], "disabled": ["systemd-timesyncd"] }, "kernel": { "append": "ostree=/ostree/boot.1/debian/bookworm/0" } } if config.filesystem_customizations: customizations["filesystem"] = config.filesystem_customizations return customizations def generate_osbuild_manifest(self, blueprint: Dict[str, Any]) -> Dict[str, Any]: """Generate OSBuild manifest from blueprint""" manifest = { "version": "2", "pipelines": [ { "name": "build", "runner": "org.osbuild.linux", "stages": [] } ] } # Add debootstrap stage debootstrap_stage = { "type": "org.osbuild.debootstrap", "options": { "suite": "bookworm", "mirror": "http://deb.debian.org/debian", "arch": blueprint.get("arch", "amd64"), "variant": "minbase", "apt_proxy": "http://192.168.1.101:3142" } } manifest["pipelines"][0]["stages"].append(debootstrap_stage) # Add APT configuration stage apt_config_stage = { "type": "org.osbuild.apt.config", "options": { "sources": self._get_apt_sources(), "preferences": {}, "proxy": "http://192.168.1.101:3142" } } manifest["pipelines"][0]["stages"].append(apt_config_stage) # Add package installation stage package_names = [pkg["name"] for pkg in blueprint["packages"]] apt_stage = { "type": "org.osbuild.apt", "options": { "packages": package_names, "recommends": False, "update": True, "apt_proxy": "http://192.168.1.101:3142" } } manifest["pipelines"][0]["stages"].append(apt_stage) # Add OSTree commit stage ostree_stage = { "type": "org.osbuild.ostree.commit", "options": { "repo": blueprint.get("name", "debian-atomic"), "branch": blueprint.get("ostree", {}).get("ref", f"debian/bookworm/{blueprint['name']}"), "subject": f"Debian atomic {blueprint['name']} system", "body": f"Built from blueprint: {blueprint['name']} v{blueprint['version']}" } } manifest["pipelines"][0]["stages"].append(ostree_stage) return manifest def _get_apt_sources(self) -> Dict[str, Any]: """Get APT sources configuration""" if not self.repository_manager: return { "main": "deb http://deb.debian.org/debian bookworm main", "security": "deb http://security.debian.org/debian-security bookworm-security main", "updates": "deb http://deb.debian.org/debian bookworm-updates main" } return self.repository_manager.generate_apt_config("bookworm", proxy="http://192.168.1.101:3142") def save_blueprint(self, blueprint: Dict[str, Any], output_dir: str = "blueprints") -> str: """Save blueprint to file""" output_path = Path(output_dir) / f"{blueprint['name']}.json" output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, 'w') as f: json.dump(blueprint, f, indent=2) return str(output_path) def validate_blueprint(self, blueprint: Dict[str, Any]) -> Dict[str, Any]: """Validate blueprint structure and content""" validation = { "valid": True, "errors": [], "warnings": [], "suggestions": [] } # Check required fields required_fields = ["name", "description", "version", "packages"] for field in required_fields: if field not in blueprint: validation["valid"] = False validation["errors"].append(f"Missing required field: {field}") # Validate packages if "packages" in blueprint: if not blueprint["packages"]: validation["warnings"].append("No packages specified") package_names = [pkg.get("name") if isinstance(pkg, dict) else pkg for pkg in blueprint["packages"]] # Check for essential packages essential_packages = ["systemd", "ostree"] missing_essential = [pkg for pkg in essential_packages if pkg not in package_names] if missing_essential: validation["suggestions"].append(f"Consider adding essential packages: {missing_essential}") # Validate customizations if "customizations" in blueprint and "services" in blueprint["customizations"]: services = blueprint["customizations"]["services"] if "enabled" in services and "disabled" in services: conflicts = set(services["enabled"]) & set(services["disabled"]) if conflicts: validation["valid"] = False validation["errors"].append(f"Services both enabled and disabled: {list(conflicts)}") return validation def generate_all_blueprints(self, output_dir: str = "blueprints") -> List[str]: """Generate all standard blueprints""" blueprints = [ ("base", self.generate_base_blueprint()), ("workstation", self.generate_workstation_blueprint()), ("server", self.generate_server_blueprint()), ("container", self.generate_container_blueprint()), ("minimal", self.generate_minimal_blueprint()) ] saved_files = [] for name, blueprint in blueprints: try: output_path = self.save_blueprint(blueprint, output_dir) saved_files.append(output_path) print(f"Generated {name} blueprint: {output_path}") except Exception as e: print(f"Failed to generate {name} blueprint: {e}") return saved_files def main(): """Example usage of blueprint generator""" print("Debian Atomic Blueprint Generator") generator = DebianAtomicBlueprintGenerator() # Generate all blueprints print("\nGenerating all blueprints...") saved_files = generator.generate_all_blueprints() print(f"\nGenerated {len(saved_files)} blueprints:") for file_path in saved_files: print(f" - {file_path}") # Example: Generate and validate a custom blueprint print("\nGenerating custom blueprint...") config = AtomicBlueprintConfig( name="debian-atomic-custom", description="Custom Debian Atomic System", version="1.0.0", base_packages=["systemd", "ostree"], additional_packages=["vim", "curl", "wget"], ostree_ref="debian/bookworm/custom" ) custom_blueprint = generator.generate_base_blueprint(config) validation = generator.validate_blueprint(custom_blueprint) print(f"Custom blueprint validation: {'Valid' if validation['valid'] else 'Invalid'}") if validation['errors']: print(f"Errors: {validation['errors']}") if validation['warnings']: print(f"Warnings: {validation['warnings']}") if __name__ == '__main__': main()