""" * SPDX-FileCopyrightText: 2023-2025 PeppermintOS Team * (peppermintosteam@proton.me) * SPDX-License-Identifier: GPL-3.0-or-later * * Set the infrastructure for bubbles to begin the ISO build * This copies needed config files to the binary and chroot * locations, based on the build base and architecture. """ import os import collections from pathlib import Path import shutil from python_modules.logger_modules import builder_logger as logger from python_modules.paths_modules import (HOME_FOLDER, WPCHROOT, BINARYPTH, BOOTSTRAP_FOLDER, FUSATOCONFIG, allowed_bases, symlinks, copyfolderfiles, copyfilesspecified) def copy_multimedia_files(sbase, sarch): """ Copy the multimedia to the ARCHIVES folder depending on the architecture. It will copy folders as needed. Args: sbase (str): Base architecture could be ("deb" or "dev") sarch (str): Architecture specifics could be ("32" or "64") """ logger.info("Copy multimedia Archive") src_archive = f'/multimedia/{sbase}{sarch}' des_archive = '/archives' source_path = HOME_FOLDER + src_archive dest_path = HOME_FOLDER + '/' + FUSATOCONFIG + des_archive logger.info("Source path: %s", source_path) logger.info("Destination path: %s", dest_path) try: shutil.copytree(source_path, dest_path, dirs_exist_ok=True) logger.info("Successfully copied files from %s to %s", source_path, dest_path) except Exception as _: logger.error("Error copying files: %s", str(_), exc_info=True) raise exit() logger.info("Archive copy completed") def copy_binary_folders(sbase, sarch): """ Copy all the needed folders to BINARY depending on the architecture. """ logger.info("Starting binary folder copy process") folder_pairs = [ (f'splash/{sbase}{sarch}/boot', 'boot'), (f'splash/{sbase}{sarch}/isolinux', 'isolinux'), (f'splash/{sbase}_live-theme', 'boot/grub'), (f'splash/{sbase}_splash', 'isolinux'), (f'splash/{sbase}_splash', 'boot/grub') ] for src, dest in folder_pairs: source_path = os.path.join(HOME_FOLDER, src) dest_path = os.path.join(HOME_FOLDER, BINARYPTH, dest) logger.info("Copying from %s to %s", source_path, dest_path) try: shutil.copytree(source_path, dest_path, dirs_exist_ok=True) logger.info("Successfully copied to %s", dest_path) except(ValueError, TypeError) as _: logger.error("Error copying to %s: %s", dest_path, str(_)) exit() logger.info("Binary folder copy process completed") def copy_fusato_configs(sbase, sarch): """ Copy all the needed files to the hooks folders depending on the architecture. """ logger.info("Starting Fusato config copy process") source = f'/hooks/normal/{sbase}{sarch}' destination = '/hooks/normal/' source_path = HOME_FOLDER + source + '/' dest_path = ( Path(HOME_FOLDER) / Path(FUSATOCONFIG) / destination.lstrip('/')) logger.info("Copying from %s to %s", source_path, dest_path) try: shutil.copytree(source_path, dest_path, dirs_exist_ok=True) logger.info("Successfully copied Fusato configs to %s", dest_path) except (ValueError, TypeError) as _: logger.error("Error copying Fusato configs to %s: %s", dest_path, str(_)) exit() logger.info("Fusato config copy process completed") def copy_server_config_files(base): """ Copies server initial config files to CHROOT depending on the base. """ if base not in allowed_bases: logger.warning("Base '%s' is not allowed. Skipping.", base) return logger.info("Copy Server Files for base: %s", base) server_path = f'/server/scripts/{base}/' file_paths = [ 'welcome.sh', 'configure_apache2.sh', 'configure_firewalld.sh', 'configure_hostname.sh', 'configure_mariadb.sh', 'configure_nginx.sh', 'configure_php_and_docker.sh', 'configure_postfix.sh', 'configure_postgresql.sh', 'configure_sqlite.sh', 'configure_ssh.sh', 'configure_static_ip.sh', 'create_user.sh', 'update_and_install.sh' ] destination = '/usr/local/bin' full_dest_path = ( Path(HOME_FOLDER) / Path(WPCHROOT) / destination.lstrip('/')) full_dest_path.mkdir(parents=True, exist_ok=True) logger.info("Ensured directory exists: %s", full_dest_path) for file in file_paths: src_path = Path(HOME_FOLDER) / server_path.lstrip('/') / file dest_path = full_dest_path / file logger.info("Copying %s to %s", src_path, dest_path) try: if src_path.exists(): if src_path.is_dir(): shutil.copytree(src_path, dest_path, dirs_exist_ok=True) else: shutil.copy2(src_path, dest_path) logger.info("Successfully copied to %s", dest_path) else: logger.error("Source path does not exist: %s", src_path) except (ValueError, TypeError) as _: logger.error( "Error copying %s to %s: %s", src_path, dest_path, _) exit() logger.info( "Completed copying server config files for base: %s", base) def copy_architecture_files(sbase, sarch, force_copy=False): """ Copy all the needed files and folders to CHROOT. Depending on the architecture, it will copy files and folders as needed. """ logger.debug("Starting copy process for architecture-specific files") sources_path = '/sources/' file_pairs = [ (f'{sources_path}{sbase}{sarch}/sources.list', f'/opt/pepconf/sources.list'), ('/id_files/pep_id', '/usr/share/peppermint/pep_id'), (f'/osrelease/{sbase}{sarch}/os-release', '/usr/lib/os-release'), (f'/osrelease/{sbase}{sarch}/os-release', '/opt/pepconf/os-release'), (f'/grub/{sbase}{sarch}/grub', '/etc/default/grub'), (f'/grub/{sbase}_themes', '/boot/grub/themes') ] for src, dest in file_pairs: src_path = Path(HOME_FOLDER) / src.lstrip('/') dest_path = ( Path(HOME_FOLDER) / Path(WPCHROOT) / dest.lstrip('/')) if dest_path.exists() and not force_copy: logger.info( "Skipping %s as it already exists at %s", src_path, dest_path) continue logger.info("Copying %s to %s", src_path, dest_path) try: if src_path.is_dir(): shutil.copytree(src_path, dest_path, symlinks=True, ignore_dangling_symlinks=True, dirs_exist_ok=True) else: dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src_path, dest_path) except (ValueError, TypeError) as _: logger.error("Error copying %s to %s: %s", src_path, dest_path, _) exit() else: logger.info("Successfully copied %s to %s", src_path, dest_path) logger.debug("Architecture-specific file copy process completed") def get_id_build_type(): """ This will get the type of build that is taking place and copy relevant files. """ logger.debug("Starting the build type identification process") source_folder = os.path.expanduser(f'{HOME_FOLDER}/build_type') dest_folder = os.path.join(os.path.expanduser('~'), 'bubbles', 'iso_configs', 'fusato', 'config', 'includes.chroot', 'opt', 'pepconf' ) logger.info("Source folder: %s", source_folder) logger.info("Destination folder: %s", dest_folder) try: os.makedirs(dest_folder, exist_ok=True) logger.info("Ensured destination folder exists: %s", dest_folder) except (ValueError, TypeError) as _: logger.error("Failed to create destination folder: %s", str(_)) exit() return files_copied = 0 for filename in os.listdir(source_folder): src_file = os.path.join(source_folder, filename) dest_file = os.path.join(dest_folder, filename) if os.path.isfile(src_file): try: shutil.copy(src_file, dest_file) logger.info('Successfully copied: %s to %s', src_file, dest_file) files_copied += 1 except (ValueError, TypeError) as _: logger.error("Failed to copy %s to %s: %s", src_file, dest_file, str(_)) exit() else: logger.warning("Skipped non-file item: %s", src_file) logger.info("Build type identification process completed. %d files " "copied.", files_copied) def ignore_missing_files(src, names): """ This will keep the build going even if files are missing from from the confiigs """ logger.info("Checking for missing files in directory: %s", src) missing_files = [] for name in names: full_path = os.path.join(src, name) if not os.path.exists(full_path): missing_files.append(name) logger.warning("File not found: %s", full_path) if missing_files: logger.info("Total missing files: %d", len(missing_files)) else: logger.info("No missing files found in %s", src) return missing_files def add_web_profile(): """ This will move the web profile depending on the build type. """ logger.debug("Starting to copy Web Profiles") path_to_bldtype_file = os.path.expanduser('~/start/bldtype') lw_src_path = '/browser_profiles/lw/' ff_src_path = '/browser_profiles/ff/' des_src_path = '/etc/skel/.local/share/pmostools' build_id_list = [ os.path.join(path_to_bldtype_file, f"{base}.{arch}{desktop}") for base in ['deb', 'dev'] for arch, desktop in [('64', 'xfc'), ('arm', 'xfc'), ('64', 'gfb'), ('64', 'opb') ] ] profile_copied = False for build_id in build_id_list: if os.path.exists(build_id): logger.info( "Found build id file: %s. Copying lw profile.", build_id) try: shutil.copytree( os.path.join(HOME_FOLDER, lw_src_path.lstrip('/')), os.path.join(HOME_FOLDER, WPCHROOT.lstrip('/'), des_src_path.lstrip('/')), dirs_exist_ok=True, ignore=ignore_missing_files ) logger.info("Successfully copied lw profile") profile_copied = True break except (ValueError, TypeError) as _: logger.error("Error copying lw profile: %s", str(_)) exit() if not profile_copied: logger.info("No matching build file found. Copying ff profile.") try: shutil.copytree( os.path.join(HOME_FOLDER, ff_src_path.lstrip('/')), os.path.join( HOME_FOLDER, WPCHROOT.lstrip('/'), des_src_path.lstrip('/')), dirs_exist_ok=True, ignore=ignore_missing_files ) logger.info("Successfully copied ff profile") except (ValueError, TypeError) as _: logger.error("Error copying ff profile: %s", str(_)) exit() logger.debug("Web profile copying process completed") def set_symlinks(): """ Set the symlinks that are used for all builds. """ logger.info("Starting to set symlinks for all builds") for target_path, source in symlinks.items(): full_target_path = os.path.join(HOME_FOLDER, WPCHROOT.lstrip('/'), target_path.lstrip('/')) logger.info("Creating symlink: %s -> %s", full_target_path, source) try: if os.path.islink(full_target_path): os.unlink(full_target_path) logger.info("Removed existing symlink: %s", full_target_path) os.makedirs(os.path.dirname(full_target_path), exist_ok=True) os.symlink(source, full_target_path) logger.info("Successfully created symlink: %s -> %s", full_target_path, source) except FileExistsError: logger.warning( "Symlink already exists and is not a symbolic link: %s", full_target_path) exit() except PermissionError: logger.error( "Permission denied when creating symlink: %s", full_target_path) exit() except (ValueError, TypeError) as _: logger.error("Error creating symlink %s -> %s: %s", full_target_path, source, str(_)) exit() logger.info("Finished setting symlinks for all builds") def icons_themes(): """ Copy icons and themes for all builds using paths defined in copyfolderfiles. """ logger.debug("Starting to copy icons and themes") copy_paths = [ ("SRC_DEBIAN_DARK", "DES_THEMES"), ("SRC_MANJARO_DARK", "DES_THEMES"), ("SRC_PEPPERMINT_DARK", "DES_THEMES"), ("SRC_DEBIAN", "DES_THEMES"), ("SRC_MANJARO", "DES_THEMES"), ("SRC_PEPPERMINT", "DES_THEMES"), ("SRC_TELA_BLUE_DARK", "DES_ICONS"), ("SRC_TELA_GREEN_DARK", "DES_ICONS"), ("SRC_TELA_RED_DARK", "DES_ICONS"), ("SRC_TELA_BLUE_BASE", "DES_ICONS"), ("SRC_TELA_GREEN_BASE", "DES_ICONS"), ("SRC_TELA_RED_BASE", "DES_ICONS"), ("SRC_TELA", "DES_ICONS"), ] for src_key, dest_key in copy_paths: if src_key in copyfolderfiles and dest_key in copyfolderfiles: src = copyfolderfiles[src_key].strip() dest = copyfolderfiles[dest_key].strip() source_path = Path(HOME_FOLDER) / src.lstrip('/') dest_path = Path(HOME_FOLDER) / WPCHROOT / dest.lstrip('/') folder_name = Path(src).name full_dest_path = dest_path / folder_name logger.info("Copying from %s to %s", source_path, full_dest_path) try: shutil.copytree(source_path, full_dest_path, dirs_exist_ok=True) logger.info("Successfully copied %s to %s", src_key, dest_key) except (ValueError, TypeError) as _: logger.error("Error copying %s to %s: %s", src_key, dest_key, str(_)) exit() else: logger.warning( "Skipping copy for %s to %s: Keys not found in" " copyfolderfiles", src_key, dest_key) logger.debug("Finished copying icons and themes") def shared_files(): """ Copy all specific files that are used for all builds. """ logger.debug("Starting to copy shared files") copy_pairs = [ ("SRC_ALIAS", "DES_ALIAS"), ("SRC_XDAILY", "DES_XDAILY"), ("SRC_PEPREPO", "DES_PEPREPO"), ("SRC_GPG_BINARY", "DES_GPG_BINARY"), ("SRC_GPG_CHROOT", "DES_GPG_CHROOY"), ("SRC_XDAILYGUI", "DES_XDAILYGUI"), ("SRC_SUGGESTED", "DES_SUGGESTED"), ("SRC_PFETCH", "DES_PFETCH"), ("SRC_WELCOME", "DES_WELCOME"), ("SRC_KUMO", "DES_KUMO"), ("SRC_LIGHTDM", "DES_LIGHTDM"), ("SRC_LIGHTDM_GREETER", "DES_LIGHTDM_GREETER"), ("SRC_PLY", "DES_PLY"), ] for src_key, des_key in copy_pairs: if src_key in copyfilesspecified and des_key in copyfilesspecified: source = copyfilesspecified[src_key].strip() destination = copyfilesspecified[des_key].strip() source_path = Path(HOME_FOLDER) / source.lstrip('/') dest_path = ( Path(HOME_FOLDER) / WPCHROOT / destination.lstrip('/') ) logger.info( "Attempting to copy: %s to %s", source_path, dest_path) try: dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_path, dest_path) logger.info("Successfully copied: %s to %s", source_path, dest_path) if des_key in ["DES_GPG_BINARY", "DES_GPG_CHROOT"]: os.chmod(dest_path, 0o644) logger.info("Set permissions to 644 for %s", dest_path) except FileNotFoundError: logger.error("Source file not found: %s", source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", dest_path) exit() except (ValueError, TypeError) as _: logger.error("Error copying %s to %s: %s", source_path, dest_path, str(_)) exit() else: logger.warning( "Skipping copy for %s to %s: Keys not found in" " copyfilesspecified", src_key, des_key) logger.info("Finished copying shared files") def setup_hooks_minis(): """ Copy all hooks files that are used for mini builds. """ logger.debug("Starting to copy hooks files") copy_pairs = [ ("SRC_HOOKS_MINI_NORMAL", "DES_HOOKS_MINI_NORMAL") ] for src_key, des_key in copy_pairs: if src_key in copyfilesspecified and des_key in copyfilesspecified: source = copyfilesspecified[src_key].strip() destination = copyfilesspecified[des_key].strip() source_path = Path(HOME_FOLDER) / source.lstrip('/') dest_path = ( Path(HOME_FOLDER) / destination.lstrip('/') ) logger.info( "Attempting to copy: %s to %s", source_path, dest_path) try: dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_path, dest_path) logger.info("Successfully copied: %s to %s", source_path, dest_path) except FileNotFoundError: logger.error("Source file not found: %s", source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", dest_path) exit() except (ValueError, TypeError) as _: logger.error("Error copying %s to %s: %s", source_path, dest_path, str(_)) exit() else: logger.warning( "Skipping copy for %s to %s: Keys not found in" " copyfilesspecified", src_key, des_key) logger.info("Finished copying Hook files") def setup_hooks(): """ Copy all hooks files that are used for all builds minus the mini. """ logger.debug("Starting to copy hooks files") copy_pairs = [ ("SRC_HOOKS_LIVE", "DES_HOOKS_LIVE"), ("SRC_HOOKS_NORMAL", "DES_HOOKS_NORMAL") ] for src_key, des_key in copy_pairs: if src_key in copyfilesspecified and des_key in copyfilesspecified: source = copyfilesspecified[src_key].strip() destination = copyfilesspecified[des_key].strip() source_path = Path(HOME_FOLDER) / source.lstrip('/') dest_path = ( Path(HOME_FOLDER) / destination.lstrip('/') ) logger.info( "Attempting to copy: %s to %s", source_path, dest_path) try: dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_path, dest_path) logger.info("Successfully copied: %s to %s", source_path, dest_path) except FileNotFoundError: logger.error("Source file not found: %s", source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", dest_path) exit() except (ValueError, TypeError) as _: logger.error("Error copying %s to %s: %s", source_path, dest_path, str(_)) exit() else: logger.warning( "Skipping copy for %s to %s: Keys not found in" " copyfilesspecified", src_key, des_key) logger.info("Finished copying Hook files") def shared_server_files(): """ This will copy all specific files that are used for the server builds. """ logger.info("Starting to copy shared server files") src_paths = ('/server/firewall/public.xml', ) des_paths = ('/etc/firewalld/zones', ) src_q = collections.deque(src_paths) des_q = collections.deque(des_paths) size_q = len(src_q) logger.info("Preparing to copy %d files", size_q) for size_length in range(size_q): source = src_q.popleft() des = des_q.popleft() full_source_path = HOME_FOLDER + source full_dest_path = HOME_FOLDER + WPCHROOT + des logger.info("Copying file %d of %d", size_length + 1, size_q) logger.info("Source: %s", full_source_path) logger.info("Destination: %s", full_dest_path) try: shutil.copy(full_source_path, full_dest_path) logger.info( "Successfully copied file to %s", full_dest_path) except FileNotFoundError: logger.error( "Source file not found: %s", full_source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", full_dest_path) exit() except IOError as _: logger.error( "I/O error occurred while copying %s to %s: %s", full_source_path, full_dest_path, str(_)) exit() logger.info("Completed copying shared server files") def boostrap_shared(): """ Copy specific folders in the bootstrap location """ logger.debug("Starting to copy shared bootstrap files") src_paths = ('issue',) des_paths = ('etc',) src_q = collections.deque(src_paths) des_q = collections.deque(des_paths) size_q = len(src_q) logger.info("Preparing to copy %d directories", size_q) for size_length in range(size_q): source = src_q.popleft() des = des_q.popleft() full_source_path = os.path.join(HOME_FOLDER, source) full_dest_path = os.path.join(HOME_FOLDER, BOOTSTRAP_FOLDER, des) logger.info("Copying directory %d of %d", size_length + 1, size_q) logger.info("Source: %s", full_source_path) logger.info("Destination: %s", full_dest_path) try: shutil.copytree( full_source_path, full_dest_path, dirs_exist_ok=True) logger.info("Successfully copied directory to %s", full_dest_path) except FileNotFoundError: logger.error("Source directory not found: %s", full_source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", full_dest_path) exit() except shutil.Error as _: logger.error("Error occurred while copying %s to %s: %s", full_source_path, full_dest_path, str(_)) exit() logger.debug("Completed copying shared bootstrap files") def shared_folders(): """ Copy shared folders that are common amongst all the builds. """ logger.debug("Starting to copy shared folders") copy_pairs = [ ("SRC_PLY_LINES", "DES_PLY_LINES"), ("SRC_APPS", "DES_APPS"), ("SRC_FONT", "DES_FONT"), ("SRC_ISSUE", "DES_ISSUE"), ("SRC_BUILD_TYPE", "DES_BUILD_TYPE"), ("SRC_ISSUE", "DES_ISSUES_OPT"), ("SRC_POLKIT", "DES_POLKIT"), ("SRC_USER_CONFIGS", "DES_USER_CONFIGS"), ("SRC_PIXMAPS", "DES_PIXMAPS"), ("SRC_WALLPAPER", "DES_WALLPAPER"), ("SRC_MENU", "DES_MENU"), ("SRC_FACE", "DES_FACE"), ("SRC_PMOSTOOLS", "DES_PMOSTOOLS"), ("SRC_AUTO_START", "DES_AUTO_START"), ("SRC_PYLIBS", "DES_PYLIBS"), ] for src_key, des_key in copy_pairs: if src_key in copyfolderfiles and des_key in copyfolderfiles: source = copyfolderfiles[src_key].strip() destination = copyfolderfiles[des_key].strip() source_path = Path(HOME_FOLDER) / source.lstrip('/') dest_path = ( Path(HOME_FOLDER) / WPCHROOT / destination.lstrip('/') ) logger.info("Attempting to copy folder: %s to %s", source_path, dest_path) try: shutil.copytree(source_path, dest_path, dirs_exist_ok=True) logger.info("Successfully copied folder: %s to %s", source_path, dest_path) except FileNotFoundError: logger.error("Source folder not found: %s", source_path) exit() except PermissionError: logger.error( "Permission denied when copying to: %s", dest_path) exit() except (ValueError, TypeError) as _: logger.error("Error copying folder %s to %s: %s", source_path, dest_path, str(_)) exit() else: logger.warning( "Skipping copy for %s to %s: Keys not found in " "copyfolderfiles", src_key, des_key) logger.info("Finished copying shared folders") get_id_build_type() def loaded_folders(): """ This function will get the files that are used by the loaded builds. """ logger.debug("Starting to copy loaded folders") src_paths = ('/loaded/application', '/loaded/wallpaper') des_paths = ('/usr/share/applications', '/usr/share/backgrounds') src_q = collections.deque(src_paths) des_q = collections.deque(des_paths) size_q = len(des_q) logger.info("Preparing to copy %d directories", size_q) for size_length in range(size_q): source = src_q.popleft() des = des_q.popleft() full_source_path = HOME_FOLDER + source full_dest_path = HOME_FOLDER + '/' + WPCHROOT + des logger.info("Copying directory %d of %d", size_length + 1, size_q) logger.info("Source: %s", full_source_path) logger.info("Destination: %s", full_dest_path) try: shutil.copytree(full_source_path, full_dest_path, dirs_exist_ok=True) logger.info( "Successfully copied directory from %s to %s", full_source_path, full_dest_path) except FileNotFoundError: logger.error("Source directory not found: %s", full_source_path) exit() except PermissionError: logger.error("Permission denied when copying to: %s", full_dest_path) exit() except shutil.Error as _: logger.error("Error occurred while copying %s to %s: %s", full_source_path, full_dest_path, str(_)) exit() logger.debug("Completed copying loaded folders")