# SPDX-FileCopyrightText: 2023-2025 PeppermintOS Team # (peppermintosteam@proton.me) # # SPDX-License-Identifier: GPL-3.0-or-later # # This script is the main function to execute the entire ISO build process for Peppermint OS. # # Credits: # - PeppermintOS Team (peppermintosteam@proton.me) - Development and maintenance of the project. # # License: # This code is distributed under the GNU General Public License version 3 or later (GPL-3.0-or-later). # For more details, please refer to the LICENSE file included in the project or visit: # https://www.gnu.org/licenses/gpl-3.0-or-later.html import os import sys import logging import datetime import platform import argparse print(f"Python version in use: {sys.version_info}") BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, BASE_DIR) try: from builder.core.bootstrap.fusato_structure import create_fusato_structure from builder.core.command_runner import run_command from builder.core.bootstrap.paths import paths from builder.core.bootstrap import copy_system_files from builder.core.config_loader import load_all_configurations, load_yaml_config from builder.core.bootstrap import yaml_repo_loader from builder.core.xbps_commands import xbps_commands from builder.core.initramfs_builder import create_initramfs, copy_kernel_image from builder.core.iso_generator import iso_generator_main from builder.core.rootfs_cleanup import cleanup_rootfs, post_install_cleanup from builder.core import bootloaders from builder.core.squashfs import create_squashfs_image from builder.core.mount_helper import mount_essential_filesystems_in_chroot, unmount_essential_filesystems_in_chroot from builder.core.packages_install import install_common_packages_rootfs_yaml from builder.core.install_desktop import install_desktop_environment from builder.core.install_kernel import install_kernel from builder.core.system_reconfigure import reconfigure_system_in_chroot from builder.configs import logger_config from builder.core.customize_system import customize_system from builder.core.copy_customizations import copy_custom_files from builder.core.final_cleanup import remove_fusato_directory from builder.core.move_iso import move_and_cleanup_iso from builder.core.bootstrap import bootstrap except ImportError as e: print(f"Error importing necessary modules: {e}. Please ensure all dependencies are installed.") sys.exit(1) logger = logger_config.setup_logger('iso_builder') def iso_builder_main( iso_build_dir, repos_yaml_file, architectures_config, build_stage_dir, common_packages_config, packages_list, architecture, desktop, desktops_config, kernel_type, kernels_config, iso_profile, iso_name_base, iso_build_config, output_iso_path, full_iso_profile_config_dict, Volume_ID, efi_boot_dir_name, boot_type ): create_fusato_structure() build_rootfs = True build_peptarget = True build_pephost = True logger.info(f"=> Starting ISO build for: Architecture={architecture}, Desktop={desktop}, Kernel={kernel_type}") logger.info("=> Initiating the ISO building process...") logger.info("=> Creating base directories using fusato_structure...") logger.info("=> Base directories created successfully.") logger.info("=> Initiating the Void Linux system bootstrap...") host_arch = platform.machine() xbps_cachedir_env = os.path.join(os.getcwd(), "xbps_package_cache", architecture) xbps_host_cachedir_env = os.path.join(os.getcwd(), "xbps_package_cache", host_arch) os.environ["XBPS_CACHEDIR"] = xbps_cachedir_env os.environ["XBPS_HOST_CACHEDIR"] = xbps_host_cachedir_env os.environ["XBPS_ARCH"] = architecture logger.info(f"=> XBPS_CACHEDIR (Target): {os.environ.get('XBPS_CACHEDIR')}") logger.info(f"=> XBPS_HOST_CACHEDIR (Host): {os.environ.get('XBPS_HOST_CACHEDIR')}") logger.info(f"=> XBPS_ARCH (Target): {os.environ.get('XBPS_ARCH')}") logger.info("=> Copying Void keys to ROOTFS, PEP-HOST, and PEP-TARGET...") copy_system_files.copy_void_keys_python(paths['ROOTFS']) copy_system_files.copy_void_keys_python(paths['PEPHOSTDIR']) copy_system_files.copy_void_keys_python(paths['PEPTARGETDIR']) logger.info("=> Void keys copied.") base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) version_file_path = os.path.join(base_dir, "version") try: with open(version_file_path, 'r') as f: version = f.readline().strip() logger.info(f"=> Version read from file: {version}") except FileNotFoundError: logger.error(f"=> Version file not found at: {version_file_path}. Using default version.") version = "0.0.0" except Exception as e: logger.error(f"=> Error reading version file: {e}. Using default version.") version = "0.0.0" iso_filename_info = f"{iso_name_base}-{architecture}-{kernel_type}-{version}.iso" logger.info(f"=> ISO filename will be: {os.path.join(paths['ISO_OUTPUT_DIR'], iso_filename_info)}") repo_data = yaml_repo_loader.load_repositories_from_yaml(repos_yaml_file) repositories = repo_data.get('repositories', []) all_bootstrap_packages = repo_data.get('bootstrap_packages', {}) if build_rootfs: logger.info("=> Initiating the Void Linux system bootstrap for ROOTFS (host execution)...") rootfs_bootstrap_args = bootstrap.construct_xbps_install_args( "rootfs", paths, architecture, repositories, all_bootstrap_packages ) if rootfs_bootstrap_args: initial_bootstrap_command = ["/usr/bin/xbps-install"] + rootfs_bootstrap_args logger.info(f"Executing initial ROOTFS bootstrap command (host): {' '.join(initial_bootstrap_command)}") run_command(initial_bootstrap_command) logger.info("=> Void Linux ROOTFS initial bootstrap COMPLETE.") else: logger.warning("Skipping ROOTFS bootstrap due to no packages defined or error.") mount_essential_filesystems_in_chroot(paths, logger) logger.info("=> Essential filesystems mounted in the ROOTFS.") if build_pephost: logger.info("=> Initiating the Void Linux system bootstrap for PEP-HOST (host execution)...") pephost_bootstrap_args = bootstrap.construct_xbps_install_args( "pep-host", paths, architecture, repositories, all_bootstrap_packages ) if pephost_bootstrap_args: pephost_bootstrap_command = ["/usr/bin/xbps-install"] + pephost_bootstrap_args logger.info(f"Executing PEP-HOST bootstrap command (host): {' '.join(pephost_bootstrap_command)}") run_command(pephost_bootstrap_command) logger.info("=> Void Linux PEP-HOST bootstrap COMPLETE.") else: logger.warning("Skipping PEP-HOST bootstrap due to no packages defined or error.") if build_peptarget: logger.info("=> Initiating the Void Linux system bootstrap for PEP-TARGET (host execution)...") peptarget_bootstrap_args = bootstrap.construct_xbps_install_args( "pep-target", paths, architecture, repositories, all_bootstrap_packages ) if peptarget_bootstrap_args: peptarget_bootstrap_command = ["/usr/bin/xbps-install"] + peptarget_bootstrap_args logger.info(f"Executing PEP-TARGET bootstrap command (host): {' '.join(peptarget_bootstrap_command)}") run_command(peptarget_bootstrap_command) logger.info("=> Void Linux PEP-TARGET bootstrap COMPLETE.") else: logger.warning("Skipping PEP-TARGET bootstrap due to no packages defined or error.") logger.info("=> Void Linux system bootstrap (ROOTFS, PEP-HOST, PEP-TARGET) COMPLETE.") logger.info("=> Copying network configuration files to ROOTFS...") copy_system_files.copy_network_config_files_to_chroot(paths['ROOTFS']) logger.info("=> Network configuration files copied to ROOTFS.") desktop_selecionado = desktop ficheiro_pacotes_desktop_yaml = f'builder/configs/desktops/{desktop_selecionado}.yaml' iso_build_config['desktop'] = desktop iso_build_config['kernel_type'] = kernel_type install_common_packages_rootfs_yaml( paths, paths["COMMON_PACKAGES_YAML"], architecture, host_arch, repositories ) install_desktop_environment( arch=architecture, desktop_environment_name=desktop, desktops_config=desktops_config, target_env='rootfs', paths=paths, host_arch=host_arch, repositories_data=repositories ) kernel_config = kernels_config.get('kernels', {}).get(kernel_type) install_kernel( arch=architecture, kernel_type=kernel_type, kernels_config=kernels_config, target_env='rootfs', host_arch=host_arch, repositories_data=repositories ) logger.info("=> Updating bootloader configuration (xbps-reconfigure -f linux) in ROOTFS...") kernel_package_name_for_reconfigure = None if kernels_config and 'kernels' in kernels_config and kernel_type in kernels_config['kernels']: kernel_info = kernels_config['kernels'][kernel_type] kernel_package_name_for_reconfigure = kernel_info.get('package_name') if kernel_package_name_for_reconfigure: kernel_package_name_for_reconfigure = kernel_package_name_for_reconfigure.split()[0] if not kernel_package_name_for_reconfigure: logger.warning("Failed to obtain kernel package name for reconfiguring. Using 'linux' as default.") kernel_package_name_for_reconfigure = 'linux' reconfigure_command_list = [ "sudo", "chroot", paths['ROOTFS'], "/usr/bin/xbps-reconfigure", '-f', kernel_package_name_for_reconfigure ] reconfigure_command_str = " ".join(reconfigure_command_list) logger.info(f"=> Executing XBPS_RECONFIGURE_CMD for bootloader [TARGETED TO: {paths['ROOTFS']}]: {reconfigure_command_str}") try: run_command(reconfigure_command_list) logger.info("=> Bootloader configuration updated successfully.") except Exception as e: logger.error(f"Error updating bootloader configuration: {e}") raise logger.info("=> Starting system reconfiguration in chroot...") reconfigure_system_in_chroot( paths['ROOTFS'], architecture, host_arch, repositories, ) logger.info("=> System reconfiguration in chroot COMPLETE.") copy_system_files.copy_dracut_files(paths['ROOTFS']) desktop_config_name = f"{desktop}.yaml" desktop_config_path = os.path.join("builder/configs/desktops", desktop_config_name) desktop_config = load_yaml_config(desktop_config_path, desktop_config_name) if desktop_config and 'desktop_environment' in desktop_config: customizations_path = desktop_config['desktop_environment'].get('customizations_path') if customizations_path: copy_custom_files(customizations_path, paths['ROOTFS'], desktop_config_path) else: logger.warning(f"Customizations path not defined for desktop: {desktop}") else: logger.error(f"Failed to load desktop configuration or 'desktop_environment' section not found for: {desktop}") logger.info("=> Starting system customization...") customize_system(architecture, desktop, kernel_type,iso_build_config) logger.info("=> System customization completed.") logger.info("=> Starting initramfs creation...") initrd_path, kernel_version_dict = create_initramfs(paths['ROOTFS'], paths['BOOT_DIR'], xbps_commands, iso_build_config) if boot_type == "bios" or boot_type == "hybrid": logger.info("=> Copying kernel image to BOOT directory...") copy_kernel_image( rootfs_path=paths['ROOTFS'], boot_path=paths['BOOT_DIR'], iso_build_config=iso_build_config, ) kernel_version = None if kernel_version_dict and kernel_version_dict.get('version_final_part'): kernel_version = kernel_version_dict['version_final_part'] if initrd_path: logger.info(f"=> Initramfs created successfully: {initrd_path}") else: logger.error("=> Failed to create initramfs. ISO build may fail.") return None else: logger.error("=> Failed to obtain kernel version during initramfs creation. ISO build may fail.") return None if kernel_version: iso_build_config['kernel'] = kernel_version logger.info(f"=> Kernel version extracted for bootloaders: {kernel_version}") else: logger.error("=> Failed to extract kernel version for bootloaders. Bootloader generation might fail.") kernel_version = "unknown" boot_title = f"Peppermint OS Void ({desktop.upper()} - Kernel {kernel_type})" logger.info("=> Starting bootloader generation...") isolinux_dir = bootloaders.create_isolinux_boot( architecture=architecture, kernel_version=kernel_version, keymap=iso_build_config.get('keymap', 'us'), locale=iso_build_config.get('locale', 'en_US.UTF-8'), boot_title=f"Peppermint OS Void ({desktop.upper()} - kernel {kernel_type})", boot_cmdline=iso_build_config['iso_config']['iso'].get('boot_cmdline', ''), Volume_ID=Volume_ID, paths=paths, host_arch=host_arch, repositories_data=repositories, ) if isolinux_dir: logger.info(f"=> Isolinux bootloader generated at: {isolinux_dir}") else: logger.error("=> Failed to generate Isolinux bootloader.") platform_cmdline = "" grub_efi_dir = bootloaders.create_grub_efi_boot( architecture=architecture, pep_target_path=paths["PEPTARGETDIR"], boot_dir=paths["BOOT_DIR"], grub_cfg_template_dir=paths["GRUB_EFI_CFG_TEMPLATE_DIR"], grub_modules=iso_build_config.get('grub_modules_efi', []), grub_themes=iso_build_config.get('grub_themes_efi', []), memtest_wanted=iso_build_config.get('memtest', False), splash_image=iso_build_config.get('splash_image'), platform_cmdline=platform_cmdline, kernel_version=kernel_version, paths=paths, logger=logger, boot_cmdline=iso_build_config['iso_config']['iso'].get('boot_cmdline', ''), boot_title=boot_title, keymap=iso_build_config.get('keymap', 'us'), locale=iso_build_config.get('locale', 'en_US.UTF-8'), config=iso_build_config, host_arch=host_arch, repositories_data=repositories, ) if grub_efi_dir: logger.info(f"=> GRUB EFI bootloader generated at: {grub_efi_dir}") else: logger.error("=> Failed to generate GRUB EFI bootloader.") logger.info("=> Bootloader generation complete.") logger.info("=> Unmounting filesystems.") try: unmount_essential_filesystems_in_chroot(paths, logger) logger.info("=> Filesystem unmounting complete.") except Exception as e: logger.error(f"=> Error unmounting filesystems: {e}") logger.info("=> Starting ROOTFS cleanup...") cleanup_rootfs(paths['ROOTFS'], iso_build_config) logger.info("=> ROOTFS cleanup complete.") logger.info("=> Starting post-installation cleanup of ROOTFS...") post_install_cleanup(paths['ROOTFS'], iso_build_config) logger.info("=> Post-installation cleanup of ROOTFS complete.") logger.info("=> Starting SquashFS image creation...") squashfs_output_file = os.path.join(paths['IMAGEDIR'], "LiveOS","squashfs.img") squashfs_filename = create_squashfs_image(paths['ROOTFS'], squashfs_output_file, paths['PEPHOSTDIR'], logger) if squashfs_filename: logger.info(f"=> SquashFS image created successfully: {squashfs_filename}") else: logger.error("=> Failed to create SquashFS image. ISO build may fail.") return None logger.info("=> Starting ISO image generation...") iso_final_path = iso_generator_main( arch=architecture, desktop=desktop, kernel_type=kernel_type, iso_build_dir=paths['ISO_OUTPUT_DIR'], iso_name_base=iso_name_base, iso_name=iso_filename_info, boot_type=boot_type, Volume_ID=Volume_ID, efi_boot_dir_name=efi_boot_dir_name, iso_label_prefix=iso_name_base, iso_publisher=iso_build_config.get('iso_publisher', 'Peppermint OS'), iso_application=iso_build_config.get('iso_application', 'Peppermint OS'), iso_volume_set_id=iso_build_config.get('iso_volume_set_id', 'Peppermint OS'), iso_volume_creation_date=iso_build_config.get('iso_volume_creation_date', datetime.date.today().strftime('%Y%m%d')), iso_volume_modification_date=iso_build_config.get('iso_volume_modification_date', datetime.date.today().strftime('%Y%m%d')), iso_volume_application_id=iso_build_config.get('iso_volume_application_id', 'Peppermint OS'), iso_system_id=iso_build_config.get('iso_system_id', 'Peppermint OS'), iso_build_config=iso_build_config, iso_profile=iso_profile, paths=paths, host_arch=host_arch, repositories_data=repositories, ) logger.debug(f"=> iso_generator_main returned: {iso_final_path}") if not iso_final_path or not os.path.exists(iso_final_path): logger.error("=> ISO image generation failed.") return None logger.info("=> Final cleanup tasks started.") remove_fusato_directory() logger.info("=> Final cleanup tasks finished.") final_iso_location = move_and_cleanup_iso(architecture, desktop, kernel_type, iso_name_base, iso_final_path) return final_iso_location def main(): """Main script to build Peppermint Void Linux ISOs.""" pre_parser = argparse.ArgumentParser(add_help=False) pre_parser.add_argument("architecture") pre_parser.add_argument("--debug", dest="debug_logging", action="store_true") pre_args, _ = pre_parser.parse_known_args() if pre_args.debug_logging: logging.getLogger().setLevel(logging.DEBUG) logger.info("=> DEBUG logging enabled via --debug argument.") else: logger.info("=> INFO logging enabled (default). Use --debug for more logs.") architecture = pre_args.architecture architectures_config_path = paths["ARCHITECTURES_CONFIG_YAML"] architectures_config_initial = load_yaml_config(architectures_config_path, "architectures_config.yaml") architecture_choices = list(architectures_config_initial.get('architectures', {}).keys()) config_file_name = f"iso_build_config_{architecture}.yaml" config_file_path = os.path.join(paths["ISO_BUILD_CONFIG_YAML"], config_file_name) all_configs = load_all_configurations(architecture, config_file_path=config_file_path) architectures_config = all_configs.get('architectures', {}) desktops_config = all_configs.get("desktops", {}) kernels_config = all_configs.get('kernels', {}) common_packages_config = all_configs.get('common_packages', {}) iso_build_config_data = all_configs.get('iso_config', {}).get('iso', {}) desktop_choices = list(desktops_config.get('desktops', {}).keys()) kernel_choices = list(kernels_config.get('kernels', {}).keys()) parser = argparse.ArgumentParser(description="Peppermint Void Linux ISO Builder") parser.add_argument("architecture", choices=architecture_choices, help=f"Target architecture ({', '.join(architecture_choices)})") parser.add_argument("--desktop", default="xfce", choices=desktop_choices, dest='desktop', help=f"Desktop environment ({', '.join(desktop_choices)})") parser.add_argument("--kernel", default="current", choices=kernel_choices, help=f"Kernel type to use in the ISO ({', '.join(kernel_choices)}). Default: current") parser.add_argument("-o", "--output-iso", dest="output_iso_path", default="builder/iso", help="Directory to save the resulting ISO image. Default: builder/iso") parser.add_argument("--debug", action="store_true", help="Enable detailed logging for debugging") parser.add_argument("--repos-file", dest="repos_yaml_file", type=str, default=paths["REPOS_YAML_FILE"], help=f"Path to the repositories YAML file. Default: {paths['REPOS_YAML_FILE']}") args = parser.parse_args() repos_yaml_file = args.repos_yaml_file iso_build_config_data['kernel_type'] = args.kernel iso_name_base = iso_build_config_data.get('iso_volume_label', 'PeppermintOS') iso_label = iso_name_base.upper().replace("-", "_") boot_type = iso_build_config_data.get('boot', 'hybrid') volume_id = iso_build_config_data.get('Volume_ID', iso_label) efi_boot_dir_name = iso_build_config_data.get('efi_boot_dir_name', 'efi') iso_filename = iso_builder_main( iso_build_dir=paths["IMAGEDIR"], build_stage_dir=paths["IMAGEDIR"], iso_profile="default_iso_profile", iso_build_config=all_configs, iso_name_base=iso_name_base, packages_list=[], full_iso_profile_config_dict=all_configs, architecture=args.architecture, desktop=args.desktop, kernel_type=args.kernel, output_iso_path=args.output_iso_path, repos_yaml_file=repos_yaml_file, architectures_config=architectures_config, desktops_config=desktops_config, common_packages_config=common_packages_config, kernels_config=kernels_config, Volume_ID=volume_id, efi_boot_dir_name=efi_boot_dir_name, boot_type=boot_type ) print(f"=> ISO created successfully: {iso_filename}") print(f"=> ISO saved at: {args.output_iso_path}") print(f"=> Build for {args.desktop}-{args.kernel}-{args.architecture} COMPLETE!") if __name__ == "__main__": main()