From 2cac53967b99a497914c001ddeeb9b72d090b5d4 Mon Sep 17 00:00:00 2001 From: David Runge Date: Sun, 2 May 2021 18:58:47 +0200 Subject: mkarchiso: Implement buildmodes that allow building bootstrap images archiso/mkarchiso: Introduce a buildmodes array, that can be used to build towards more than one output artifact type. Add a buildmode for building a bootstrap image (a compressed file containing a very minimal Arch installation). The buildmodes can be set either using a `buildmodes` array in a `profiledef.sh` or by using the `-m` option flag to mkarchiso and providing a space delimited, quoted list. The 'iso' buildmode is always the default if no buildmodes are setup. Implement building a bootstrap image, when using the 'bootstrap' `buildmode`, which uses a profile's 'bootstrap_packages.$arch' file to install packages using pacstrap and compressing it to a bootstrap image. The name of the output file is currently constructed from the `iso_name` value by appending `-bootstrap`. Replace the uses of `airootfs_dir` with the more generic `pacstrap_dir`, as the location denotes where pacstrap is being used. Replace uses of `img_name` with `image_name` and removing it from the global scope, so that it can be overridden per each buildmode. Rename `_cleanup_airootfs_dir()` to `_cleanup_pacstrap_dir()`. Make `_run_once()` more generic by prepending the state files with a string defined by `run_once_mode`. Add `_validate_requirements_buildmode_all()`, `_validate_requirements_buildmode_bootstrap()` and `_validate_requirements_buildmode_iso()` to validate the general requirements of the different buildmodes. Add `_build_bootstrap_image()` to generate the bootstrap image using bsdtar. Rename `_build_iso()` to `_build_iso_image()` to fit the naming of the respective bootstrap function. Extend `_read_profile()` to include the reading of bootstrap image specific packages from a file. Extend `_validate_options()` to include testing of the bootstrap packages and running of validation functions for all buildmodes. Change `_set_overrides()` to override the buildmodes if they are specified via the `-m` option flag. Change `_make_version()` to be used generically in all buildmodes. Change `_make_pkglist()` to be used generically in all buildmodes. Rename `_build_profile()` to `_build_buildmode_iso()` and set local variables that are specific to the buildmode, such as `image_name`, `pacstrap_dir`, `run_once_mode` , `buildmode_packages` and `buildmode_pkg_list`. Add `_build_buildmode_bootstrap()` and set local variables that are specific to the buildmode, such as `image_name`, `pacstrap_dir`, `run_once_mode` , `buildmode_packages` and `buildmode_pkg_list`. Add the `-m` option flag to the list of flags. Closes #127 --- archiso/mkarchiso | 393 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 272 insertions(+), 121 deletions(-) (limited to 'archiso/mkarchiso') diff --git a/archiso/mkarchiso b/archiso/mkarchiso index aaf8325..683f90e 100755 --- a/archiso/mkarchiso +++ b/archiso/mkarchiso @@ -14,10 +14,10 @@ app_name="${0##*/}" # Define global variables. All of them will be overwritten later pkg_list=() +bootstrap_pkg_list=() quiet="" work_dir="" out_dir="" -img_name="" gpg_key="" iso_name="" iso_label="" @@ -28,6 +28,9 @@ install_dir="" arch="" pacman_conf="" packages="" +bootstrap_packages="" +pacstrap_dir="" +buildmodes=() bootmodes=() airootfs_image_type="" airootfs_image_tool_options=() @@ -63,8 +66,8 @@ _msg_error() { _mount_airootfs() { trap "_umount_airootfs" EXIT HUP INT TERM install -d -m 0755 -- "${work_dir}/mnt/airootfs" - _msg_info "Mounting '${airootfs_dir}.img' on '${work_dir}/mnt/airootfs'..." - mount -- "${airootfs_dir}.img" "${work_dir}/mnt/airootfs" + _msg_info "Mounting '${pacstrap_dir}.img' on '${work_dir}/mnt/airootfs'..." + mount -- "${pacstrap_dir}.img" "${work_dir}/mnt/airootfs" _msg_info "Done!" } @@ -95,6 +98,8 @@ usage: ${app_name} [options] Default: '${iso_publisher}' -g Set the PGP key ID to be used for signing the rootfs image -h This message + -m [build modes] Build modes to use (valid modes are: 'bootstrap' and 'iso'). + Multiple build modes are provided as quoted, space delimited list. -o Set the output directory Default: '${out_dir}' -p PACKAGE(S) Package(s) to install. @@ -119,38 +124,41 @@ _show_config() { _msg_info " Installation directory: ${install_dir}" _msg_info " Build date: ${build_date}" _msg_info " Output directory: ${out_dir}" + _msg_info " Current build mode: ${buildmode}" + _msg_info " Build modes: ${buildmodes[*]}" _msg_info " GPG key: ${gpg_key:-None}" _msg_info " Profile: ${profile}" _msg_info "Pacman configuration file: ${pacman_conf}" - _msg_info " Image file name: ${img_name}" + _msg_info " Image file name: ${image_name:-None}" _msg_info " ISO volume label: ${iso_label}" _msg_info " ISO publisher: ${iso_publisher}" _msg_info " ISO application: ${iso_application}" _msg_info " Boot modes: ${bootmodes[*]}" - _msg_info " Packages: ${pkg_list[*]}" + _msg_info " Packages File: ${buildmode_packages}" + _msg_info " Packages: ${buildmode_pkg_list[*]}" } # Cleanup airootfs -_cleanup_airootfs() { - _msg_info "Cleaning up what we can on airootfs..." +_cleanup_pacstrap_dir() { + _msg_info "Cleaning up in pacstrap location..." # Delete all files in /boot - [[ -d "${airootfs_dir}/boot" ]] && find "${airootfs_dir}/boot" -mindepth 1 -delete + [[ -d "${pacstrap_dir}/boot" ]] && find "${pacstrap_dir}/boot" -mindepth 1 -delete # Delete pacman database sync cache files (*.tar.gz) - [[ -d "${airootfs_dir}/var/lib/pacman" ]] && find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete + [[ -d "${pacstrap_dir}/var/lib/pacman" ]] && find "${pacstrap_dir}/var/lib/pacman" -maxdepth 1 -type f -delete # Delete pacman database sync cache - [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]] && find "${airootfs_dir}/var/lib/pacman/sync" -delete + [[ -d "${pacstrap_dir}/var/lib/pacman/sync" ]] && find "${pacstrap_dir}/var/lib/pacman/sync" -delete # Delete pacman package cache - [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]] && find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete + [[ -d "${pacstrap_dir}/var/cache/pacman/pkg" ]] && find "${pacstrap_dir}/var/cache/pacman/pkg" -type f -delete # Delete all log files, keeps empty dirs. - [[ -d "${airootfs_dir}/var/log" ]] && find "${airootfs_dir}/var/log" -type f -delete + [[ -d "${pacstrap_dir}/var/log" ]] && find "${pacstrap_dir}/var/log" -type f -delete # Delete all temporary files and dirs - [[ -d "${airootfs_dir}/var/tmp" ]] && find "${airootfs_dir}/var/tmp" -mindepth 1 -delete + [[ -d "${pacstrap_dir}/var/tmp" ]] && find "${pacstrap_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 - rm -f -- "${airootfs_dir}/etc/machine-id" - printf '' > "${airootfs_dir}/etc/machine-id" + rm -f -- "${pacstrap_dir}/etc/machine-id" + printf '' > "${pacstrap_dir}/etc/machine-id" _msg_info "Done!" } @@ -166,49 +174,49 @@ _run_mksquashfs() { # Makes a ext4 filesystem inside a SquashFS from a source directory. _mkairootfs_ext4+squashfs() { - [[ -e "${airootfs_dir}" ]] || _msg_error "The path '${airootfs_dir}' does not exist" 1 + [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1 _msg_info "Creating ext4 image of 32 GiB..." if [[ "${quiet}" == "y" ]]; then - mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G + mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${pacstrap_dir}.img" 32G else - mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G + mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${pacstrap_dir}.img" 32G fi - tune2fs -c 0 -i 0 -- "${airootfs_dir}.img" > /dev/null + tune2fs -c 0 -i 0 -- "${pacstrap_dir}.img" > /dev/null _msg_info "Done!" _mount_airootfs - _msg_info "Copying '${airootfs_dir}/' to '${work_dir}/mnt/airootfs/'..." - cp -aT -- "${airootfs_dir}/" "${work_dir}/mnt/airootfs/" + _msg_info "Copying '${pacstrap_dir}/' to '${work_dir}/mnt/airootfs/'..." + cp -aT -- "${pacstrap_dir}/" "${work_dir}/mnt/airootfs/" chown -- 0:0 "${work_dir}/mnt/airootfs/" _msg_info "Done!" _umount_airootfs install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating SquashFS image, this may take some time..." - _run_mksquashfs "${airootfs_dir}.img" + _run_mksquashfs "${pacstrap_dir}.img" _msg_info "Done!" - rm -- "${airootfs_dir}.img" + rm -- "${pacstrap_dir}.img" } # Makes a SquashFS filesystem from a source directory. _mkairootfs_squashfs() { - [[ -e "${airootfs_dir}" ]] || _msg_error "The path '${airootfs_dir}' does not exist" 1 + [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1 install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating SquashFS image, this may take some time..." - _run_mksquashfs "${airootfs_dir}" + _run_mksquashfs "${pacstrap_dir}" } # Makes an EROFS file system from a source directory. _mkairootfs_erofs() { local fsuuid - [[ -e "${airootfs_dir}" ]] || _msg_error "The path '${airootfs_dir}' does not exist" 1 + [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1 install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}" local image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" # Generate reproducible file system UUID from SOURCE_DATE_EPOCH fsuuid="$(uuidgen --sha1 --namespace 93a870ff-8565-4cf3-a67b-f47299271a96 --name "${SOURCE_DATE_EPOCH}")" _msg_info "Creating EROFS image, this may take some time..." - mkfs.erofs -U "${fsuuid}" "${airootfs_image_tool_options[@]}" -- "${image_path}" "${airootfs_dir}" + mkfs.erofs -U "${fsuuid}" "${airootfs_image_tool_options[@]}" -- "${image_path}" "${pacstrap_dir}" _msg_info "Done!" } @@ -239,9 +247,9 @@ _mksignature() { # Helper function to run functions only one time. _run_once() { - if [[ ! -e "${work_dir}/build.${1}" ]]; then + if [[ ! -e "${work_dir}/${run_once_mode}.${1}" ]]; then "$1" - touch "${work_dir}/build.${1}" + touch "${work_dir}/${run_once_mode}.${1}" fi } @@ -267,7 +275,7 @@ _make_pacman_conf() { # see `man 8 pacman` for further info pacman-conf --config "${pacman_conf}" | \ sed "/CacheDir/d;/DBPath/d;/HookDir/d;/LogFile/d;/RootDir/d;/\[options\]/a CacheDir = ${_cache_dirs} - /\[options\]/a HookDir = ${airootfs_dir}/etc/pacman.d/hooks/" > "${work_dir}/pacman.conf" + /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" > "${work_dir}/${buildmode}.pacman.conf" } # Prepare working directory and copy custom airootfs files (airootfs) @@ -275,27 +283,27 @@ _make_custom_airootfs() { local passwd=() local filename permissions - install -d -m 0755 -o 0 -g 0 -- "${airootfs_dir}" + install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}" if [[ -d "${profile}/airootfs" ]]; then _msg_info "Copying custom airootfs files..." - cp -af --no-preserve=ownership,mode -- "${profile}/airootfs/." "${airootfs_dir}" + cp -af --no-preserve=ownership,mode -- "${profile}/airootfs/." "${pacstrap_dir}" # Set ownership and mode for files and directories for filename in "${!file_permissions[@]}"; do IFS=':' read -ra permissions <<< "${file_permissions["${filename}"]}" - # Prevent file path traversal outside of $airootfs_dir - if [[ "$(realpath -q -- "${airootfs_dir}${filename}")" != "${airootfs_dir}"* ]]; then - _msg_error "Failed to set permissions on '${airootfs_dir}${filename}'. Outside of valid path." 1 + # Prevent file path traversal outside of $pacstrap_dir + if [[ "$(realpath -q -- "${pacstrap_dir}${filename}")" != "${pacstrap_dir}"* ]]; then + _msg_error "Failed to set permissions on '${pacstrap_dir}${filename}'. Outside of valid path." 1 # Warn if the file does not exist - elif [[ ! -e "${airootfs_dir}${filename}" ]]; then - _msg_warning "Cannot change permissions of '${airootfs_dir}${filename}'. The file or directory does not exist." + elif [[ ! -e "${pacstrap_dir}${filename}" ]]; then + _msg_warning "Cannot change permissions of '${pacstrap_dir}${filename}'. The file or directory does not exist." else if [[ "${filename: -1}" == "/" ]]; then - chown -fhR -- "${permissions[0]}:${permissions[1]}" "${airootfs_dir}${filename}" - chmod -fR -- "${permissions[2]}" "${airootfs_dir}${filename}" + chown -fhR -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}" + chmod -fR -- "${permissions[2]}" "${pacstrap_dir}${filename}" else - chown -fh -- "${permissions[0]}:${permissions[1]}" "${airootfs_dir}${filename}" - chmod -f -- "${permissions[2]}" "${airootfs_dir}${filename}" + chown -fh -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}" + chmod -f -- "${permissions[2]}" "${pacstrap_dir}${filename}" fi fi done @@ -305,7 +313,7 @@ _make_custom_airootfs() { # Install desired packages to airootfs _make_packages() { - _msg_info "Installing packages to '${airootfs_dir}/'..." + _msg_info "Installing packages to '${pacstrap_dir}/'..." if [[ -n "${gpg_key}" ]]; then exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg" @@ -313,9 +321,9 @@ _make_packages() { fi if [[ "${quiet}" = "y" ]]; then - pacstrap -C "${work_dir}/pacman.conf" -c -G -M -- "${airootfs_dir}" "${pkg_list[@]}" &> /dev/null + pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" &> /dev/null else - pacstrap -C "${work_dir}/pacman.conf" -c -G -M -- "${airootfs_dir}" "${pkg_list[@]}" + pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" fi if [[ -n "${gpg_key}" ]]; then @@ -338,27 +346,27 @@ _make_customize_airootfs() { # Skip invalid home directories [[ "${passwd[5]}" == '/' ]] && continue [[ -z "${passwd[5]}" ]] && continue - # Prevent path traversal outside of $airootfs_dir - if [[ "$(realpath -q -- "${airootfs_dir}${passwd[5]}")" == "${airootfs_dir}"* ]]; then - if [[ ! -d "${airootfs_dir}${passwd[5]}" ]]; then - install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${airootfs_dir}${passwd[5]}" + # Prevent path traversal outside of $pacstrap_dir + if [[ "$(realpath -q -- "${pacstrap_dir}${passwd[5]}")" == "${pacstrap_dir}"* ]]; then + if [[ ! -d "${pacstrap_dir}${passwd[5]}" ]]; then + install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${pacstrap_dir}${passwd[5]}" fi - cp -dnRT --preserve=mode,timestamps,links -- "${airootfs_dir}/etc/skel/." "${airootfs_dir}${passwd[5]}" - chmod -f 0750 -- "${airootfs_dir}${passwd[5]}" - chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}" + cp -dnRT --preserve=mode,timestamps,links -- "${pacstrap_dir}/etc/skel/." "${pacstrap_dir}${passwd[5]}" + chmod -f 0750 -- "${pacstrap_dir}${passwd[5]}" + chown -hR -- "${passwd[2]}:${passwd[3]}" "${pacstrap_dir}${passwd[5]}" else - _msg_error "Failed to set permissions on '${airootfs_dir}${passwd[5]}'. Outside of valid path." 1 + _msg_error "Failed to set permissions on '${pacstrap_dir}${passwd[5]}'. Outside of valid path." 1 fi done < "${profile}/airootfs/etc/passwd" _msg_info "Done!" fi - if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then - _msg_info "Running customize_airootfs.sh in '${airootfs_dir}' chroot..." + if [[ -e "${pacstrap_dir}/root/customize_airootfs.sh" ]]; then + _msg_info "Running customize_airootfs.sh in '${pacstrap_dir}' chroot..." _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future archiso version." - chmod -f -- +x "${airootfs_dir}/root/customize_airootfs.sh" - eval -- arch-chroot "${airootfs_dir}" "/root/customize_airootfs.sh" - rm -- "${airootfs_dir}/root/customize_airootfs.sh" + chmod -f -- +x "${pacstrap_dir}/root/customize_airootfs.sh" + eval -- arch-chroot "${pacstrap_dir}" "/root/customize_airootfs.sh" + rm -- "${pacstrap_dir}/root/customize_airootfs.sh" _msg_info "Done! customize_airootfs.sh run successfully." fi } @@ -376,15 +384,15 @@ _make_boot_on_iso9660() { local ucode_image _msg_info "Preparing 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}/" - install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-"* "${isofs_dir}/${install_dir}/boot/${arch}/" + install -m 0644 -- "${pacstrap_dir}/boot/initramfs-"*".img" "${isofs_dir}/${install_dir}/boot/${arch}/" + install -m 0644 -- "${pacstrap_dir}/boot/vmlinuz-"* "${isofs_dir}/${install_dir}/boot/${arch}/" for ucode_image in {intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio}; do - if [[ -e "${airootfs_dir}/boot/${ucode_image}" ]]; then - install -m 0644 -- "${airootfs_dir}/boot/${ucode_image}" "${isofs_dir}/${install_dir}/boot/" - if [[ -e "${airootfs_dir}/usr/share/licenses/${ucode_image%.*}/" ]]; then + if [[ -e "${pacstrap_dir}/boot/${ucode_image}" ]]; then + install -m 0644 -- "${pacstrap_dir}/boot/${ucode_image}" "${isofs_dir}/${install_dir}/boot/" + if [[ -e "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/" ]]; then install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/" - install -m 0644 -- "${airootfs_dir}/usr/share/licenses/${ucode_image%.*}/"* \ + install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/"* \ "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/" fi fi @@ -405,28 +413,28 @@ _make_bootmode_bios.syslinux.mbr() { if [[ -e "${profile}/syslinux/splash.png" ]]; then install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/syslinux/" fi - install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/syslinux/" - install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/syslinux/" - install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/syslinux/" + install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/syslinux/" + install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/syslinux/" + install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/syslinux/" _run_once _make_boot_on_iso9660 if [[ -e "${isofs_dir}/syslinux/hdt.c32" ]]; then install -d -m 0755 -- "${isofs_dir}/syslinux/hdt" - if [[ -e "${airootfs_dir}/usr/share/hwdata/pci.ids" ]]; then - gzip -cn9 "${airootfs_dir}/usr/share/hwdata/pci.ids" > \ + if [[ -e "${pacstrap_dir}/usr/share/hwdata/pci.ids" ]]; then + gzip -cn9 "${pacstrap_dir}/usr/share/hwdata/pci.ids" > \ "${isofs_dir}/syslinux/hdt/pciids.gz" fi - find "${airootfs_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -cn9 '{}' ';' -quit > \ + find "${pacstrap_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -cn9 '{}' ';' -quit > \ "${isofs_dir}/syslinux/hdt/modalias.gz" fi # Add other aditional/extra files to ${install_dir}/boot/ - if [[ -e "${airootfs_dir}/boot/memtest86+/memtest.bin" ]]; then + if [[ -e "${pacstrap_dir}/boot/memtest86+/memtest.bin" ]]; then # rename for PXE: https://wiki.archlinux.org/index.php/Syslinux#Using_memtest - install -m 0644 -- "${airootfs_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest" + install -m 0644 -- "${pacstrap_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest" install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/" - install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \ + install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/common/GPL2/license.txt" \ "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/" fi _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully." @@ -436,8 +444,8 @@ _make_bootmode_bios.syslinux.mbr() { _make_bootmode_bios.syslinux.eltorito() { _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..." install -d -m 0755 -- "${isofs_dir}/syslinux" - install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/syslinux/" - install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/syslinux/" + install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/syslinux/" + install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/syslinux/" # ISOLINUX and SYSLINUX installation is shared _run_once _make_bootmode_bios.syslinux.mbr @@ -449,7 +457,7 @@ _make_bootmode_bios.syslinux.eltorito() { _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" \ + install -m 0644 -- "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ "${isofs_dir}/EFI/BOOT/BOOTx64.EFI" install -d -m 0755 -- "${isofs_dir}/loader/entries" @@ -464,8 +472,8 @@ _make_efi_dir_on_iso9660() { # edk2-shell based UEFI shell # shellx64.efi is picked up automatically when on / - if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then - install -m 0644 -- "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${isofs_dir}/shellx64.efi" + if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then + install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${isofs_dir}/shellx64.efi" fi _msg_info "Done!" } @@ -476,10 +484,10 @@ _make_boot_on_fat() { _msg_info "Preparing kernel and initramfs for the FAT file system..." mmd -i "${work_dir}/efiboot.img" \ "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/${arch}" - mcopy -i "${work_dir}/efiboot.img" "${airootfs_dir}/boot/vmlinuz-"* \ - "${airootfs_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/" + mcopy -i "${work_dir}/efiboot.img" "${pacstrap_dir}/boot/vmlinuz-"* \ + "${pacstrap_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/" for ucode_image in \ - "${airootfs_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} + "${pacstrap_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} do if [[ -e "${ucode_image}" ]]; then all_ucode_images+=("${ucode_image}") @@ -498,12 +506,12 @@ _make_bootmode_uefi-x64.systemd-boot.esp() { # the required image size in KiB (rounded up to the next full MiB with an additional MiB for reserved sectors) efiboot_imgsize="$(du -bc \ - "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ - "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \ + "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ + "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \ "${profile}/efiboot/" \ - "${airootfs_dir}/boot/vmlinuz-"* \ - "${airootfs_dir}/boot/initramfs-"*".img" \ - "${airootfs_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} \ + "${pacstrap_dir}/boot/vmlinuz-"* \ + "${pacstrap_dir}/boot/initramfs-"*".img" \ + "${pacstrap_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} \ 2>/dev/null | awk 'function ceil(x){return int(x)+(x>int(x))} function byte_to_kib(x){return x/1024} function mib_to_kib(x){return x*1024} @@ -517,7 +525,7 @@ _make_bootmode_uefi-x64.systemd-boot.esp() { mmd -i "${work_dir}/efiboot.img" ::/EFI ::/EFI/BOOT mcopy -i "${work_dir}/efiboot.img" \ - "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI + "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI mmd -i "${work_dir}/efiboot.img" ::/loader ::/loader/entries mcopy -i "${work_dir}/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/ @@ -529,9 +537,9 @@ _make_bootmode_uefi-x64.systemd-boot.esp() { done # shellx64.efi is picked up automatically when on / - if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then + if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then mcopy -i "${work_dir}/efiboot.img" \ - "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi + "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi fi # Copy kernel and initramfs @@ -664,6 +672,37 @@ _validate_requirements_airootfs_image_type_erofs() { fi } +_validate_requirements_buildmode_all() { + if ! command -v pacman &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating build mode '${_buildmode}': pacman is not available on this host. Install 'pacman'!" 0 + fi + if ! command -v find &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating build mode '${_buildmode}': find is not available on this host. Install 'findutils'!" 0 + fi + if ! command -v gzip &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating build mode '${_buildmode}': gzip is not available on this host. Install 'gzip'!" 0 + fi +} + +_validate_requirements_buildmode_bootstrap() { + _validate_requirements_buildmode_all + if ! command -v bsdtar &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating build mode '${_buildmode}': bsdtar is not available on this host. Install 'libarchive'!" 0 + fi +} + +_validate_requirements_buildmode_iso() { + _validate_requirements_buildmode_all + if ! command -v awk &> /dev/null; then + (( validation_error=validation_error+1 )) + _msg_error "Validating build mode '${_buildmode}': awk is not available on this host. Install 'awk'!" 0 + fi +} + # SYSLINUX El Torito _add_xorrisofs_options_bios.syslinux.eltorito() { xorrisofs_options+=( @@ -763,8 +802,24 @@ _add_xorrisofs_options_uefi-x64.systemd-boot.eltorito() { [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat') } +# Build bootstrap image +_build_bootstrap_image() { + local _bootstrap_parent + _bootstrap_parent="$(dirname -- "${pacstrap_dir}")" + + [[ -d "${out_dir}" ]] || install -d -- "${out_dir}" + + cd -- "${_bootstrap_parent}" + + _msg_info "Creating bootstrap image..." + bsdtar -cf - "root.${arch}" | gzip -cn9 > "${out_dir}/${image_name}" + _msg_info "Done!" + du -h -- "${out_dir}/${image_name}" + cd -- "${OLDPWD}" +} + # Build ISO -_build_iso() { +_build_iso_image() { local xorrisofs_options=() local bootmode @@ -789,10 +844,10 @@ _build_iso() { -publisher "${iso_publisher}" \ -preparer "prepared by ${app_name}" \ "${xorrisofs_options[@]}" \ - -output "${out_dir}/${img_name}" \ + -output "${out_dir}/${image_name}" \ "${isofs_dir}/" _msg_info "Done!" - du -h -- "${out_dir}/${img_name}" + du -h -- "${out_dir}/${image_name}" } # Read profile's values from profiledef.sh @@ -816,14 +871,23 @@ _read_profile() { packages="$(realpath -- "${packages}")" pacman_conf="$(realpath -- "${pacman_conf}")" + # Resolve paths of files that may reside in the profile's directory + if [[ -z "$bootstrap_packages" ]] && [[ -e "${profile}/bootstrap_packages.${arch}" ]]; then + bootstrap_packages="${profile}/bootstrap_packages.${arch}" + bootstrap_packages="$(realpath -- "${bootstrap_packages}")" + pacman_conf="$(realpath -- "${pacman_conf}")" + fi + cd -- "${OLDPWD}" fi } # Validate set options _validate_options() { - local validation_error=0 bootmode + local validation_error=0 bootmode _buildmode local pkg_list_from_file=() + local bootstrap_pkg_list_from_file=() + _msg_info "Validating options..." # Check if the package list file exists and read packages from it if [[ -e "${packages}" ]]; then @@ -835,8 +899,23 @@ _validate_options() { fi else (( validation_error=validation_error+1 )) - _msg_error "File '${packages}' does not exist." 0 + _msg_error "Packages file '${packages}' does not exist." 0 + fi + # Check if packages for the bootstrap image are specified + if [[ "${buildmodes[*]}" == *bootstrap* ]]; then + if [[ -e "${bootstrap_packages}" ]]; then + mapfile -t bootstrap_pkg_list_from_file < \ + <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${bootstrap_packages}") + bootstrap_pkg_list+=("${bootstrap_pkg_list_from_file[@]}") + if (( ${#bootstrap_pkg_list_from_file} < 1 )); then + (( validation_error=validation_error+1 )) + _msg_error "No package specified in '${bootstrap_packages}'." 0 + fi + else + (( validation_error=validation_error+1 )) + _msg_error "Bootstrap packages file '${bootstrap_packages}' does not exist." 0 + fi fi # Check if pacman configuration file exists @@ -844,6 +923,21 @@ _validate_options() { (( validation_error=validation_error+1 )) _msg_error "File '${pacman_conf}' does not exist." 0 fi + + # Check if the specified buildmodes are supported + for _buildmode in "${buildmodes[@]}"; do + if typeset -f "_build_buildmode_${_buildmode}" &> /dev/null; then + if typeset -f "_validate_requirements_buildmode_${_buildmode}" &> /dev/null; then + "_validate_requirements_buildmode_${_buildmode}" + else + _msg_warning "Function '_validate_requirements_buildmode_${_buildmode}' does not exist. Validating the requirements of '${_buildmode}' build mode will not be possible." + fi + else + (( validation_error=validation_error+1 )) + _msg_error "${_buildmode} is not a valid build mode!" 0 + fi + done + # Check if the specified bootmodes are supported for bootmode in "${bootmodes[@]}"; do if typeset -f "_make_bootmode_${bootmode}" &> /dev/null; then @@ -868,6 +962,7 @@ _validate_options() { (( 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 @@ -877,6 +972,10 @@ _validate_options() { # Set defaults and, if present, overrides from mkarchiso command line option parameters _set_overrides() { # Set variables that have command line overrides + [[ ! -v override_buildmodes ]] || buildmodes=("${override_buildmodes[@]}") + if (( "${#buildmodes[@]}" < 1 )); then + buildmodes+=('iso') + fi if [[ -v override_work_dir ]]; then work_dir="$override_work_dir" elif [[ -z "$work_dir" ]]; then @@ -896,6 +995,7 @@ _set_overrides() { fi pacman_conf="$(realpath -- "$pacman_conf")" [[ ! -v override_pkg_list ]] || pkg_list+=("${override_pkg_list[@]}") + # TODO: allow overriding bootstrap_pkg_list if [[ -v override_iso_label ]]; then iso_label="$override_iso_label" elif [[ -z "$iso_label" ]]; then @@ -927,7 +1027,6 @@ _set_overrides() { [[ -n "$arch" ]] || arch="$(uname -m)" [[ -n "$airootfs_image_type" ]] || airootfs_image_type="squashfs" [[ -n "$iso_name" ]] || iso_name="${app_name}" - [[ -n "$img_name" ]] || img_name="${iso_name}-${iso_version}-${arch}.iso" } _export_gpg_publickey() { @@ -935,43 +1034,84 @@ _export_gpg_publickey() { } _make_version() { - local osrelease - install -d -m 0755 -- "${isofs_dir}/${install_dir}" - _msg_info "Creating files with iso version..." - # Write version file to airootfs - rm -f -- "${airootfs_dir}/version" - printf '%s\n' "${iso_version}" > "${airootfs_dir}/version" - # Write version file to ISO 9660 - printf '%s\n' "${iso_version}" > "${isofs_dir}/${install_dir}/version" - # Write grubenv with version information to ISO 9660 - printf '%.1024s' "$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\n%s' \ - "${iso_name}" "${iso_version}" "$(printf '%0.1s' "#"{1..1024})")" \ - > "${isofs_dir}/${install_dir}/grubenv" + local _os_release + + _msg_info "Creating version files..." + # Write version file to system installation dir + rm -f -- "${pacstrap_dir}/version" + printf '%s\n' "${iso_version}" > "${pacstrap_dir}/version" + + if [[ "${buildmode}" == "iso" ]]; then + install -d -m 0755 -- "${isofs_dir}/${install_dir}" + # Write version file to ISO 9660 + printf '%s\n' "${iso_version}" > "${isofs_dir}/${install_dir}/version" + # Write grubenv with version information to ISO 9660 + printf '%.1024s' "$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\n%s' \ + "${iso_name}" "${iso_version}" "$(printf '%0.1s' "#"{1..1024})")" \ + > "${isofs_dir}/${install_dir}/grubenv" + fi + # Append IMAGE_ID & IMAGE_VERSION to os-release - osrelease="$(realpath -- "${airootfs_dir}/etc/os-release")" - if [[ ! -e "${airootfs_dir}/etc/os-release" && -e "${airootfs_dir}/usr/lib/os-release" ]]; then - osrelease="$(realpath -- "${airootfs_dir}/usr/lib/os-release")" + _os_release="$(realpath -- "${pacstrap_dir}/etc/os-release")" + if [[ ! -e "${pacstrap_dir}/etc/os-release" && -e "${pacstrap_dir}/usr/lib/os-release" ]]; then + _os_release="$(realpath -- "${pacstrap_dir}/usr/lib/os-release")" fi - if [[ "${osrelease}" != "${airootfs_dir}"* ]]; then - _msg_warning "os-release file '${osrelease}' is outside of valid path." + if [[ "${_os_release}" != "${pacstrap_dir}"* ]]; then + _msg_warning "os-release file '${_os_release}' is outside of valid path." else - [[ ! -e "${osrelease}" ]] || sed -i '/^IMAGE_ID=/d;/^IMAGE_VERSION=/d' "${osrelease}" - printf 'IMAGE_ID=%s\nIMAGE_VERSION=%s\n' "${iso_name}" "${iso_version}" >> "${osrelease}" + [[ ! -e "${_os_release}" ]] || sed -i '/^IMAGE_ID=/d;/^IMAGE_VERSION=/d' "${_os_release}" + printf 'IMAGE_ID=%s\nIMAGE_VERSION=%s\n' "${iso_name}" "${iso_version}" >> "${_os_release}" fi _msg_info "Done!" } _make_pkglist() { - install -d -m 0755 -- "${isofs_dir}/${install_dir}" _msg_info "Creating a list of installed packages on live-enviroment..." - pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt" + case "${buildmode}" in + "bootstrap") + pacman -Q --sysroot "${pacstrap_dir}" > "${pacstrap_dir}/pkglist.${arch}.txt" + ;; + "iso") + install -d -m 0755 -- "${isofs_dir}/${install_dir}" + pacman -Q --sysroot "${pacstrap_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt" + ;; + esac _msg_info "Done!" } -_build_profile() { +_build_buildmode_bootstrap() { + local image_name="${iso_name}-bootstrap-${iso_version}-${arch}.tar.gz" + local run_once_mode="${buildmode}" + local buildmode_packages="${bootstrap_packages}" + local buildmode_pkg_list=() + + # Set the package list to use + buildmode_pkg_list=("${bootstrap_pkg_list[@]}") + # Set up essential directory paths + pacstrap_dir="${work_dir}/${arch}/bootstrap/root.${arch}" + [[ -d "${work_dir}" ]] || install -d -- "${work_dir}" + install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}" + + [[ "${quiet}" == "y" ]] || _show_config + _run_once _make_pacman_conf + _run_once _make_packages + _run_once _make_version + _run_once _make_pkglist + _run_once _cleanup_pacstrap_dir + _run_once _build_bootstrap_image +} + +_build_buildmode_iso() { + local image_name="${iso_name}-${iso_version}-${arch}.iso" + local run_once_mode="${buildmode}" + local buildmode_packages="${packages}" + local buildmode_pkg_list=() # Set up essential directory paths - airootfs_dir="${work_dir}/${arch}/airootfs" + pacstrap_dir="${work_dir}/${arch}/airootfs" isofs_dir="${work_dir}/iso" + # Set the package list to use + buildmode_pkg_list=("${pkg_list[@]}") + # Create working directory [[ -d "${work_dir}" ]] || install -d -- "${work_dir}" # Write build date to file or if the file exists, read it from there @@ -990,12 +1130,22 @@ _build_profile() { _run_once _make_customize_airootfs _run_once _make_pkglist _make_bootmodes - _run_once _cleanup_airootfs + _run_once _cleanup_pacstrap_dir _run_once _prepare_airootfs_image - _run_once _build_iso + _run_once _build_iso_image +} + +# build all buildmodes +_build() { + local buildmode + local run_once_mode="build" + + for buildmode in "${buildmodes[@]}"; do + _run_once "_build_buildmode_${buildmode}" + done } -while getopts 'p:C:L:P:A:D:w:o:g:vh?' arg; do +while getopts 'p:C:L:P:A:D:w:m:o:g:vh?' arg; do case "${arg}" in p) read -r -a override_pkg_list <<< "${OPTARG}" ;; C) override_pacman_conf="${OPTARG}" ;; @@ -1004,6 +1154,7 @@ while getopts 'p:C:L:P:A:D:w:o:g:vh?' arg; do A) override_iso_application="${OPTARG}" ;; D) override_install_dir="${OPTARG}" ;; w) override_work_dir="${OPTARG}" ;; + m) read -r -a override_buildmodes <<< "${OPTARG}" ;; o) override_out_dir="${OPTARG}" ;; g) override_gpg_key="${OPTARG}" ;; v) override_quiet="n" ;; @@ -1032,6 +1183,6 @@ profile="$(realpath -- "${1}")" _read_profile _set_overrides _validate_options -_build_profile +_build # vim:ts=4:sw=4:et: -- cgit v1.2.3-70-g09d2