From 798ea29398af0dcdfe9829654a7311695601cf09 Mon Sep 17 00:00:00 2001 From: David P Date: Wed, 18 Nov 2020 21:15:17 -0300 Subject: update Makefile, sync with archiso Imported changes: 3160db0 (HEAD -> master, origin/master, origin/HEAD) Fix evaluation bugs in mkarchiso 96ac5e2 mkarchiso: add xorrisofs options from boot mode specific functions instead of hardcoding them in _build_iso 4dfb473 mkarchiso: validate profile right after reading it 57d510f mkarchiso: general cleanup and simplification Non-imported changes: 6c39713 Use official archlinux Docker image Parabola changes: * remove talkingparabola from Makefile * adapt imported changes to work with dual ISOs Signed-off-by: David P --- Makefile | 7 +- README.profile.rst | 10 +- parabolaiso/mkparabolaiso | 697 +++++++++++++++++++++++++++++----------------- 3 files changed, 456 insertions(+), 258 deletions(-) diff --git a/Makefile b/Makefile index 3d8dc68..90e80c3 100644 --- a/Makefile +++ b/Makefile @@ -40,12 +40,7 @@ lint: configs/lxde-openrc/airootfs/etc/local.d/etc-pacman.d-gnupg.start \ configs/lxde-openrc/airootfs/etc/NetworkManager/dispatcher.d/reflector \ configs/lxde-openrc/airootfs/usr/local/bin/choose-mirror \ - configs/talkingparabola/airootfs/root/.automated_script.sh \ - configs/talkingparabola/airootfs/usr/bin/livecd-alsa-unmuter \ - configs/talkingparabola/airootfs/usr/bin/pick-a-card \ - configs/talkingparabola/airootfs/usr/bin/talk-to-me \ - configs/talkingparabola/airootfs/usr/local/bin/choose-mirror \ - configs/releng/airootfs/usr/local/bin/livecd-sound + configs/releng/airootfs/usr/local/bin/livecd-sound shellcheck -s dash $(HOOKS_FILES) $(SCRIPT_FILES) install: install-program install-examples install-doc diff --git a/README.profile.rst b/README.profile.rst index 236c69f..a34d557 100644 --- a/README.profile.rst +++ b/README.profile.rst @@ -27,7 +27,7 @@ The image file is constructed from some of the variables in **profiledef.sh**: ` (e.g. `parabola-202010-x86_64.iso`). * `iso_name`: The first part of the name of the resulting image (defaults to `mkparabolaiso`) -* `iso_label`: The ISO's volume label (defaults to `mkparabolaiso`) +* `iso_label`: The ISO's volume label (defaults to `MKPARABOLAISO`) * `iso_publisher`: A free-form string that states the publisher of the resulting image (defaults to `mkparabolaiso`) * `iso_application`: A free-form string that states the application (i.e. its use-case) of the resulting image (defaults to `mkparabolaiso iso`) @@ -36,16 +36,24 @@ The image file is constructed from some of the variables in **profiledef.sh**: ` directory on the resulting image into which all files will be installed (defaults to `mkparabolaiso`) * `bootmodes`: A list of strings, that state the supported boot modes of the resulting image. Only the following are understood: + - `bios.syslinux.mbr`: Syslinux for x86 BIOS booting from a disk - `bios.syslinux.eltorito`: Syslinux for x86 BIOS booting from an optical disc - `uefi-x64.systemd-boot.esp`: Systemd-boot for x86_64 UEFI booting from a disk - `uefi-x64.systemd-boot.eltorito`: Systemd-boot for x86_64 UEFI booting from an optical disc - `uefi-x64.refind.esp`: rEFInd for x86_64 UEFI booting from a disk - `uefi-x64.refind.eltorito`: rEFInd for x86_64 UEFI booting from an optical disc + Note that BIOS El Torito boot mode must always be listed before UEFI El Torito boot mode. * `arch`: The architecture (e.g. `x86_64`) to build the image for. This is also used to resolve the name of the packages file (e.g. `packages.x86_64`) * `pacman_conf`: The `pacman.conf` to use to install packages to the work directory when creating the image (defaults to the host's `/etc/pacman.conf`) +* `airootfs_image_type`: The image type to create. The following options are understood (defaults to `squashfs`): + + - `squashfs`: Create a squashfs image directly from the airootfs work directory + - `ext4+squashfs`: Create an ext4 partition, copy the airootfs work directory to it and create a squashfs image from it +* `airootfs_image_tool_options`: An array of options to pass to the tool to create the airootfs image. Currently only + `mksquashfs` is supported - see `mksquashfs --help` for all possible options (defaults to `('-comp' 'xz')`). packages.arch ============= diff --git a/parabolaiso/mkparabolaiso b/parabolaiso/mkparabolaiso index df8f184..b88ea34 100755 --- a/parabolaiso/mkparabolaiso +++ b/parabolaiso/mkparabolaiso @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # SPDX-License-Identifier: GPL-3.0-or-later @@ -12,7 +12,6 @@ export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-"$(date +%s)"}" # mkparabolaiso defaults app_name="${0##*/}" pkg_list=() -run_cmd="" quiet="y" work_dir="work" out_dir="out" @@ -66,15 +65,6 @@ _msg_error() { fi } -_chroot_init() { - install -d -m 0755 -o 0 -g 0 -- "${airootfs_dir}" - _pacman base syslinux -} - -_chroot_run() { - eval -- arch-chroot "${airootfs_dir}" "${run_cmd}" -} - _mount_airootfs() { trap "_umount_airootfs" EXIT HUP INT TERM install -d -m 0755 -- "${work_dir}/mnt/airootfs" @@ -123,8 +113,7 @@ ENDUSAGETEXT exit "${1}" } -# Shows configuration according to command mode. -# $1: build_profile | init | install | run | prepare | iso +# Shows configuration options. _show_config() { local build_date build_date="$(date --utc --iso-8601=seconds -d "@${SOURCE_DATE_EPOCH}")" @@ -147,50 +136,24 @@ _show_config() { _msg_info " Packages (x86_64): ${pkg_list_x86_64[*]:-None}" _msg_info " Packages (i686): ${pkg_list_i686[*]:-None}" fi - [[ "${quiet}" == "y" ]] || printf '\n' -} - -# Install desired packages to airootfs -_pacman() { - _msg_info "Installing packages to '${airootfs_dir}/'..." - - if [[ "${quiet}" = "y" ]]; then - pacstrap -C "${work_dir}/pacman_${arch}.conf" -c -G -M -- "${airootfs_dir}" "$@" &> /dev/null - else - pacstrap -C "${work_dir}/pacman_${arch}.conf" -c -G -M -- "${airootfs_dir}" "$@" - fi - - _msg_info "Done! Packages installed successfully." } # Cleanup airootfs -_cleanup() { +_cleanup_airootfs() { _msg_info "Cleaning up what we can on ${arch} airootfs..." # Delete all files in /boot - if [[ -d "${airootfs_dir}/boot" ]]; then - find "${airootfs_dir}/boot" -mindepth 1 -delete - fi + [[ -d "${airootfs_dir}/boot" ]] && find "${airootfs_dir}/boot" -mindepth 1 -delete # Delete pacman database sync cache files (*.tar.gz) - if [[ -d "${airootfs_dir}/var/lib/pacman" ]]; then - find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete - fi + [[ -d "${airootfs_dir}/var/lib/pacman" ]] && find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete # Delete pacman database sync cache - if [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]]; then - find "${airootfs_dir}/var/lib/pacman/sync" -delete - fi + [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]] && find "${airootfs_dir}/var/lib/pacman/sync" -delete # Delete pacman package cache - if [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]]; then - find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete - fi + [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]] && find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete # Delete all log files, keeps empty dirs. - if [[ -d "${airootfs_dir}/var/log" ]]; then - find "${airootfs_dir}/var/log" -type f -delete - fi + [[ -d "${airootfs_dir}/var/log" ]] && find "${airootfs_dir}/var/log" -type f -delete # Delete all temporary files and dirs - if [[ -d "${airootfs_dir}/var/tmp" ]]; then - find "${airootfs_dir}/var/tmp" -mindepth 1 -delete - fi + [[ -d "${airootfs_dir}/var/tmp" ]] && find "${airootfs_dir}/var/tmp" -mindepth 1 -delete # Delete package pacman related files. find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete # Create an empty /etc/machine-id @@ -199,28 +162,18 @@ _cleanup() { _msg_info "Done!" } -_mkairootfs_create_image() { - if (( $# < 1 )); then - _msg_error "Function '${FUNCNAME[0]}' requires at least one argument" 1 - fi - - image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" - if [[ "${airootfs_image_type}" =~ .*squashfs ]] ; then - if [[ "${quiet}" == "y" ]]; then - mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" -no-progress > /dev/null - else - mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" - fi +_run_mksquashfs() { + local image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" + if [[ "${quiet}" == "y" ]]; then + mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" -no-progress > /dev/null else - _msg_error "Unsupported image type: '${airootfs_image_type}'" 1 + mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" fi } # Makes a ext4 filesystem inside a SquashFS from a source directory. -_mkairootfs_img() { - if [[ ! -e "${airootfs_dir}" ]]; then - _msg_error "The path '${airootfs_dir}' does not exist" 1 - fi +_mkairootfs_ext4+squashfs() { + [[ -e "${airootfs_dir}" ]] || _msg_error "The path '${airootfs_dir}' does not exist" 1 _msg_info "Creating ext4 image of 32 GiB..." if [[ "${quiet}" == "y" ]]; then @@ -238,20 +191,18 @@ _mkairootfs_img() { _umount_airootfs install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating SquashFS image, this may take some time..." - _mkairootfs_create_image "${airootfs_dir}.img" + _run_mksquashfs "${airootfs_dir}.img" _msg_info "Done!" rm -- "${airootfs_dir}.img" } # Makes a SquashFS filesystem from a source directory. -_mkairootfs_sfs() { - if [[ ! -e "${airootfs_dir}" ]]; then - _msg_error "The path '${airootfs_dir}' does not exist" 1 - fi +_mkairootfs_squashfs() { + [[ -e "${airootfs_dir}" ]] || _msg_error "The path '${airootfs_dir}' does not exist" 1 install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating ${arch} SquashFS image, this may take some time..." - _mkairootfs_create_image "${airootfs_dir}" + _run_mksquashfs "${airootfs_dir}" _msg_info "Done!" } @@ -356,19 +307,30 @@ _make_custom_airootfs() { fi } -# Packages (airootfs) +# Install desired packages to airootfs _make_packages() { + _msg_info "Installing packages to '${airootfs_dir}/'..." + + local pkg_list_arch + eval "pkg_list_arch=(\${pkg_list_${arch}[@]})" + if [[ -n "${gpg_key}" ]]; then exec {PARABOLAISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg" export PARABOLAISO_GNUPG_FD fi - local pkg_list_arch - eval "pkg_list_arch=(\${pkg_list_${arch}[@]})" - _pacman "${pkg_list[@]}" "${pkg_list_arch[@]}" + + if [[ "${quiet}" = "y" ]]; then + pacstrap -C "${work_dir}/pacman_${arch}.conf" -c -G -M -- "${airootfs_dir}" "${pkg_list[@]}" "${pkg_list_arch[@]}" &> /dev/null + else + pacstrap -C "${work_dir}/pacman_${arch}.conf" -c -G -M -- "${airootfs_dir}" "${pkg_list[@]}" "${pkg_list_arch[@]}" + fi + if [[ -n "${gpg_key}" ]]; then exec {PARABOLAISO_GNUPG_FD}<&- unset PARABOLAISO_GNUPG_FD fi + + _msg_info "Done! Packages installed successfully." } # Customize installation (airootfs) @@ -392,8 +354,7 @@ _make_customize_airootfs() { if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then _msg_info "Running customize_airootfs.sh in '${airootfs_dir}' chroot..." _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future parabolaiso version." - local run_cmd="/root/customize_airootfs.sh" - _chroot_run + eval -- arch-chroot "${airootfs_dir}" "/root/customize_airootfs.sh" rm -- "${airootfs_dir}/root/customize_airootfs.sh" _msg_info "Done! customize_airootfs.sh run successfully." fi @@ -403,16 +364,12 @@ _make_customize_airootfs() { _make_bootmodes() { local bootmode for bootmode in "${bootmodes[@]}"; do - if typeset -f "_make_boot_${bootmode}" &> /dev/null; then - _run_once "_make_boot_${bootmode}" - else - _msg_error "${bootmode} is not a valid boot mode" 1 - fi + _run_once "_make_bootmode_${bootmode}" done } # Prepare kernel/initramfs ${install_dir}/boot/ -_make_boot_on_iso() { +_make_boot_on_iso9660() { _msg_info "Preparing ${arch} kernel and initramfs for the ISO 9660 file system..." install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}" install -m 0644 -- "${airootfs_dir}/boot/initramfs-"*".img" "${isofs_dir}/${install_dir}/boot/${arch}/" @@ -421,7 +378,7 @@ _make_boot_on_iso() { } # Prepare /${install_dir}/boot/syslinux -_make_boot_bios.syslinux.mbr() { +_make_bootmode_bios.syslinux.mbr() { _msg_info "Setting up SYSLINUX for BIOS booting from a disk..." install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux" for _cfg in "${profile}/syslinux/"*.cfg; do @@ -437,7 +394,7 @@ _make_boot_bios.syslinux.mbr() { install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/${install_dir}/boot/syslinux/" install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/${install_dir}/boot/syslinux/" - _run_dual '_run_once _make_boot_on_iso' + _run_dual '_run_once _make_boot_on_iso9660' if [[ -e "${isofs_dir}/${install_dir}/boot/syslinux/hdt.c32" ]]; then install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux/hdt" @@ -461,7 +418,7 @@ _make_boot_bios.syslinux.mbr() { } # Prepare /isolinux -_make_boot_bios.syslinux.eltorito() { +_make_bootmode_bios.syslinux.eltorito() { _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..." install -d -m 0755 -- "${isofs_dir}/isolinux" for _cfg in "${profile}/isolinux/"*".cfg"; do @@ -475,13 +432,13 @@ _make_boot_bios.syslinux.eltorito() { install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/ldlinux.c32" "${isofs_dir}/isolinux/" # isolinux.cfg loads syslinux.cfg - _run_once _make_boot_bios.syslinux.mbr + _run_once _make_bootmode_bios.syslinux.mbr _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully." } # Prepare /EFI on ISO-9660 -_make_efi() { +_make_efi_dir_on_iso9660() { _msg_info "Preparing an /EFI directory for the ISO 9660 file system..." install -d -m 0755 -- "${isofs_dir}/EFI/BOOT" install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ @@ -504,7 +461,7 @@ _make_efi() { fi } -_make_refind_efi() { +_make_refind_efi_dir_on_iso9660() { _msg_info "Preparing an /EFI directory for the ISO 9660 file system..." install -d -m 0755 -- "${isofs_dir}/EFI/BOOT/entries" install -m 0644 -- "${airootfs_dir}/usr/share/refind/refind_x64.efi" \ @@ -537,7 +494,7 @@ _make_boot_on_fat() { } # Prepare efiboot.img::/EFI for EFI boot mode -_make_boot_uefi-x64.systemd-boot.esp() { +_make_bootmode_uefi-x64.systemd-boot.esp() { local efiboot_imgsize="0" _msg_info "Setting up systemd-boot for UEFI booting..." @@ -584,7 +541,7 @@ _make_boot_uefi-x64.systemd-boot.esp() { _msg_info "Done! systemd-boot set up for UEFI booting successfully." } -_make_boot_uefi-x64.refind.esp() { +_make_bootmode_uefi-x64.refind.esp() { local efiboot_imgsize="0" _msg_info "Setting up rEFInd for UEFI booting..." @@ -632,143 +589,344 @@ _make_boot_uefi-x64.refind.esp() { } # Prepare efiboot.img::/EFI for "El Torito" EFI boot mode -_make_boot_uefi-x64.systemd-boot.eltorito() { - _run_once _make_boot_uefi-x64.systemd-boot.esp - # Set up /EFI on ISO-9660 - _run_once _make_efi +_make_bootmode_uefi-x64.systemd-boot.eltorito() { + _run_once _make_bootmode_uefi-x64.systemd-boot.esp + # Set up /EFI on ISO-9660 to allow preparing an installation medium by manually copying files + _run_once _make_efi_dir_on_iso9660 } -_make_boot_uefi-x64.refind.eltorito() { - _run_once _make_boot_uefi-x64.refind.esp - # Set up /EFI on ISO-9660 - _run_once _make_refind_efi +_make_bootmode_uefi-x64.refind.eltorito() { + _run_once _make_bootmode_uefi-x64.refind.esp + # Set up /EFI on ISO-9660 to allow preparing an installation medium by manually copying files + _run_once _make_refind_efi_dir_on_iso9660 } -# Build airootfs filesystem image -_make_prepare() { - if [[ "${airootfs_image_type}" == "squashfs" ]]; then # prepare airootfs.sfs for overlayfs usage (default) - _run_once _mkairootfs_sfs - elif [[ "${airootfs_image_type}" == "ext4+squashfs" ]]; then # prepare airootfs.sfs for dm-snapshot usage - _run_once _mkairootfs_img +_validate_requirements_bootmode_bios.syslinux.mbr() { + # bios.syslinux.mbr requires bios.syslinux.eltorito + # shellcheck disable=SC2076 + if [[ ! " ${bootmodes[*]} " =~ ' bios.syslinux.eltorito ' ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Using 'bios.syslinux.mbr' boot mode without 'bios.syslinux.eltorito' is not supported." 0 + fi + + # Check if the syslinux package is in the package list + # shellcheck disable=SC2076 + if [[ ! " ${pkg_list[*]} " =~ ' syslinux ' ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The 'syslinux' package is missing from the package list!" 0 + fi + + # Check if syslinux configuration files exist + if [[ ! -d "${profile}/syslinux" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The '${profile}/syslinux' directory is missing!" 0 else - _msg_error "Unsupported image type: '${airootfs_image_type}'" 1 + local cfgfile + for cfgfile in "${profile}/syslinux/"*'.cfg'; do + if [[ -e "${cfgfile}" ]]; then + break + else + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/syslinux/'!" 0 + fi + done fi - _mkchecksum - if [[ "${gpg_key}" ]]; then - _mksignature + + # Check for optional packages + # shellcheck disable=SC2076 + if [[ ! " ${pkg_list[*]} " =~ ' memtest86+ ' ]]; then + _msg_info "Validating '${bootmode}': 'memtest86+' is not in the package list. Memmory testing will not be available from syslinux." fi } -# Build ISO -_make_iso() { - local xorrisofs_options=() - - [[ -d "${out_dir}" ]] || install -d -- "${out_dir}" +_validate_requirements_bootmode_bios.syslinux.eltorito() { + # Check if the syslinux package is in the package list + # shellcheck disable=SC2076 + if [[ ! " ${pkg_list[*]} " =~ ' syslinux ' ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The 'syslinux' package is missing from the package list!" 0 + fi - if [[ "${quiet}" == "y" ]]; then - xorrisofs_options+=('-quiet') + # Check if isolinux configuration files exist + if [[ ! -d "${profile}/isolinux" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The '${profile}/isolinux' directory is missing!" 0 + else + local cfgfile + for cfgfile in "${profile}/isolinux/"*'.cfg'; do + if [[ -e "${cfgfile}" ]]; then + break + else + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/isolinux/'!" 0 + fi + done fi - # xorrisofs options for x86 BIOS booting using SYSLINUX + # Check for optional packages # shellcheck disable=SC2076 - if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.' ]]; then + if [[ ! " ${pkg_list[*]} " =~ ' memtest86+ ' ]]; then + _msg_info "Validating '${bootmode}': 'memtest86+' is not in the package list. Memory testing will not be available from syslinux." + fi +} - # SYSLINUX El Torito - if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.eltorito ' ]]; then - if [[ ! -f "${isofs_dir}/isolinux/isolinux.bin" ]]; then - _msg_error "The file '${isofs_dir}/isolinux/isolinux.bin' does not exist." 1 - fi +_validate_requirements_bootmode_uefi-x64.systemd-boot.esp() { + # Check if mkfs.fat is available + if ! command -v mkfs.fat &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': mkfs.fat is not available on this host. Install 'dosfstools'!" 0 + fi - # SYSLINUX MBR - if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then - if [[ ! -f "${isofs_dir}/isolinux/isohdpfx.bin" ]]; then - _msg_error "The file '${isofs_dir}/isolinux/isohdpfx.bin' does not exist." 1 - fi + # Check if mmd and mcopy are available + if ! { command -v mmd &> /dev/null && command -v mcopy &> /dev/null; }; then + _msg_error "Validating '${bootmode}': mmd and/or mcopy are not available on this host. Install 'mtools'!" 0 + fi - xorrisofs_options+=( - # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot isolinux/isolinux.bin" - '-isohybrid-mbr' "${isofs_dir}/isolinux/isohdpfx.bin" - # When GPT is used, create an additional partition in the MBR (besides 0xEE) for sectors 0–1 (MBR - # bootstrap code area) and mark it as bootable - # This violates the UEFI specification, but may allow booting on some systems - # https://wiki.parabola.nu/Partitioning#Tricking_old_BIOS_into_booting_from_GPT - '--mbr-force-bootable' - # Set the ISO 9660 partition's type to "Linux filesystem data" - # When only MBR is present, the partition type ID will be 0x83 "Linux" as xorriso translates all - # GPT partition type GUIDs except for the ESP GUID to MBR type ID 0x83 - '-iso_mbr_part_type' '0FC63DAF-8483-4772-8E79-3D69D8477DE4' - # Move the first partition away from the start of the ISO to match the expectations of partition - # editors - # May allow booting on some systems - # https://dev.lovelyhq.com/libburnia/libisoburn/src/branch/master/doc/partition_offset.wiki - '-partition_offset' '16' - ) + # Check if systemd-boot configuration files exist + if [[ ! -d "${profile}/efiboot/loader/entries" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The '${profile}/efiboot/loader/entries' directory is missing!" 0 + else + if [[ ! -e "${profile}/efiboot/loader/loader.conf" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': File '${profile}/efiboot/loader/loader.conf' not found!" 0 + fi + local conffile + for conffile in "${profile}/efiboot/loader/entries/"*'.conf'; do + if [[ -e "${conffile}" ]]; then + break + else + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/efiboot/loader/entries/'!" 0 fi + done + fi - xorrisofs_options+=( - # El Torito boot image for x86 BIOS - '-eltorito-boot' 'isolinux/isolinux.bin' - # El Torito boot catalog file - '-eltorito-catalog' 'isolinux/boot.cat' - # Required options to boot with ISOLINUX - '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table' - ) - else - _msg_error "Using 'bios.syslinux.mbr' boot mode without 'bios.syslinux.eltorito' is not supported." 1 + # Check for optional packages + # shellcheck disable=SC2076 + if [[ "${arch}" == "dual" ]]; then + if [[ ! " ${pkg_list_x86_64[*]} " =~ ' edk2-shell ' ]]; then + _msg_info "'edk2-shell' is not in the x86_64 package list. The ISO will not contain a bootable UEFI shell." + fi + else + if [[ ! " ${pkg_list[*]} " =~ ' edk2-shell ' ]]; then + _msg_info "'edk2-shell' is not in the package list. The ISO will not contain a bootable UEFI shell." fi fi +} - # xorrisofs options for X64 UEFI booting using rEFInd/systemd-boot - # shellcheck disable=SC2076 - if [[ " ${bootmodes[*]} " =~ ( uefi-x64.refind.| uefi-x64.systemd-boot.) ]]; then - if [[ ! -f "${work_dir}/efiboot.img" ]]; then - _msg_error "The file '${work_dir}/efiboot.img' does not exist." 1 +_validate_requirements_bootmode_uefi-x64.refind.esp() { + # Check if mkfs.fat is available + if ! command -v mkfs.fat &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': mkfs.fat is not available on this host. Install 'dosfstools'!" 0 + fi + + # Check if mmd and mcopy are available + if ! { command -v mmd &> /dev/null && command -v mcopy &> /dev/null; }; then + _msg_error "Validating '${bootmode}': mmd and/or mcopy are not available on this host. Install 'mtools'!" 0 + fi + + # Check if rEFInd configuration files exist + if [[ ! -d "${profile}/efiboot/EFI/BOOT/entries" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': The '${profile}/efiboot/EFI/BOOT/entries' directory is missing!" 0 + else + if [[ ! -e "${profile}/efiboot/EFI/BOOT/refind.conf" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': File '${profile}/efiboot/EFI/BOOT/refind.conf' not found!" 0 fi - [[ -e "${isofs_dir}/EFI/parabolaiso" ]] && rm -rf -- "${isofs_dir}/EFI/parabolaiso" - - # rEFInd/systemd-boot in an attached EFI system partition - if [[ " ${bootmodes[*]} " =~ ( uefi-x64.refind.esp| uefi-x64.systemd-boot.esp) ]]; then - # Move the first partition away from the start of the ISO, otherwise the GPT will not be valid and ISO 9660 - # partition will not be mountable - [[ " ${xorrisofs_options[*]} " =~ ' -partition_offset ' ]] || xorrisofs_options+=('-partition_offset' '16') - xorrisofs_options+=( - # Attach efiboot.img as a second partition and set its partition type to "EFI system partition" - '-append_partition' '2' 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' "${work_dir}/efiboot.img" - # Ensure GPT is used as some systems do not support UEFI booting without it - '-appended_part_as_gpt' - ) - - # rEFInd/systemd-boot in an attached EFI system partition via El Torito - if [[ " ${bootmodes[*]} " =~ ( uefi-x64.refind.eltorito| uefi-x64.systemd-boot.eltorito) ]]; then - xorrisofs_options+=( - # Start a new El Torito boot entry for UEFI - '-eltorito-alt-boot' - # Set the second partition as the El Torito UEFI boot image - '-e' '--interval:appended_partition_2:all::' - # Boot image is not emulating floppy or hard disk; required for all known boot loaders - '-no-emul-boot' - ) + local conffile + for conffile in "${profile}/efiboot/EFI/BOOT/entries/"*'.conf'; do + if [[ -e "${conffile}" ]]; then + break + else + (( validation_error=validation_error+1 )) + _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/efiboot/EFI/BOOT/entries/'!" 0 fi - # rEFInd/systemd-boot in an embedded efiboot.img via El Torito - elif [[ " ${bootmodes[*]} " =~ ( uefi-x64.refind.eltorito| uefi-x64.systemd-boot.eltorito) ]]; then - # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a - # file inside the ISO 9660 file system - install -d -m 0755 -- "${isofs_dir}/EFI/parabolaiso" - cp -a -- "${work_dir}/efiboot.img" "${isofs_dir}/EFI/parabolaiso/efiboot.img" - - xorrisofs_options+=( - # Start a new El Torito boot entry for UEFI - '-eltorito-alt-boot' - # Set efiboot.img as the El Torito UEFI boot image - '-e' 'EFI/parabolaiso/efiboot.img' - # Boot image is not emulating floppy or hard disk; required for all known boot loaders - '-no-emul-boot' - ) + done + fi + + # Check for optional packages + # shellcheck disable=SC2076 + if [[ "${arch}" == "dual" ]]; then + if [[ ! " ${pkg_list_x86_64[*]} " =~ ' edk2-shell ' ]]; then + _msg_info "'edk2-shell' is not in the x86_64 package list. The ISO will not contain a bootable UEFI shell." + fi + else + if [[ ! " ${pkg_list[*]} " =~ ' edk2-shell ' ]]; then + _msg_info "'edk2-shell' is not in the package list. The ISO will not contain a bootable UEFI shell." fi + fi +} + +_validate_requirements_bootmode_uefi-x64.systemd-boot.eltorito() { + # uefi-x64.systemd-boot.eltorito has the exact same requirements as uefi-x64.systemd-boot.esp + _validate_requirements_bootmode_uefi-x64.systemd-boot.esp +} + +_validate_requirements_bootmode_uefi-x64.refind.eltorito() { + # uefi-x64.refind.eltorito has the exact same requirements as uefi-x64.refind.esp + _validate_requirements_bootmode_uefi-x64.refind.esp +} - # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito - [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat') +# Build airootfs filesystem image +_prepare_airootfs_image() { + _run_once "_mkairootfs_${airootfs_image_type}" + _mkchecksum + if [[ -n "${gpg_key}" ]]; then + _mksignature fi +} + +_validate_requirements_airootfs_image_type_squashfs() { + if ! command -v mksquashfs &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${airootfs_image_type}': mksquashfs is not available on this host. Install 'squashfs-tools'!" 0 + fi +} + +_validate_requirements_airootfs_image_type_ext4+squashfs() { + if ! { command -v mkfs.ext4 &> /dev/null && command -v tune2fs &> /dev/null; }; then + (( validation_error=validation_error+1 )) + _msg_error "Validating '${airootfs_image_type}': mkfs.ext4 and/or tune2fs is not available on this host. Install 'e2fsprogs'!" 0 + fi + _validate_requirements_airootfs_image_type_squashfs +} + +# SYSLINUX El Torito +_add_xorrisofs_options_bios.syslinux.eltorito() { + xorrisofs_options+=( + # El Torito boot image for x86 BIOS + '-eltorito-boot' 'isolinux/isolinux.bin' + # El Torito boot catalog file + '-eltorito-catalog' 'isolinux/boot.cat' + # Required options to boot with ISOLINUX + '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table' + ) +} + +# SYSLINUX MBR +_add_xorrisofs_options_bios.syslinux.mbr() { + xorrisofs_options+=( + # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot isolinux/isolinux.bin" + '-isohybrid-mbr' "${isofs_dir}/isolinux/isohdpfx.bin" + # When GPT is used, create an additional partition in the MBR (besides 0xEE) for sectors 0–1 (MBR + # bootstrap code area) and mark it as bootable + # This violates the UEFI specification, but may allow booting on some systems + # https://wiki.archlinux.org/index.php/Partitioning#Tricking_old_BIOS_into_booting_from_GPT + '--mbr-force-bootable' + # Set the ISO 9660 partition's type to "Linux filesystem data" + # When only MBR is present, the partition type ID will be 0x83 "Linux" as xorriso translates all + # GPT partition type GUIDs except for the ESP GUID to MBR type ID 0x83 + '-iso_mbr_part_type' '0FC63DAF-8483-4772-8E79-3D69D8477DE4' + # Move the first partition away from the start of the ISO to match the expectations of partition + # editors + # May allow booting on some systems + # https://dev.lovelyhq.com/libburnia/libisoburn/src/branch/master/doc/partition_offset.wiki + '-partition_offset' '16' + ) +} + +# systemd-boot in an attached EFI system partition +_add_xorrisofs_options_uefi-x64.systemd-boot.esp() { + # Move the first partition away from the start of the ISO, otherwise the GPT will not be valid and ISO 9660 + # partition will not be mountable + # shellcheck disable=SC2076 + [[ " ${xorrisofs_options[*]} " =~ ' -partition_offset ' ]] || xorrisofs_options+=('-partition_offset' '16') + xorrisofs_options+=( + # Attach efiboot.img as a second partition and set its partition type to "EFI system partition" + '-append_partition' '2' 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' "${work_dir}/efiboot.img" + # Ensure GPT is used as some systems do not support UEFI booting without it + '-appended_part_as_gpt' + ) +} + +# systemd-boot via El Torito +_add_xorrisofs_options_uefi-x64.systemd-boot.eltorito() { + # shellcheck disable=SC2076 + if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' ]]; then + # systemd-boot in an attached EFI system partition via El Torito + xorrisofs_options+=( + # Start a new El Torito boot entry for UEFI + '-eltorito-alt-boot' + # Set the second partition as the El Torito UEFI boot image + '-e' '--interval:appended_partition_2:all::' + # Boot image is not emulating floppy or hard disk; required for all known boot loaders + '-no-emul-boot' + ) + else + # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a + # file inside the ISO 9660 file system + install -d -m 0755 -- "${isofs_dir}/EFI/parabolaiso" + cp -a -- "${work_dir}/efiboot.img" "${isofs_dir}/EFI/parabolaiso/efiboot.img" + # systemd-boot in an embedded efiboot.img via El Torito + xorrisofs_options+=( + # Start a new El Torito boot entry for UEFI + '-eltorito-alt-boot' + # Set efiboot.img as the El Torito UEFI boot image + '-e' 'EFI/parabolaiso/efiboot.img' + # Boot image is not emulating floppy or hard disk; required for all known boot loaders + '-no-emul-boot' + ) + fi + # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito + # shellcheck disable=SC2076 + [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat') +} + +# rEFInd in an attached EFI system partition +_add_xorrisofs_options_uefi-x64.refind.esp() { + # _add_xorrisofs_options_uefi-x64.refind.esp does the exact same thing as _add_xorrisofs_options_uefi-x64.systemd-boot.esp + _add_xorrisofs_options_uefi-x64.systemd-boot.esp +} + +# rEFInd via El Torito +_add_xorrisofs_options_uefi-x64.refind.eltorito() { + # shellcheck disable=SC2076 + if [[ " ${bootmodes[*]} " =~ ' uefi-x64.refind.esp ' ]]; then + # rEFInd in an attached EFI system partition via El Torito + xorrisofs_options+=( + # Start a new El Torito boot entry for UEFI + '-eltorito-alt-boot' + # Set the second partition as the El Torito UEFI boot image + '-e' '--interval:appended_partition_2:all::' + # Boot image is not emulating floppy or hard disk; required for all known boot loaders + '-no-emul-boot' + ) + else + # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a + # file inside the ISO 9660 file system + install -d -m 0755 -- "${isofs_dir}/EFI/parabolaiso" + cp -a -- "${work_dir}/efiboot.img" "${isofs_dir}/EFI/parabolaiso/efiboot.img" + # rEFInd in an embedded efiboot.img via El Torito + xorrisofs_options+=( + # Start a new El Torito boot entry for UEFI + '-eltorito-alt-boot' + # Set efiboot.img as the El Torito UEFI boot image + '-e' 'EFI/parabolaiso/efiboot.img' + # Boot image is not emulating floppy or hard disk; required for all known boot loaders + '-no-emul-boot' + ) + fi + # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito + # shellcheck disable=SC2076 + [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat') +} + +# Build ISO +_build_iso() { + local xorrisofs_options=() + local bootmode + + [[ -d "${out_dir}" ]] || install -d -- "${out_dir}" + + [[ "${quiet}" == "y" ]] && xorrisofs_options+=('-quiet') + + # Add required xorrisofs options for each boot mode + for bootmode in "${bootmodes[@]}"; do + typeset -f "_add_xorrisofs_options_${bootmode}" &> /dev/null && "_add_xorrisofs_options_${bootmode}" + done _msg_info "Creating ISO image..." xorriso -as mkisofs \ @@ -790,6 +948,10 @@ _make_iso() { # Read profile's values from profiledef.sh _read_profile() { + local validation_error=0 + local bootmode + + _msg_info "Reading profile..." if [[ -z "${profile}" ]]; then _msg_error "No profile specified!" 1 fi @@ -804,61 +966,100 @@ _read_profile() { # shellcheck source=configs/releng/profiledef.sh . "${profile}/profiledef.sh" + # Resolve paths if [[ "${arch}" == "dual" ]]; then - # Resolve paths - packages="$(realpath -- "${profile}"/packages.{both,i686,x86_64})" + packages_dual="$(realpath -- "${profile}"/packages.{both,i686,x86_64})" pacman_conf="$(realpath -- "${pacman_conf}")" - - # Enumerate packages - local pkgs - for pkgs in ${packages}; do - if [[ "${pkgs##*/}" = "packages.both" ]]; then - [[ -e "${pkgs}" ]] || _msg_error "File '${pkgs}' does not exist!" 1 - mapfile -t pkg_list < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${pkgs}") - elif [[ -e "${pkgs}" ]]; then - mapfile -t "pkg_list_${pkgs##*.}" < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${pkgs}") - fi - done else - # Resolve paths packages="$(realpath -- "${profile}/packages.${arch}")" pacman_conf="$(realpath -- "${pacman_conf}")" - - # Enumerate packages - [[ -e "${packages}" ]] || _msg_error "File '${packages}' does not exist!" 1 - mapfile -t pkg_list < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}") fi cd -- "${OLDPWD}" + + # Validate profile + # Check if the package list file exists and read packages from it + if [[ "${arch}" == "dual" ]]; then + for packages in ${packages_dual}; do + if [[ "${packages##*/}" = "packages.both" ]]; then + if [[ -e "${packages}" ]]; then + mapfile -t pkg_list < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}") + if (( ${#pkg_list} < 1 )); then + (( validation_error=validation_error+1 )) + _msg_error "No package specified in '${packages}'." 0 + fi + else + (( validation_error=validation_error+1 )) + _msg_error "File '${packages}' does not exist." 0 + fi + elif [[ -e "${packages}" ]]; then + mapfile -t "pkg_list_${packages##*.}" < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}") + fi + done + else + if [[ -e "${packages}" ]]; then + mapfile -t pkg_list < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}") + if (( ${#pkg_list} < 1 )); then + (( validation_error=validation_error+1 )) + _msg_error "No package specified in '${packages}'." 0 + fi + else + (( validation_error=validation_error+1 )) + _msg_error "File '${packages}' does not exist." 0 + fi + fi + # Check if pacman configuration file exists + if [[ ! -e "${pacman_conf}" ]]; then + (( validation_error=validation_error+1 )) + _msg_error "File '${pacman_conf}' does not exist." 0 + fi + # Check if the specified bootmodes are supported + for bootmode in "${bootmodes[@]}"; do + if typeset -f "_make_bootmode_${bootmode}" &> /dev/null; then + if typeset -f "_validate_requirements_bootmode_${bootmode}" &> /dev/null; then + "_validate_requirements_bootmode_${bootmode}" + else + _msg_warning "Function '_validate_requirements_bootmode_${bootmode}' does not exist. Validating the requirements of '${bootmode}' boot mode will not be possible." + fi + else + (( validation_error=validation_error+1 )) + _msg_error "${bootmode} is not a valid boot mode!" 0 + fi + done + # Check if the specified airootfs_image_type is supported + if typeset -f "_mkairootfs_${airootfs_image_type}" &> /dev/null; then + if typeset -f "_validate_requirements_airootfs_image_type_${airootfs_image_type}" &> /dev/null; then + "_validate_requirements_airootfs_image_type_${airootfs_image_type}" + else + _msg_warning "Function '_validate_requirements_airootfs_image_type_${airootfs_image_type}' does not exist. Validating the requirements of '${airootfs_image_type}' airootfs image type will not be possible." + fi + else + (( validation_error=validation_error+1 )) + _msg_error "Unsupported image type: '${airootfs_image_type}'" 0 + fi + if (( validation_error )); then + _msg_error "${validation_error} errors were encountered while validating the profile. Aborting." 1 + fi fi + _msg_info "Done!" } # set overrides from mkparabolaiso option parameters, if present _set_overrides() { - if [[ -n "$override_iso_label" ]]; then - iso_label="$override_iso_label" - fi - if [[ -n "$override_iso_publisher" ]]; then - iso_publisher="$override_iso_publisher" - fi - if [[ -n "$override_iso_application" ]]; then - iso_application="$override_iso_application" - fi - if [[ -n "$override_install_dir" ]]; then - install_dir="$override_install_dir" - fi - if [[ -n "$override_pacman_conf" ]]; then - pacman_conf="$override_pacman_conf" - fi - if [[ -n "$override_gpg_key" ]]; then - gpg_key="$override_gpg_key" - fi + _msg_info "Setting overrides..." + [[ -n "$override_iso_label" ]] && iso_label="$override_iso_label" + [[ -n "$override_iso_publisher" ]] && iso_publisher="$override_iso_publisher" + [[ -n "$override_iso_application" ]] && iso_application="$override_iso_application" + [[ -n "$override_install_dir" ]] && install_dir="$override_install_dir" + [[ -n "$override_pacman_conf" ]] && pacman_conf="$override_pacman_conf" + [[ -n "$override_gpg_key" ]] && gpg_key="$override_gpg_key" + # NOTE: the call to _msg_info() conveniently guards this function from evaluating to false + _msg_info "Done!" } + _export_gpg_publickey() { - if [[ -n "${gpg_key}" ]]; then - gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}" - fi + gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}" } @@ -875,7 +1076,6 @@ _build_profile() { isofs_dir="${work_dir}/iso" # Set ISO file name img_name="${iso_name}-${iso_version}-${arch}.iso" - # Create working directory [[ -d "${work_dir}" ]] || install -d -- "${work_dir}" # Write build date to file or if the file exists, read it from there @@ -885,27 +1085,25 @@ _build_profile() { printf '%s\n' "$SOURCE_DATE_EPOCH" > "${work_dir}/build_date" fi - _show_config - + [[ "${quiet}" == "n" ]] && _show_config _run_dual '_run_once _make_pacman_conf' - _run_once _export_gpg_publickey + [[ -n "${gpg_key}" ]] && _run_once _export_gpg_publickey _run_dual '_run_once _make_custom_airootfs' \ '_run_once _make_packages' _run_dual '_run_once _make_customize_airootfs' _run_dual '_run_once _make_pkglist' _make_bootmodes - _run_dual '_run_once _cleanup' \ - '_run_once _make_prepare' - _run_once _make_iso + _run_dual '_run_once _cleanup_airootfs' \ + '_run_once _prepare_airootfs_image' + _run_once _build_iso } -while getopts 'p:r:C:L:P:A:D:w:o:g:vh?' arg; do +while getopts 'p:C:L:P:A:D:w:o:g:vh?' arg; do case "${arg}" in p) read -r -a opt_pkg_list <<< "${OPTARG}" pkg_list+=("${opt_pkg_list[@]}") ;; - r) run_cmd="${OPTARG}" ;; C) override_pacman_conf="$(realpath -- "${OPTARG}")" ;; L) override_iso_label="${OPTARG}" ;; P) override_iso_publisher="${OPTARG}" ;; @@ -937,9 +1135,6 @@ fi # get the absolute path representation of the first non-option argument profile="$(realpath -- "${1}")" -# Set directory path defaults for legacy commands -airootfs_dir="${work_dir}/airootfs" -isofs_dir="${work_dir}/iso" _read_profile _set_overrides _build_profile -- cgit v1.2.2