#!/usr/bin/env python3 """ This file contains all code required to generate the boot image for live-bootstrap """ # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022-2023 Dor Askayo # SPDX-FileCopyrightText: 2021 Andrius Štikonas # SPDX-FileCopyrightText: 2021 Melg Eight # SPDX-FileCopyrightText: 2021-23 Samuel Tyler import hashlib import os import random import shutil import struct import tarfile import traceback import requests # pylint: disable=too-many-instance-attributes class Generator(): """ Class responsible for generating the basic media to be consumed. """ git_dir = os.path.join(os.path.dirname(os.path.join(__file__)), '..') distfiles_dir = os.path.join(git_dir, 'distfiles') raw_container_magic = b'LBPAYLD1' # pylint: disable=too-many-arguments,too-many-positional-arguments def __init__(self, arch, external_sources, early_preseed, repo_path, mirrors, build_guix_also=False): self.arch = arch self.early_preseed = early_preseed self.external_sources = external_sources self.repo_path = repo_path self.mirrors = mirrors self.build_guix_also = build_guix_also self.pre_network_source_manifest = self.get_source_manifest( stop_before_improve="get_network", build_guix_also=False, ) self.pre_import_source_manifest = self.get_source_manifest( stop_before_improve="import_payload", build_guix_also=False, ) # Only raw-external mode needs full upfront availability for container generation. if self.external_sources and not self.repo_path: self.source_manifest = self.get_source_manifest(build_guix_also=self.build_guix_also) else: self.source_manifest = self.pre_network_source_manifest self.bootstrap_source_manifest = self.source_manifest self.external_source_manifest = [] self.external_image = None self.kernel_bootstrap_mode = None self.target_dir = None self.external_dir = None def reuse(self, target): """ Reuse a previously prepared bwrap environment for further stages. """ self.target_dir = target.path self.external_dir = os.path.join(self.target_dir, 'external') self.distfiles() def _select_kernel_bootstrap_mode(self): """ Select how kernel-bootstrap should transport distfiles. """ if self.repo_path: # Keep second-disk staging outside init image for ext3 repo mode. self.external_dir = os.path.join(os.path.dirname(self.target_dir), 'external') self.kernel_bootstrap_mode = "repo" self.external_source_manifest = [] return if self.external_sources: # Raw external container mode keeps early distfiles inside init image. self.external_dir = os.path.join(self.target_dir, 'external') self.kernel_bootstrap_mode = "raw_external" self._prepare_kernel_bootstrap_external_manifests() return # Network-only mode keeps pre-network distfiles inside init image. self.external_dir = os.path.join(self.target_dir, 'external') self.kernel_bootstrap_mode = "network_only" self.bootstrap_source_manifest = self.pre_network_source_manifest self.external_source_manifest = [] def _prepare_kernel_bootstrap_external_manifests(self): """ Split distfiles between init image and external raw container. """ # Keep the early builder image small: include only sources needed # before improve: import_payload runs, so external.img is the primary # carrier for the remaining distfiles. self.bootstrap_source_manifest = self.pre_import_source_manifest full_manifest = self.get_source_manifest(build_guix_also=self.build_guix_also) if self.bootstrap_source_manifest == full_manifest: raise ValueError("steps/manifest must include `improve: import_payload` in kernel-bootstrap mode.") bootstrap_set = set(self.bootstrap_source_manifest) self.external_source_manifest = [entry for entry in full_manifest if entry not in bootstrap_set] def _kernel_bootstrap_init_manifest(self): """ Return the exact manifest that is allowed inside init image. """ mode_to_manifest = { "network_only": self.pre_network_source_manifest, # up to get_network "raw_external": self.bootstrap_source_manifest, # up to import_payload "repo": self.pre_network_source_manifest, # up to get_network } manifest = mode_to_manifest.get(self.kernel_bootstrap_mode) if manifest is None: raise ValueError(f"Unexpected kernel bootstrap mode: {self.kernel_bootstrap_mode}") return manifest def _copy_manifest_distfiles(self, out_dir, manifest): os.makedirs(out_dir, exist_ok=True) for entry in manifest: file_name = entry[3].strip() shutil.copy2(os.path.join(self.distfiles_dir, file_name), os.path.join(out_dir, file_name)) def _ensure_manifest_distfiles(self, manifest): for entry in manifest: checksum, directory, url, file_name = entry distfile_path = os.path.join(directory, file_name) if not os.path.isfile(distfile_path): self.download_file(url, directory, file_name) self.check_file(distfile_path, checksum) def _create_raw_container_image(self, target_path, manifest, image_name="external.img"): if manifest is None: manifest = [] if manifest: # Guarantee all payload distfiles exist and match checksums. self._ensure_manifest_distfiles(manifest) files_by_name = {} for checksum, _, _, file_name in manifest: if file_name in files_by_name and files_by_name[file_name] != checksum: raise ValueError( f"Conflicting container file with same name but different hash: {file_name}" ) files_by_name[file_name] = checksum container_path = os.path.join(target_path, image_name) ordered_names = sorted(files_by_name.keys()) with open(container_path, "wb") as container: container.write(self.raw_container_magic) if len(ordered_names) > 0xFFFFFFFFFFFFFFFF: raise ValueError("Too many files for raw container format.") container.write(struct.pack(" 0xFFFFFFFFFFFFFFFF: raise ValueError(f"Container file name too long: {file_name}") src_path = os.path.join(self.distfiles_dir, file_name) file_size = os.path.getsize(src_path) if file_size > 0xFFFFFFFFFFFFFFFF: raise ValueError(f"Container file too large for raw container format: {file_name}") container.write(struct.pack(" 3: file_name = source[3] else: # Automatically determine file name based on URL. file_name = os.path.basename(source[1]) entry = (source[2], directory, source[1], file_name) if entry not in entries: entries.append(entry) return entries stage0_arch_map = { "amd64": "AMD64", }