diff options
Diffstat (limited to 'parabolaiso/mkparabolaiso')
-rwxr-xr-x | parabolaiso/mkparabolaiso | 810 |
1 files changed, 631 insertions, 179 deletions
diff --git a/parabolaiso/mkparabolaiso b/parabolaiso/mkparabolaiso index f7fae78..51251f5 100755 --- a/parabolaiso/mkparabolaiso +++ b/parabolaiso/mkparabolaiso @@ -4,29 +4,49 @@ set -e -u -export LANG=C +# Control the environment +umask 0022 +export LANG="C" +export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-"$(date +%s)"}" -app_name=${0##*/} -arch=${arch:-$(uname -m)} +# mkparabolaiso defaults +app_name="${0##*/}" pkg_list=() run_cmd="" quiet="y" -pacman_conf="/etc/pacman.conf" -iso_label="PARA_$(date +%Y%m)" -iso_publisher="Parabola GNU/Linux-libre <https://www.parabola.nu>" -iso_application="Parabola GNU/Linux-libre Live/Rescue CD" -install_dir="parabola" work_dir="work" out_dir="out" +img_name="${app_name}.iso" sfs_mode="sfs" sfs_comp="xz" -gpg_key= +gpg_key="" + +# profile defaults +profile="" +iso_name="${app_name}" +iso_label="${app_name^^}" +iso_publisher="${app_name}" +iso_application="${app_name} iso" +iso_version="" +install_dir="${app_name}" +arch="${arch:-$(uname -m)}" +pacman_conf="/etc/pacman.conf" +bootmodes=() + # Show an INFO message # $1: message string _msg_info() { local _msg="${1}" - echo "[mkparabolaiso] INFO: ${_msg}" + [[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}" + +} + +# Show a WARNING message +# $1: message string +_msg_warning() { + local _msg="${1}" + printf '\n[%s] WARNING: %s\n\n' "${app_name}" "${_msg}" >&2 } # Show an ERROR message then exit with status @@ -35,83 +55,73 @@ _msg_info() { _msg_error() { local _msg="${1}" local _error=${2} - echo - echo "[mkparabolaiso] ERROR: ${_msg}" - echo - if [[ ${_error} -gt 0 ]]; then - "exit ${_error}" + printf '\n[%s] ERROR: %s\n\n' "${app_name}" "${_msg}" >&2 + if (( _error > 0 )); then + exit "${_error}" fi } _chroot_init() { - mkdir -p ${work_dir}/airootfs + mkdir -p -- "${airootfs_dir}" _pacman base syslinux } _chroot_run() { - eval arch-chroot ${work_dir}/airootfs "${run_cmd}" + eval -- arch-chroot "${airootfs_dir}" "${run_cmd}" } _mount_airootfs() { trap "_umount_airootfs" EXIT HUP INT TERM - mkdir -p "${work_dir}/mnt/airootfs" - _msg_info "Mounting '${work_dir}/airootfs.img' on '${work_dir}/mnt/airootfs'" - mount "${work_dir}/airootfs.img" "${work_dir}/mnt/airootfs" + mkdir -p -- "${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 "Done!" } _umount_airootfs() { _msg_info "Unmounting '${work_dir}/mnt/airootfs'" - umount -d "${work_dir}/mnt/airootfs" + umount -d -- "${work_dir}/mnt/airootfs" _msg_info "Done!" - rmdir "${work_dir}/mnt/airootfs" + rmdir -- "${work_dir}/mnt/airootfs" trap - EXIT HUP INT TERM } # Show help usage, with an exit status. # $1: exit status number. -_usage () -{ - echo "usage ${app_name} [options] command <command options>" - echo " general options:" - echo " -p PACKAGE(S) Package(s) to install, can be used multiple times" - echo " -r <command> Run <command> inside airootfs" - echo " -C <file> Config file for pacman." - echo " Default: '${pacman_conf}'" - echo " -L <label> Set a label for the disk" - echo " Default: '${iso_label}'" - echo " -P <publisher> Set a publisher for the disk" - echo " Default: '${iso_publisher}'" - echo " -A <application> Set an application name for the disk" - echo " Default: '${iso_application}'" - echo " -D <install_dir> Set an install_dir. All files will by located here." - echo " Default: '${install_dir}'" - echo " NOTE: Max 8 characters, use only [a-z0-9]" - echo " -w <work_dir> Set the working directory" - echo " Default: '${work_dir}'" - echo " -o <out_dir> Set the output directory" - echo " Default: '${out_dir}'" - echo " -s <sfs_mode> Set SquashFS image mode (img or sfs)" - echo " img: prepare airootfs.sfs for dm-snapshot usage" - echo " sfs: prepare airootfs.sfs for overlayfs usage" - echo " Default: ${sfs_mode}" - echo " -c <comp_type> Set SquashFS compression type (gzip, lzma, lzo, xz, zstd)" - echo " Default: '${sfs_comp}'" - echo " -v Enable verbose output" - echo " -h This message" - echo " commands:" - echo " init" - echo " Make base layout and install base group" - echo " install" - echo " Install all specified packages (-p)" - echo " run" - echo " run command specified by -r" - echo " prepare" - echo " build all images" - echo " pkglist" - echo " make a pkglist.txt of packages installed on airootfs" - echo " iso <image name>" - echo " build an iso image from the working dir" +_usage () { + IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true +usage ${app_name} [options] command <command options> + general options: + -B <profile_dir> Directory of the parabolaiso profile to build + -p PACKAGE(S) Package(s) to install, can be used multiple times + -C <file> Config file for pacman. + Default: '${pacman_conf}' + -L <label> Set a label for the disk + Default: '${iso_label}' + -P <publisher> Set a publisher for the disk + Default: '${iso_publisher}' + -A <application> Set an application name for the disk + Default: '${iso_application}' + -D <install_dir> Set an install_dir. All files will by located here. + Default: '${install_dir}' + NOTE: Max 8 characters, use only [a-z0-9] + -w <work_dir> Set the working directory + Default: '${work_dir}' + -o <out_dir> Set the output directory + Default: '${out_dir}' + -s <sfs_mode> Set SquashFS image mode (img or sfs) + img: prepare airootfs.sfs for dm-snapshot usage + sfs: prepare airootfs.sfs for overlayfs usage + Default: '${sfs_mode}' + -c <comp_type> Set SquashFS compression type (gzip, lzma, lzo, xz, zstd) + Default: '${sfs_comp}' + -v Enable verbose output + -h This message + commands: + build_profile + build an iso image from a profile +ENDUSAGETEXT + printf '%s\n' "${usagetext}" exit "${1}" } @@ -119,7 +129,7 @@ _usage () # $1: init | install | run | prepare | iso _show_config () { local _mode="$1" - echo + printf '\n' _msg_info "Configuration settings" _msg_info " Command: ${command_name}" _msg_info " Architecture: ${arch}" @@ -147,18 +157,17 @@ _show_config () { _msg_info " Disk application: ${iso_application}" ;; esac - echo + printf '\n' } # Install desired packages to airootfs -_pacman () -{ - _msg_info "Installing packages to '${work_dir}/airootfs/'..." +_pacman () { + _msg_info "Installing packages to '${airootfs_dir}/'..." if [[ "${quiet}" = "y" ]]; then - pacstrap -C "${pacman_conf}" -c -G -M "${work_dir}/airootfs" "$@" &> /dev/null + pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" --arch "${arch}" "$@" &> /dev/null else - pacstrap -C "${pacman_conf}" -c -G -M "${work_dir}/airootfs" "$@" + pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" --arch "${arch}" "$@" fi _msg_info "Packages installed successfully!" @@ -168,86 +177,84 @@ _pacman () _cleanup () { _msg_info "Cleaning up what we can on airootfs..." - # Delete initcpio image(s) - if [[ -d "${work_dir}/airootfs/boot" ]]; then - find "${work_dir}/airootfs/boot" -type f -name '*.img' -delete - fi - # Delete kernel(s) - if [[ -d "${work_dir}/airootfs/boot" ]]; then - find "${work_dir}/airootfs/boot" -type f -name 'vmlinuz*' -delete + # Delete all files in /boot + if [[ -d "${airootfs_dir}/boot" ]]; then + find "${airootfs_dir}/boot" -mindepth 1 -delete fi # Delete pacman database sync cache files (*.tar.gz) - if [[ -d "${work_dir}/airootfs/var/lib/pacman" ]]; then - find "${work_dir}/airootfs/var/lib/pacman" -maxdepth 1 -type f -delete + if [[ -d "${airootfs_dir}/var/lib/pacman" ]]; then + find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete fi # Delete pacman database sync cache - if [[ -d "${work_dir}/airootfs/var/lib/pacman/sync" ]]; then - find "${work_dir}/airootfs/var/lib/pacman/sync" -delete + if [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]]; then + find "${airootfs_dir}/var/lib/pacman/sync" -delete fi # Delete pacman package cache - if [[ -d "${work_dir}/airootfs/var/cache/pacman/pkg" ]]; then - find "${work_dir}/airootfs/var/cache/pacman/pkg" -type f -delete + if [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]]; then + find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete fi # Delete all log files, keeps empty dirs. - if [[ -d "${work_dir}/airootfs/var/log" ]]; then - find "${work_dir}/airootfs/var/log" -type f -delete + if [[ -d "${airootfs_dir}/var/log" ]]; then + find "${airootfs_dir}/var/log" -type f -delete fi # Delete all temporary files and dirs - if [[ -d "${work_dir}/airootfs/var/tmp" ]]; then - find "${work_dir}/airootfs/var/tmp" -mindepth 1 -delete + if [[ -d "${airootfs_dir}/var/tmp" ]]; then + find "${airootfs_dir}/var/tmp" -mindepth 1 -delete fi # Delete package pacman related files. - find "${work_dir}" \( -name "*.pacnew" -o -name "*.pacsave" -o -name "*.pacorig" \) -delete + find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete _msg_info "Done!" + # Create an empty /etc/machine-id + printf '' > "${airootfs_dir}/etc/machine-id" } # Makes a ext4 filesystem inside a SquashFS from a source directory. _mkairootfs_img () { - if [[ ! -e "${work_dir}/airootfs" ]]; then - _msg_error "The path '${work_dir}/airootfs' does not exist" 1 + if [[ ! -e "${airootfs_dir}" ]]; then + _msg_error "The path '${airootfs_dir}' does not exist" 1 fi _msg_info "Creating ext4 image of 32GiB..." - truncate -s 32G "${work_dir}/airootfs.img" - local _qflag="" + truncate -s 32G -- "${airootfs_dir}.img" if [[ "${quiet}" == "y" ]]; then - _qflag="-q" + mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" + else + mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" fi - mkfs.ext4 ${_qflag} -O ^has_journal,^resize_inode -E lazy_itable_init=0 -m 0 -F "${work_dir}/airootfs.img" - tune2fs -c 0 -i 0 "${work_dir}/airootfs.img" &> /dev/null + tune2fs -c 0 -i 0 -- "${airootfs_dir}.img" &> /dev/null _msg_info "Done!" _mount_airootfs - _msg_info "Copying '${work_dir}/airootfs/' to '${work_dir}/mnt/airootfs/'..." - cp -aT "${work_dir}/airootfs/" "${work_dir}/mnt/airootfs/" - chown root:root "${work_dir}/mnt/airootfs/" + _msg_info "Copying '${airootfs_dir}/' to '${work_dir}/mnt/airootfs/'..." + cp -aT -- "${airootfs_dir}/" "${work_dir}/mnt/airootfs/" + chown root:root -- "${work_dir}/mnt/airootfs/" _msg_info "Done!" _umount_airootfs - mkdir -p "${work_dir}/iso/${install_dir}/${arch}" + mkdir -p -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating SquashFS image, this may take some time..." if [[ "${quiet}" = "y" ]]; then - mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \ + mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \ -comp "${sfs_comp}" -no-progress &> /dev/null else - mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \ + mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \ -comp "${sfs_comp}" fi _msg_info "Done!" - rm "${work_dir}/airootfs.img" + rm -- "${airootfs_dir}.img" } # Makes a SquashFS filesystem from a source directory. _mkairootfs_sfs () { - if [[ ! -e "${work_dir}/airootfs" ]]; then - _msg_error "The path '${work_dir}/airootfs' does not exist" 1 + if [[ ! -e "${airootfs_dir}" ]]; then + _msg_error "The path '${airootfs_dir}' does not exist" 1 fi - mkdir -p "${work_dir}/iso/${install_dir}/${arch}" + mkdir -p -- "${isofs_dir}/${install_dir}/${arch}" _msg_info "Creating SquashFS image, this may take some time..." if [[ "${quiet}" = "y" ]]; then - mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \ + mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \ -comp "${sfs_comp}" -no-progress &> /dev/null else - mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \ + mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \ -comp "${sfs_comp}" fi _msg_info "Done!" @@ -255,44 +262,340 @@ _mkairootfs_sfs () { _mkchecksum () { _msg_info "Creating checksum file for self-test..." - cd "${work_dir}/iso/${install_dir}/${arch}" + cd -- "${isofs_dir}/${install_dir}/${arch}" sha512sum airootfs.sfs > airootfs.sha512 - cd "${OLDPWD}" + cd -- "${OLDPWD}" _msg_info "Done!" } _mksignature () { _msg_info "Creating signature file..." - cd "${work_dir}/iso/${install_dir}/${arch}" + cd -- "${isofs_dir}/${install_dir}/${arch}" gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs - cd "${OLDPWD}" + cd -- "${OLDPWD}" _msg_info "Done!" } -command_pkglist () { - _show_config pkglist +# Helper function to run functions only one time. +_run_once() { + if [[ ! -e "${work_dir}/build.${1}_${arch}" ]]; then + "$1" + touch "${work_dir}/build.${1}_${arch}" + fi +} - _msg_info "Creating a list of installed packages on live-enviroment..." - pacman -Q --sysroot "${work_dir}/airootfs" > \ - "${work_dir}/iso/${install_dir}/pkglist.${arch}.txt" - _msg_info "Done!" +# Set up custom pacman.conf with current cache directories. +_make_pacman_conf() { + local _cache_dirs + _cache_dirs="$(pacman-conf CacheDir)" + sed -r "s|^#?\\s*CacheDir.+|CacheDir = ${_cache_dirs[*]//$'\n'/ }|g" \ + "${pacman_conf}" > "${work_dir}/pacman.conf" +} + +# Prepare working directory and copy custom airootfs files (airootfs) +_make_custom_airootfs() { + mkdir -m 755 -- "${airootfs_dir}" + + local passwd=() + if [[ -d "${profile}/airootfs" ]]; then + cp -af --no-preserve=ownership -- "${profile}/airootfs/." "${airootfs_dir}" + + [[ -e "${airootfs_dir}/etc/shadow" ]] && chmod -f 0400 -- "${airootfs_dir}/etc/shadow" + [[ -e "${airootfs_dir}/etc/gshadow" ]] && chmod -f 0400 -- "${airootfs_dir}/etc/gshadow" + + # Set up user home directories and permissions + if [[ -e "${airootfs_dir}/etc/passwd" ]]; then + while IFS=':' read -a passwd -r; do + [[ "${passwd[5]}" == '/' ]] && continue + [[ -z "${passwd[5]}" ]] && continue + + if [[ -d "${airootfs_dir}${passwd[5]}" ]]; then + chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}" + chmod -f 0750 -- "${airootfs_dir}${passwd[5]}" + else + install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${airootfs_dir}${passwd[5]}" + fi + done < "${airootfs_dir}/etc/passwd" + fi + fi +} +# Packages (airootfs) +_make_packages() { + 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 [[ -n "${gpg_key}" ]]; then + exec {parabolaiso_GNUPG_FD}<&- + unset parabolaiso_GNUPG_FD + fi } -# Create an ISO9660 filesystem from "iso" directory. -command_iso () { - local _iso_efi_boot_args=() +# Customize installation (airootfs) +_make_customize_airootfs() { + local passwd=() + if [[ -e "${profile}/airootfs/etc/passwd" ]]; then + while IFS=':' read -a passwd -r; do + if [[ "${passwd[5]}" == '/' ]]; then + continue + fi + cp -RdT --preserve=mode,timestamps,links -- "${airootfs_dir}/etc/skel" "${airootfs_dir}${passwd[5]}" + chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}" + + done < "${profile}/airootfs/etc/passwd" + fi + + if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then + _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" + local work_dir="${work_dir}/${arch}" + command_run + rm -- "${airootfs_dir}/root/customize_airootfs.sh" + fi +} + +# Set up boot loaders +_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 + done +} + +# Prepare kernel/initramfs ${install_dir}/boot/ +_make_boot_on_iso() { + mkdir -p -- "${isofs_dir}/${install_dir}/boot/${arch}" + install -m 0644 -- "${airootfs_dir}/boot/parabolaiso.img" "${isofs_dir}/${install_dir}/boot/${arch}/" + install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-linux-libre" "${isofs_dir}/${install_dir}/boot/${arch}/" +} - if [[ ! -f "${work_dir}/iso/isolinux/isolinux.bin" ]]; then - _msg_error "The file '${work_dir}/iso/isolinux/isolinux.bin' does not exist." 1 +# Prepare /${install_dir}/boot/syslinux +_make_boot_bios.syslinux.mbr() { + mkdir -p "${isofs_dir}/${install_dir}/boot/syslinux" + for _cfg in "${profile}/syslinux/"*.cfg; do + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${_cfg}" > "${isofs_dir}/${install_dir}/boot/syslinux/${_cfg##*/}" + done + if [[ -e "${profile}/syslinux/splash.png" ]]; then + install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/${install_dir}/boot/syslinux/" + fi + install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/${install_dir}/boot/syslinux/" + 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/" + + if [[ "${arch}" == "dual" ]]; then + for arch in i686 x86_64; do + local airootfs_dir="${work_dir}/${arch}/airootfs" + _make_boot_on_iso + done + else + _make_boot_on_iso fi - if [[ ! -f "${work_dir}/iso/isolinux/isohdpfx.bin" ]]; then - _msg_error "The file '${work_dir}/iso/isolinux/isohdpfx.bin' does not exist." 1 + _uname_r=$(file -b "${isofs_dir}/${install_dir}/boot/${arch}/vmlinuz-linux-libre" | awk 'f{print;f=0} /version/{f=1}' RS=' ') + + mkdir -p "${isofs_dir}/${install_dir}/boot/syslinux/hdt" + gzip -c -9 "${airootfs_dir}/usr/share/hwdata/pci.ids" > \ + "${isofs_dir}/${install_dir}/boot/syslinux/hdt/pciids.gz" + gzip -c -9 "${airootfs_dir}/usr/lib/modules/${_uname_r}/modules.alias" > \ + "${isofs_dir}/${install_dir}/boot/syslinux/hdt/modalias.gz" + + # Add other aditional/extra files to ${install_dir}/boot/ + if [[ -e "${airootfs_dir}/boot/memtest86+/memtest.bin" ]]; then + # rename for PXE: https://wiki.parabola.nu/index.php/Syslinux#Using_memtest + install -m 0644 -- "${airootfs_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest" + mkdir -p "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/" + install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \ + "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/" fi +} - # If exists, add an EFI "El Torito" boot image (FAT filesystem) to ISO-9660 image. - if [[ -f "${work_dir}/iso/EFI/parabolaiso/efiboot.img" ]]; then - _iso_efi_boot_args+=( +# Prepare /isolinux +_make_boot_bios.syslinux.eltorito() { + mkdir -p "${isofs_dir}/isolinux" + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${profile}/isolinux/isolinux.cfg" > "${isofs_dir}/isolinux/isolinux.cfg" + install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/isolinux/" + install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/isolinux/" + 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 +} + +# Prepare /EFI on ISO-9660 +_make_efi() { + mkdir -p "${isofs_dir}/EFI/BOOT" + install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ + "${isofs_dir}/EFI/BOOT/BOOTx64.EFI" + + mkdir -p "${isofs_dir}/loader/entries" + install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/" + + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${profile}/efiboot/loader/entries/parabolaiso-x86_64-usb.conf" > \ + "${isofs_dir}/loader/entries/parabolaiso-x86_64.conf" + + # 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" + fi +} + +_make_refind_efi() { + mkdir -p "${isofs_dir}/EFI/boot/entries" + install -m 0644 -- "${airootfs_dir}/usr/share/refind/refind_x64.efi" \ + "${isofs_dir}/EFI/boot/bootx64.efi" + + install -m 0644 -- "${profile}/efiboot/EFI/boot/refind.conf" "${isofs_dir}/EFI/boot/" + + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${profile}/efiboot/EFI/boot/entries/parabolaiso-x86_64-usb.conf" > \ + "${isofs_dir}/EFI/boot/entries/parabolaiso-x86_64.conf" + + # 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" + fi +} + +# Prepare kernel/initramfs on efiboot.img +_make_boot_on_fat() { + mkdir -p "${work_dir}/efiboot/EFI/parabolaiso" + install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-linux-libre" "${work_dir}/efiboot/EFI/parabolaiso/" + install -m 0644 -- "${isofs_dir}/${install_dir}/boot/${arch}/parabolaiso.img" "${work_dir}/efiboot/EFI/parabolaiso/" +} + +# Prepare efiboot.img::/EFI for EFI boot mode +_make_boot_uefi-x64.systemd-boot.esp() { + mkdir -p "${isofs_dir}/EFI/parabolaiso" + mkfs.fat -C -n parabolaiso_EFI "${isofs_dir}/EFI/parabolaiso/efiboot.img" 65536 + + mkdir -p "${work_dir}/efiboot" + mount "${isofs_dir}/EFI/parabolaiso/efiboot.img" "${work_dir}/efiboot" + + mkdir -p "${work_dir}/efiboot/EFI/BOOT" + install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \ + "${work_dir}/efiboot/EFI/BOOT/BOOTx64.EFI" + + mkdir -p "${work_dir}/efiboot/loader/entries" + install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${work_dir}/efiboot/loader/" + + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${profile}/efiboot/loader/entries/parabolaiso-x86_64-cd.conf" > \ + "${work_dir}/efiboot/loader/entries/parabolaiso-x86_64.conf" + + # 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" "${work_dir}/efiboot/shellx64.efi" + fi + + # Copy kernel and initramfs + _make_boot_on_fat + + umount -d "${work_dir}/efiboot" +} + +_make_boot_uefi-x64.refind.esp() { + mkdir -p "${isofs_dir}/EFI/parabolaiso" + mkfs.fat -C -n parabolaiso_EFI "${isofs_dir}/EFI/parabolaiso/efiboot.img" 65536 + + mkdir -p "${work_dir}/efiboot" + mount "${isofs_dir}/EFI/parabolaiso/efiboot.img" "${work_dir}/efiboot" + + mkdir -p "${work_dir}/efiboot/EFI/boot/entries" + install -m 0644 -- "${airootfs_dir}/usr/share/refind/refind_x64.efi" \ + "${work_dir}/efiboot/EFI/boot/bootx64.efi" + + install -m 0644 -- "${profile}/efiboot/EFI/boot/refind.conf" "${work_dir}/efiboot/EFI/boot/" + + sed "s|%PARABOLAISO_LABEL%|${iso_label}|g; + s|%INSTALL_DIR%|${install_dir}|g; + s|%ARCH%|${arch}|g" \ + "${profile}/efiboot/EFI/boot/entries/parabolaiso-x86_64-cd.conf" > \ + "${work_dir}/efiboot/EFI/boot/entries/parabolaiso-x86_64.conf" + + # 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" "${work_dir}/efiboot/shellx64.efi" + fi + + # Copy kernel and initramfs + _make_boot_on_fat + + umount -d "${work_dir}/efiboot" +} + +# 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_boot_uefi-x64.refind.eltorito() { + _run_once _make_boot_uefi-x64.refind.esp + # Set up /EFI on ISO-9660 + _run_once _make_refind_efi +} + +# Build airootfs filesystem image +_make_prepare() { + if [[ "${sfs_mode}" == "sfs" ]]; then + _mkairootfs_sfs + else + _mkairootfs_img + fi + _mkchecksum + if [[ "${gpg_key}" ]]; then + _mksignature + fi +} + +# Build ISO +_make_iso() { + local xorrisofs_options=() + + if [[ "${quiet}" == "y" ]]; then + xorrisofs_options+=('-quiet') + fi + # shellcheck disable=SC2076 + if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.' ]]; then + if [[ ! -f "${isofs_dir}/isolinux/isolinux.bin" ]]; then + _msg_error "The file '${isofs_dir}/isolinux/isolinux.bin' does not exist." 1 + fi + if [[ ! -f "${isofs_dir}/isolinux/isohdpfx.bin" ]]; then + _msg_error "The file '${isofs_dir}/isolinux/isohdpfx.bin' does not exist." 1 + fi + xorrisofs_options+=( + '-eltorito-boot' 'isolinux/isolinux.bin' + '-eltorito-catalog' 'isolinux/boot.cat' + '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table' + '-isohybrid-mbr' "${isofs_dir}/isolinux/isohdpfx.bin" + ) + fi + # shellcheck disable=SC2076 + if [[ " ${bootmodes[*]} " =~ ( uefi-x64.refind.| uefi-x64.systemd-boot.) ]]; then + xorrisofs_options+=( '-eltorito-alt-boot' '-e' 'EFI/parabolaiso/efiboot.img' '-no-emul-boot' @@ -300,45 +603,137 @@ command_iso () { ) fi - _show_config iso - - mkdir -p "${out_dir}" _msg_info "Creating ISO image..." - local _qflag="" - if [[ "${quiet}" == "y" ]]; then - xorriso -as mkisofs -quiet \ + xorriso -as mkisofs \ -iso-level 3 \ -full-iso9660-filenames \ + -joliet \ + -joliet-long \ -rational-rock \ -volid "${iso_label}" \ -appid "${iso_application}" \ -publisher "${iso_publisher}" \ - -preparer "prepared by mkparabolaiso" \ - -eltorito-boot isolinux/isolinux.bin \ - -eltorito-catalog isolinux/boot.cat \ - -no-emul-boot -boot-load-size 4 -boot-info-table \ - -isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \ - "${_iso_efi_boot_args[@]}" \ + -preparer "prepared by ${app_name}" \ + "${xorrisofs_options[@]}" \ -output "${out_dir}/${img_name}" \ - "${work_dir}/iso/" + "${isofs_dir}/" + _msg_info "Done! | $(du -h -- "${out_dir}/${img_name}")" +} + +# Read profile's values from profiledef.sh +_read_profile () { + if [[ -z "${profile}" ]]; then + _msg_error "No profile specified!" 1 + fi + if [[ ! -d "${profile}" ]]; then + _msg_error "Profile '${profile}' does not exist!" 1 + elif [[ ! -e "${profile}/profiledef.sh" ]]; then + _msg_error "Profile '${profile}' is missing 'profiledef.sh'!" 1 else - xorriso -as mkisofs \ - -iso-level 3 \ - -full-iso9660-filenames \ - -rational-rock \ - -volid "${iso_label}" \ - -appid "${iso_application}" \ - -publisher "${iso_publisher}" \ - -preparer "prepared by mkparabolaiso" \ - -eltorito-boot isolinux/isolinux.bin \ - -eltorito-catalog isolinux/boot.cat \ - -no-emul-boot -boot-load-size 4 -boot-info-table \ - -isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \ - "${_iso_efi_boot_args[@]}" \ - -output "${out_dir}/${img_name}" \ - "${work_dir}/iso/" + # Source profile's variables + # shellcheck source=configs/releng/profiledef.sh + . "${profile}/profiledef.sh" + cd -- "${profile}" + + if [[ "${arch}" == "dual" ]]; then + # Resolve paths + packages="$(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}") + if (( ${#pkg_list[@]} == 0 )); then + _msg_error "'${pkgs}' does not list any packages!" 1 + fi + 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}") + if (( ${#pkg_list[@]} == 0 )); then + _msg_error "'${packages}' does not list any packages!" 1 + fi + fi + + cd -- "${OLDPWD}" + fi +} + +_set_up_directories() { + local directory + for directory in "${work_dir}" "${out_dir}" "${work_dir}/${arch}" "${isofs_dir}" "${isofs_dir}/${install_dir}"; do + [[ -d "${directory}" ]] || mkdir -m 0755 -- "${directory}" + done +} + +_print_settings() { + _msg_info "${app_name} configuration settings" + _msg_info " Command: ${command_name}" + _msg_info " Working directory: ${work_dir}" + _msg_info " Output directory: ${out_dir}" + _msg_info " GPG key: ${gpg_key:-None}" + _msg_info "Profile configuration settings" + _msg_info " Profile: ${profile}" + _msg_info " Architecture: ${arch}" + _msg_info " Image name: ${img_name}" + _msg_info " Disk label: ${iso_label}" + _msg_info " Disk publisher: ${iso_publisher}" + _msg_info " Disk application: ${iso_application}" + _msg_info " Installation directory: ${install_dir}" + _msg_info " Pacman config file: ${pacman_conf}" + _msg_info " Packages: ${pkg_list[*]}" + if [[ "${arch}" == "dual" ]]; then + _msg_info " Packages (x86_64): ${pkg_list_x86_64[*]:-None}" + _msg_info " Packages (i686): ${pkg_list_i686[*]:-None}" + fi + _msg_info " Boot modes: ${bootmodes[*]}" +} + +_export_gpg_publickey() { + if [[ -n "${gpg_key}" ]]; then + gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}" fi - _msg_info "Done! | $(ls -sh "${out_dir}/${img_name}")" +} + + +_make_pkglist() { + _msg_info "Creating a list of installed packages on live-enviroment..." + pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt" + _msg_info "Done!" +} + +command_pkglist () { + _show_config pkglist + _make_pkglist +} + +# Create an ISO9660 filesystem from "iso" directory. +command_iso () { + bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito') + + # If exists, add an EFI "El Torito" boot image (FAT filesystem) to ISO-9660 image. + if [[ -f "${isofs_dir}/EFI/parabolaiso/efiboot.img" ]]; then + if [[ -e "${airootfs_dir}/usr/share/refind/refind_x64.efi" ]]; then + bootmodes+=('uefi-x64.refind.esp' 'uefi-x64.refind.eltorito') + else + bootmodes+=('uefi-x64.systemd-boot.esp' 'uefi-x64.systemd-boot.eltorito') + fi + fi + + _show_config iso + mkdir -p -- "${out_dir}" + _make_iso } # create airootfs.sfs filesystem, and push it in "iso" directory. @@ -346,15 +741,7 @@ command_prepare () { _show_config prepare _cleanup - if [[ "${sfs_mode}" == "sfs" ]]; then - _mkairootfs_sfs - else - _mkairootfs_img - fi - _mkchecksum - if [[ "${gpg_key}" ]]; then - _mksignature - fi + _make_prepare } # Install packages on airootfs. @@ -364,14 +751,14 @@ command_install () { _msg_error "Pacman config file '${pacman_conf}' does not exist" 1 fi - if [[ "${#pkg_list[@]}" -eq 0 ]]; then + if (( ${#pkg_list[@]} == 0 )); then _msg_error "Packages must be specified" 0 _usage 1 fi _show_config install - _pacman "${pkg_list[@]}" + _make_packages } command_init() { @@ -384,26 +771,74 @@ command_run() { _chroot_run } -if [[ "${EUID}" -ne 0 ]]; then - _msg_error "This script must be run as root." 1 -fi - -umask 0022 +command_build_profile() { + # Set up essential directory paths + airootfs_dir="${work_dir}/${arch}/airootfs" + isofs_dir="${work_dir}/iso" + # Set ISO file name + img_name="${iso_name}-${iso_version}-${arch}.iso" + + if [[ "${arch}" == "dual" ]]; then + _print_settings + for arch in i686 x86_64; do + _run_once _set_up_directories + done + _run_once _make_pacman_conf + _run_once _export_gpg_publickey + for arch in i686 x86_64; do + airootfs_dir="${work_dir}/${arch}/airootfs" + _run_once _make_custom_airootfs + _run_once _make_packages + done + for arch in i686 x86_64; do + airootfs_dir="${work_dir}/${arch}/airootfs" + _run_once _make_customize_airootfs + done + for arch in i686 x86_64; do + _run_once _make_pkglist + done + arch="dual" + _make_bootmodes + _run_once _cleanup + for arch in i686 x86_64; do + airootfs_dir="${work_dir}/${arch}/airootfs" + _run_once _make_prepare + done + _run_once _make_iso + else + _print_settings + _run_once _set_up_directories + _run_once _make_pacman_conf + _run_once _export_gpg_publickey + _run_once _make_custom_airootfs + _run_once _make_packages + _run_once _make_customize_airootfs + _run_once _make_pkglist + _make_bootmodes + _run_once _cleanup + _run_once _make_prepare + _run_once _make_iso + fi +} -while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:i:vh' arg; do +while getopts 'B:p:r:C:L:P:A:D:w:o:s:c:g:dvh' arg; do case "${arg}" in + B) + profile="$(realpath -- "${OPTARG}")" + _read_profile + ;; p) read -r -a opt_pkg_list <<< "${OPTARG}" pkg_list+=("${opt_pkg_list[@]}") ;; r) run_cmd="${OPTARG}" ;; - C) pacman_conf="${OPTARG}" ;; + C) pacman_conf="$(realpath -- "${OPTARG}")" ;; L) iso_label="${OPTARG}" ;; P) iso_publisher="${OPTARG}" ;; A) iso_application="${OPTARG}" ;; D) install_dir="${OPTARG}" ;; - w) work_dir="${OPTARG}" ;; - o) out_dir="${OPTARG}" ;; + w) work_dir="$(realpath -- "${OPTARG}")" ;; + o) out_dir="$(realpath -- "${OPTARG}")" ;; s) sfs_mode="${OPTARG}" ;; c) sfs_comp="${OPTARG}" ;; g) gpg_key="${OPTARG}" ;; @@ -416,38 +851,55 @@ while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:i:vh' arg; do esac done +if (( EUID != 0 )); then + _msg_error "${app_name} must be run as root." 1 +fi + shift $((OPTIND - 1)) -if [[ $# -lt 1 ]]; then +if (( $# < 1 )); then _msg_error "No command specified" 0 _usage 1 fi command_name="${1}" +# Set directory path defaults +airootfs_dir="${work_dir}/airootfs" +isofs_dir="${work_dir}/iso" + case "${command_name}" in init) + _msg_warning "The '${command_name}' command is deprecated!" command_init ;; install) + _msg_warning "The '${command_name}' command is deprecated!" command_install ;; run) + _msg_warning "The '${command_name}' command is deprecated!" command_run ;; prepare) + _msg_warning "The '${command_name}' command is deprecated!" command_prepare ;; pkglist) + _msg_warning "The '${command_name}' command is deprecated!" command_pkglist ;; iso) - if [[ $# -lt 2 ]]; then + _msg_warning "The '${command_name}' command is deprecated!" + if (( $# < 2 )); then _msg_error "No image specified" 0 _usage 1 fi img_name="${2}" command_iso ;; + build_profile) + command_build_profile + ;; *) _msg_error "Invalid command name '${command_name}'" 0 _usage 1 |