builder_peppermint_void/builder/iso_builder.py
2025-04-29 12:27:36 +00:00

517 lines
23 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
)
kernels_config = load_yaml_config(paths['KERNELS_CONFIG_YAML'], 'kernels.yaml')
install_kernel(
arch=architecture,
kernel_type=kernel_type,
kernels_config=kernels_config,
target_env='rootfs',
paths=paths,
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]
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]
logger.info(f"=> DEBUG: kernel_type for reconfigure: '{kernel_type}'") # Debug line
logger.info(f"=> DEBUG: kernel_info for reconfigure: {kernel_info}")  # Debug line
package_name_string_from_yaml = kernel_info.get('package_name') # Get the string
logger.info(f"=> DEBUG: package_name_string_from_yaml: '{package_name_string_from_yaml}'") # Debug line
if package_name_string_from_yaml:
# Only split if the string is not None or empty
kernel_package_name_for_reconfigure = package_name_string_from_yaml.split()[0]
logger.info(f"=> DEBUG: kernel_package_name_for_reconfigure after split: '{kernel_package_name_for_reconfigure}'") # Debug line
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(
rootfs_path=paths['ROOTFS'],
target_architecture=architecture,
repositories_data=repositories,
paths=paths,
locale="en_US.UTF-8"
)
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()