builder_peppermint_void/builder/iso_builder.py
2025-04-28 00:33:43 +00:00

500 lines
22 KiB
Python

# 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
)
desktop_config = load_yaml_config(ficheiro_pacotes_desktop_yaml, desktop_selecionado + ".yaml")
install_desktop_environment(
arch=architecture,
desktop_environment_name=desktop,
desktops_config=desktops_config,
target_env='rootfs',
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()