diff --git a/stages/org.osbuild.debootstrap.py b/stages/org.osbuild.debootstrap.py index 93a7428c..95680855 100644 --- a/stages/org.osbuild.debootstrap.py +++ b/stages/org.osbuild.debootstrap.py @@ -1,11 +1,29 @@ #!/usr/bin/python3 -import os -import sys -import subprocess -import tempfile -import shutil +""" +Create base Debian filesystem using debootstrap -import osbuild.api +This stage uses debootstrap to create a minimal Debian base system. +Similar to how OSBuild uses dnf/rpm for Fedora, this creates the foundation +for Debian-based builds. + +Uses the following binaries from the host: + * `debootstrap` to create the base Debian filesystem + * `cp` to copy files to the target tree + +This stage will return metadata about the created base system. +""" + +import contextlib +import json +import os +import subprocess +import sys +import tempfile +from operator import itemgetter + +from osbuild import api +from osbuild.util.mnt import mount +from osbuild.util.runners import create_machine_id_if_needed def run_debootstrap(suite, target, mirror, arch=None, variant=None, extra_packages=None, apt_proxy=None): @@ -59,6 +77,54 @@ deb {mirror} {suite}-security main print(f"Created sources.list for {suite}") +def generate_base_metadata(tree, suite, arch): + """Generate metadata about the created base system""" + + # Get dpkg package list (similar to rpm metadata) + try: + cmd = [ + "chroot", tree, + "dpkg-query", "-W", + "--showformat=${Package}\t${Version}\t${Architecture}\t${Status}\n" + ] + + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + + packages = [] + for line in result.stdout.strip().split('\n'): + if line.strip(): + parts = line.split('\t') + if len(parts) >= 4 and 'installed' in parts[3]: + packages.append({ + "name": parts[0], + "version": parts[1], + "architecture": parts[2], + "status": parts[3] + }) + + packages = sorted(packages, key=itemgetter("name")) + + return { + "base_system": { + "suite": suite, + "architecture": arch, + "method": "debootstrap" + }, + "packages": packages + } + + except subprocess.CalledProcessError as e: + print(f"Warning: Could not generate package metadata: {e}") + return { + "base_system": { + "suite": suite, + "architecture": arch, + "method": "debootstrap" + }, + "packages": [] + } + + def main(tree, options): """Main function for debootstrap stage""" @@ -78,45 +144,45 @@ def main(tree, options): print("No mirror specified for debootstrap") return 1 - # Create temporary directory for debootstrap - with tempfile.TemporaryDirectory() as temp_dir: - print(f"Creating base Debian filesystem in {temp_dir}") - - # Run debootstrap - if not run_debootstrap(suite, temp_dir, mirror, arch, variant, extra_packages, apt_proxy): - return 1 - - # Set up apt sources - setup_apt_sources(temp_dir, suite, mirror) - - # Configure apt proxy if specified - if apt_proxy: - print(f"Configuring apt proxy: {apt_proxy}") - proxy_config = f"""Acquire::http::Proxy "{apt_proxy}"; + print(f"Creating base Debian filesystem: {suite} from {mirror}") + + # Run debootstrap directly into the target tree + if not run_debootstrap(suite, tree, mirror, arch, variant, extra_packages, apt_proxy): + return 1 + + # Set up apt sources + setup_apt_sources(tree, suite, mirror) + + # Configure apt proxy if specified + if apt_proxy: + print(f"Configuring apt proxy: {apt_proxy}") + proxy_config = f"""Acquire::http::Proxy "{apt_proxy}"; Acquire::https::Proxy "{apt_proxy}"; """ - proxy_file = os.path.join(temp_dir, "etc/apt/apt.conf.d/99proxy") - os.makedirs(os.path.dirname(proxy_file), exist_ok=True) - with open(proxy_file, "w") as f: - f.write(proxy_config) - - # Copy files to target tree - print(f"Copying filesystem to target tree: {tree}") - - # Ensure target directory exists - os.makedirs(tree, exist_ok=True) - - # Copy all files from temp directory to target - for item in os.listdir(temp_dir): - src = os.path.join(temp_dir, item) - dst = os.path.join(tree, item) - - if os.path.isdir(src): - if os.path.exists(dst): - shutil.rmtree(dst) - shutil.copytree(src, dst) - else: - shutil.copy2(src, dst) + proxy_file = os.path.join(tree, "etc/apt/apt.conf.d/99proxy") + os.makedirs(os.path.dirname(proxy_file), exist_ok=True) + with open(proxy_file, "w") as f: + f.write(proxy_config) + + # Mount essential filesystems for package operations (similar to rpm stage) + for source in ("/dev", "/sys", "/proc"): + target = os.path.join(tree, source.lstrip("/")) + os.makedirs(target, exist_ok=True) + mount(source, target, ro=False) + + # Create /dev/fd symlink + os.symlink("/proc/self/fd", f"{tree}/dev/fd") + + # Generate metadata about the created system + try: + metadata = generate_base_metadata(tree, suite, arch) + api.metadata(metadata) + except Exception as e: + print(f"Warning: Could not generate metadata: {e}") + + # Remove random seed if it exists (like rpm stage does) + with contextlib.suppress(FileNotFoundError): + os.unlink(f"{tree}/var/lib/systemd/random-seed") print("Base Debian filesystem created successfully") return 0