# 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.bootstrap.yaml_repo_loader import generate_bootstrap_commands 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 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.bootstrap.cache import get_or_download_package, sync_repositories from builder.core.final_cleanup import remove_fusato_directory from builder.core.move_iso import move_and_cleanup_iso 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 ): """Main function to execute the entire ISO build process.""" create_fusato_structure() build_rootfs = True build_peptarget = True build_pephost = True rootfs_path = paths["ROOTFS"] boot_path = paths["BOOT_DIR"] pep_target_path = paths["PEPTARGETDIR"] pephost_path = paths["PEPHOSTDIR"] 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"=> Cache directory for the target architecture: {os.environ.get('XBPS_CACHEDIR')}") logger.info(f"=> Cache directory for the host architecture: {os.environ.get('XBPS_HOST_CACHEDIR')}") logger.info("=> Copying Void keys to ROOTFS...") 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 to ROOTFS, PEPHOSTDIR, and PEPTARGETDIR.") 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 = f"{iso_name_base}-{architecture}-{kernel_type}-{version}.iso" iso_build_dir = paths["ISO_OUTPUT_DIR"] iso_path = os.path.join(iso_build_dir, iso_filename) logger.info(f"=> ISO filename will be: {iso_path}") paths['GRUB_EFI_DIR'] = os.path.join(paths['ISO_BASE_DIR'], "grub_efi") paths['ISOLINUX_DIR'] = os.path.join(paths['ISO_BASE_DIR'], "isolinux") sync_repositories(architecture, repos_yaml_file) repo_data = yaml_repo_loader.load_repositories_from_yaml(repos_yaml_file) repositories = repo_data['repositories'] bootstrap_packages = repo_data['bootstrap_packages'] if build_rootfs: rootfs_path = paths["ROOTFS"] logger.info("=> Executing bootstrap command for ROOTFS...") bootstrap_commands = generate_bootstrap_commands(paths, architecture, repositories, bootstrap_packages) for category, packages in bootstrap_packages.items(): if isinstance(packages, list): for package in packages: get_or_download_package(package, architecture, repos_yaml_file) elif isinstance(packages, str): get_or_download_package(packages, architecture, repos_yaml_file) for command_list in bootstrap_commands: target_path = command_list[-2] command_str = ' '.join(command_list) logger.info(f"=> Executing command [TARGETED TO: {target_path}]: {command_str}") run_command(command_list) logger.info("=> Void Linux ROOTFS bootstrap COMPLETE.") if build_pephost: pephost_path = paths["PEPHOSTDIR"] logger.info("=> Executing bootstrap command for PEP-HOST...") bootstrap_commands = generate_bootstrap_commands(paths, architecture, repositories, bootstrap_packages) for category, packages in bootstrap_packages.items(): if isinstance(packages, list): for package in packages: get_or_download_package(package, architecture, repos_yaml_file) elif isinstance(packages, str): get_or_download_package(packages, architecture, repos_yaml_file) for command_list in bootstrap_commands: target_path = command_list[-2] command_str = ' '.join(command_list) logger.info(f"=> Executing command [TARGETED TO: {target_path}]: {command_str}") run_command(command_list) logger.info("=> Void Linux PEP-HOST bootstrap COMPLETE.") if build_peptarget: peptarget_path = paths["PEPTARGETDIR"] logger.info("=> Executing bootstrap command for PEP-TARGET...") bootstrap_commands = generate_bootstrap_commands(paths, architecture, repositories, bootstrap_packages) for category, packages in bootstrap_packages.items(): if isinstance(packages, list): for package in packages: get_or_download_package(package, architecture, repos_yaml_file) elif isinstance(packages, str): get_or_download_package(packages, architecture, repos_yaml_file) for command_list in bootstrap_commands: target_path = command_list[-2] command_str = ' '.join(command_list) logger.info(f"=> Executing command [TARGETED TO: {target_path}]: {command_str}") run_command(command_list) logger.info("=> Void Linux PEP-TARGET bootstrap COMPLETE.") logger.info("=> Void Linux system bootstrap (ROOTFS, PEP-HOST, PEP-TARGET) COMPLETE.") mount_essential_filesystems_in_chroot(paths, logger) logger.info("=> Essential filesystems mounted in the chroot.") 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 common_packages = load_yaml_config('builder/configs/packages/common_packages.yaml', 'common_packages.yaml') if common_packages: for category, packages in common_packages.items(): if isinstance(packages, list): for package in packages: get_or_download_package(package, architecture, repos_yaml_file) elif isinstance(packages, str): get_or_download_package(packages, architecture, repos_yaml_file) install_common_packages_rootfs_yaml(paths["ROOTFS"], paths["COMMON_PACKAGES_YAML"], architecture) desktop_config = load_yaml_config(ficheiro_pacotes_desktop_yaml, desktop_selecionado + ".yaml") if desktop_config and 'desktop_environment' in desktop_config: desktop_packages = desktop_config['desktop_environment'].get('desktop_packages', []) login_manager_packages = desktop_config['desktop_environment'].get('login_manager_packages', []) default_packages = desktop_config['desktop_environment'].get('default_packages', []) for package in desktop_packages: get_or_download_package(package, architecture, repos_yaml_file) for package in login_manager_packages: get_or_download_package(package, architecture, repos_yaml_file) for package in default_packages: get_or_download_package(package, architecture, repos_yaml_file) install_desktop_environment( arch=architecture, desktop_environment_name=desktop, desktops_config=desktops_config, target_env='rootfs' ) kernel_config = kernels_config.get('kernels', {}).get(kernel_type) if kernel_config and 'package_name' in kernel_config: kernel_package = kernel_config['package_name'].split()[0] get_or_download_package(kernel_package, architecture, repos_yaml_file) install_kernel( arch=architecture, kernel_type=kernel_type, kernels_config=kernels_config, target_env='rootfs' ) 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 = [ "chroot", paths['ROOTFS'], xbps_commands["XBPS_RECONFIGURE_CMD"], '-f', kernel_package_name_for_reconfigure ] reconfigure_command_str = " ".join(reconfigure_command) logger.info(f"=> Executing XBPS_RECONFIGURE_CMD for bootloader [TARGETED TO: {paths['ROOTFS']}]: {reconfigure_command_str}") try: run_command(reconfigure_command) 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, repositories, xbps_cachedir_env ) logger.info("=> System reconfiguration in chroot COMPLETE.") copy_system_files.copy_dracut_files(rootfs_path) 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') rootfs_path = paths['ROOTFS'] if customizations_path: copy_custom_files(customizations_path, rootfs_path, 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(rootfs_path, boot_path, 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=rootfs_path, boot_path=boot_path, 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, ) 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=pep_target_path, 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 ) 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_path = None iso_path = iso_generator_main( arch=architecture, desktop=desktop, kernel_type=kernel_type, iso_build_dir=iso_build_dir, iso_name_base=iso_name_base, iso_name=iso_filename, 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 ) logger.debug(f"=> iso_generator_main returned: {iso_path}") remove_fusato_directory() logger.info("=> Final cleanup tasks finished.") move_and_cleanup_iso(architecture, desktop, kernel_type, iso_name_base) logger.info("=> ISO moved and old files removed from the server.") return iso_filename def main(): """Main script to build Peppermint Void Linux ISOs.""" # Etapa 1: Parser preliminar (somente arquitetura e debug) 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 # Etapa 2: Carrega as configurações com base na arquitetura 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()) # Etapa 3: Parser final com choices dinâmicos 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() # Etapa 4: Define variáveis com base nos argumentos e config 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') # Etapa 5: Execução do processo de build 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 ) # Etapa 6: Saída final 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()