#!/usr/bin/env python3 """ Integration script between apt-ostree and deb-bootc-image-builder This script demonstrates the complete Debian Atomic pipeline: 1. apt-ostree creates OSTree commits from treefiles 2. deb-bootc-image-builder creates bootable images from OSTree commits This mimics the Fedora Atomic flow: treefiles โ†’ rpm-ostree โ†’ bootc-image-builder """ import os import sys import subprocess import json import tempfile import shutil from pathlib import Path from typing import Dict, Any, Optional class DebianAtomicPipeline: """Complete Debian Atomic pipeline integration""" def __init__(self, workspace_path: str): self.workspace_path = Path(workspace_path) self.apt_ostree_path = self.workspace_path / "apt-ostree" self.deb_bootc_path = self.workspace_path / "deb-bootc-image-builder" self.atomic_configs_path = self.workspace_path / "debian-atomic-configs" # Validate paths self._validate_paths() # Temporary working directory self.temp_dir = Path(tempfile.mkdtemp(prefix="debian-atomic-")) print(f"Working directory: {self.temp_dir}") def _validate_paths(self): """Validate that all required components exist""" required_paths = [ self.apt_ostree_path, self.deb_bootc_path, self.atomic_configs_path ] for path in required_paths: if not path.exists(): raise FileNotFoundError(f"Required path not found: {path}") print("โœ… All required components found") def create_ostree_commit(self, treefile: str, variant: str) -> Dict[str, Any]: """Step 1: Use apt-ostree to create OSTree commit from treefile""" print(f"\n๐Ÿ”ง Step 1: Creating OSTree commit from {treefile}") # Build apt-ostree if needed apt_ostree_bin = self.apt_ostree_path / "target" / "debug" / "apt-ostree" if not apt_ostree_bin.exists(): print("Building apt-ostree...") self._build_apt_ostree() # Create OSTree commit treefile_path = self.atomic_configs_path / "treefiles" / treefile if not treefile_path.exists(): raise FileNotFoundError(f"Treefile not found: {treefile_path}") # Run apt-ostree compose result = subprocess.run([ str(apt_ostree_bin), "compose", str(treefile_path) ], capture_output=True, text=True, cwd=self.temp_dir) if result.returncode != 0: print(f"Error creating OSTree commit: {result.stderr}") raise RuntimeError("Failed to create OSTree commit") print("โœ… OSTree commit created successfully") # Extract commit information commit_info = self._extract_commit_info() return commit_info def _build_apt_ostree(self): """Build apt-ostree binary""" print("Building apt-ostree...") result = subprocess.run([ "cargo", "build", "--bin", "apt-ostree" ], cwd=self.apt_ostree_path, capture_output=True, text=True) if result.returncode != 0: print(f"Build failed: {result.stderr}") raise RuntimeError("Failed to build apt-ostree") print("โœ… apt-ostree built successfully") def _extract_commit_info(self) -> Dict[str, Any]: """Extract commit information from apt-ostree output""" # Look for the repository created by apt-ostree repo_path = self.temp_dir / "apt-ostree-repo" if not repo_path.exists(): raise RuntimeError("OSTree repository not found") # Get commit information commits_dir = repo_path / "commits" if commits_dir.exists(): commits = list(commits_dir.iterdir()) if commits: commit_id = commits[0].name print(f"Found commit: {commit_id}") # Read metadata metadata_file = commits[0] / "metadata.json" if metadata_file.exists(): with open(metadata_file) as f: metadata = json.load(f) return { "commit_id": commit_id, "repo_path": str(repo_path), "metadata": metadata } raise RuntimeError("No commits found in repository") def create_bootable_image(self, commit_info: Dict[str, Any], output_format: str = "raw") -> str: """Step 2: Use deb-bootc-image-builder to create bootable image from OSTree commit""" print(f"\n๐Ÿ”ง Step 2: Creating bootable image from OSTree commit") # Check if deb-bootc-image-builder binary exists bib_bin = self.deb_bootc_path / "bib" / "bootc-image-builder" if not bib_bin.exists(): print("Building deb-bootc-image-builder...") self._build_deb_bootc() # Create a recipe that uses the OSTree commit recipe = self._create_ostree_recipe(commit_info) recipe_path = self.temp_dir / "ostree-recipe.yml" with open(recipe_path, 'w') as f: json.dump(recipe, f, indent=2) print(f"Created recipe: {recipe_path}") # Run deb-bootc-image-builder output_image = self.temp_dir / f"debian-atomic-{output_format}.img" result = subprocess.run([ str(bib_bin), "build", str(recipe_path), "--output", str(output_image) ], capture_output=True, text=True, cwd=self.temp_dir) if result.returncode != 0: print(f"Error creating bootable image: {result.stderr}") raise RuntimeError("Failed to create bootable image") print(f"โœ… Bootable image created: {output_image}") return str(output_image) def _build_deb_bootc(self): """Build deb-bootc-image-builder""" print("Building deb-bootc-image-builder...") result = subprocess.run([ "go", "build", "-o", "bib/bootc-image-builder", "./cmd/bootc-image-builder" ], cwd=self.deb_bootc_path, capture_output=True, text=True) if result.returncode != 0: print(f"Build failed: {result.stderr}") raise RuntimeError("Failed to build deb-bootc-image-builder") print("โœ… deb-bootc-image-builder built successfully") def _create_ostree_recipe(self, commit_info: Dict[str, Any]) -> Dict[str, Any]: """Create a recipe that uses the OSTree commit""" return { "name": "debian-atomic-ostree", "description": "Debian Atomic system built from OSTree commit", "base_image": "debian:trixie-slim", "stages": [ { "name": "ostree_integration", "type": "ostree", "config": { "commit_id": commit_info["commit_id"], "repo_path": commit_info["repo_path"], "branch": commit_info["metadata"].get("branch", "debian-atomic") } }, { "name": "system_config", "type": "system", "config": { "hostname": "debian-atomic", "timezone": "UTC", "locale": "en_US.UTF-8" } } ], "output": { "format": "raw", "size": "5G" } } def run_complete_pipeline(self, treefile: str, variant: str, output_format: str = "raw") -> str: """Run the complete Debian Atomic pipeline""" print(f"๐Ÿš€ Starting Debian Atomic pipeline") print(f"Treefile: {treefile}") print(f"Variant: {variant}") print(f"Output format: {output_format}") try: # Step 1: Create OSTree commit commit_info = self.create_ostree_commit(treefile, variant) # Step 2: Create bootable image output_image = self.create_bootable_image(commit_info, output_format) print(f"\n๐ŸŽ‰ Pipeline completed successfully!") print(f"Output image: {output_image}") return output_image except Exception as e: print(f"โŒ Pipeline failed: {e}") raise def cleanup(self): """Clean up temporary files""" if self.temp_dir.exists(): shutil.rmtree(self.temp_dir) print(f"Cleaned up: {self.temp_dir}") def main(): """Main function to demonstrate the integration""" if len(sys.argv) < 2: print("Usage: python3 integrate-with-apt-ostree.py [variant] [output_format]") print("Example: python3 integrate-with-apt-ostree.py minimal.yaml minimal raw") sys.exit(1) treefile = sys.argv[1] variant = sys.argv[2] if len(sys.argv) > 2 else "minimal" output_format = sys.argv[3] if len(sys.argv) > 3 else "raw" # Get workspace path (assuming script is in deb-bootc-image-builder) script_dir = Path(__file__).parent workspace_path = script_dir.parent try: pipeline = DebianAtomicPipeline(str(workspace_path)) output_image = pipeline.run_complete_pipeline(treefile, variant, output_format) print(f"\n๐Ÿ“‹ Summary:") print(f"โœ… OSTree commit created from {treefile}") print(f"โœ… Bootable image created: {output_image}") print(f"โœ… Debian Atomic pipeline completed successfully!") print(f"\n๐Ÿงช To test the image:") print(f"qemu-system-x86_64 -m 2G -drive file={output_image},format=raw -nographic") except Exception as e: print(f"โŒ Pipeline failed: {e}") sys.exit(1) finally: # Don't cleanup automatically for testing # pipeline.cleanup() pass if __name__ == "__main__": main()