#!/bin/bash set +o posix shopt -s extglob ##################################################################################### # Script : inst_iso.build # Purpose: Build EFI bootable ISO's of the Slackware Installer. # Author : Stuart Winter # Date...: 25-Mar-2025 ##################################################################################### # # Copyright 2025 Stuart Winter, Donostia, Spain. # All rights reserved. # # Redistribution and use of this script, with or without modification, is # permitted provided that the following conditions are met: # # 1. Redistributions of this script must retain the above copyright # notice, this list of conditions and the following disclaimer. # 1. Redistributions of this script must retain the above copyright # notice, this list of conditions and the following disclaimer. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ##################################################################################### # Credits & reference # David M. Syzdek: https://github.com/syzdek/efibootiso # This served as the foundation for building the EFI-bootable ISO, using GRUB as the # bootloader. ##################################################################################### # Sanity checks: which grub-mkimage 2>&1 > /dev/null || { echo "grub is required. You can find it in the 'a' Slackware package series"; exit 1; } # Settings: VERSION="$( basename $0 ) v1.00 Stuart Winter " ##################################################################################### ## Functions ##################################################################################### #12345678901234567890123456789012345678901234567890123456789012345678901234567890 # handy | ruler | | | | | | | function display_help() { printf "${VERSION} Main options: -A, --installer-aio Make an All in One Slackware Installer image -a, --iso-architecture Specify the target architecture for the bootable EFI Slackware Installer image. This is used for GRUB's settings. The options are: 'aarch64', 'x86_64' or 'x86' -B, --installer-bare Make the bare Slackware Installer that does not contain the Slackware packages. -d, --output-dir Output directory for the resulting images. By default, the output directory is not created if it does not exist. This is to surface errors during the build process. Use the '-c' operator to create the output directory if required. -c, --create-output-dir Create the output directory if it does not exist. -g, --grub-config-file The GRUB configuration file used to boot the Slackware Installer. -S, --slackware-tree The Slackware tree (directory) - e.g. \"slackwareaarch64-current\" The Kernel, Installer image and GRUB package is obtained from here. -O, --overlay-slackware-tree An overlay to the Slackware tree. This is used if you want to pick a different Kernel and Installer from the version in the provided Slackware tree. -r, --slackware-release-name Specify the release of Slackware. This is used to configure the AiO ISO so that the Slackware Installer knows how to find the correct packages. -L, --linux-kernel-image The Slackware Linux Kernel image. This is relative to the Slackware tree that is specified by the --slackware-tree option. -N, --slackware-installer-image The Slackware Linux Installer image. This is relative to the Slackware tree that is specified by the --slackware-tree option. -i, --iso-file-name Bame for the resulting ISO - e.g. 'generic_aarch64' The detected Kernel version will be appended to the image file name. -I, --image-app-id Application id written into the volume header of the ISO. -l, --create-latest-symlink Create a symlink between .iso -> _latest.iso -m, --md5sum-iso create an MD5 sum of the resulting ISO -n, --no-cleanup Do not clean up the temporary workspace when complete. [ default: clean up ] -T, --tmpdir The temporary working space directory [ default: $TMPDIR ] " } # Set defaults for the configurable settings: function build_settings_set_defaults() { # Installer ISO type INSTALLER_TYPE=Bare # ISO output directory ISO_OUTPUTDIR=/tmp/build-iso-installer # The ISO GRUB configuration file: # This needs to be set by the command line - we won't set a default value here. unset ISO_GRUB_CFG # Create output dir if it doesn't exist: ISO_OUTPUTDIR_CREATE=No # ISO file name ISO_FILENAME=slk_installer.iso # ISO Volume ID - default to building the "bare" ISO # When building the AiO Installer, this is set to "SLKins_aio-pkgs", which is # detected by '/usr/lib/setup/INS-all-in-one' to discover if the volume containing the # Slackware packages is present, and offer to use it as the installation source. # This is why this setting isn't configurable by the user. ISO_VOL_ID="SLKins_bare" # ISO Application ID. This isn't used by the Installer, so can be set by the user: ISO_APP_ID="Slackware Installer" # Create "latest" symlink ISO_CREATE_LATEST_SYMLINK=No # Create MD5 sum of ISO ISO_CREATE_MD5=No # Clean up after completing? WORKSPACE_CLEANUP=Yes # Temporary working space directory: TMPDIR=/tmp/slk-iso-build } # Output the settings: function build_settings_display() { printf " ISO generation settings: ------------------------ Installer ISO type.....: ${INSTALLER_TYPE} ISO GRUB config file...: ${ISO_GRUB_CFG} ISO output directory...: ${ISO_OUTPUTDIR} Create output dir......: ${ISO_OUTPUTDIR_CREATE} ISO file name..........: ${ISO_FILENAME} ISO Volume ID..........: ${ISO_VOL_ID} ISO Application ID.....: ${ISO_APP_ID} Create \"latest\" symlink: ${ISO_CREATE_LATEST_SYMLINK} Temporary directory....: ${TMPDIR} Target architecture....: ${ISO_ARCH} Slackware release......: ${SLK_RELNAME} Create MD5 sum of ISO..: ${ISO_CREATE_MD5} Slackware component settings: ----------------------------- Kernel.................: ${SLK_KERNEL_IMAGE} Installer image........: ${SLK_INST_IMAGE} Slackware tree.........: ${SLK_TREE} Slackware tree overlay : $( { [ -z ${SLK_TREE_OVERLAY} ] && echo "[ None ]" || echo ${SLK_TREE_OVERLAY} ;} ) General settings: ---------------- Clean up workspace.....: ${WORKSPACE_CLEANUP} " } # Parse command line parameters. # Whilst some of the variables need to be set, we don't mandate that they are configured # from the command line. This enables us to set them from the environment, and on # Slackware ARM, have some set from the Slackware ARM development kit. function parse_params() { eval set -- "${PARAMS}" for param in $*; do case "$param" in -A|--installer-aio) INSTALLER_TYPE=AiO ISO_VOL_ID="SLKins_aio-pkgs" shift 1;; -B|--installer-bare) INSTALLER_TYPE=Bare ISO_VOL_ID="SLKins_bare" shift 1;; -I|--image-app-id) ISO_APP_ID="$2" shift 2;; -a|--iso-architecture) ISO_ARCH="$2" shift 2;; -d|--output-dir) ISO_OUTPUTDIR="$2" shift 2;; -c|--create-output-dir) ISO_OUTPUTDIR_CREATE=Yes shift 1;; -g|--grub-config-file) ISO_GRUB_CFG="$2" shift 2;; -S|--slackware-tree) SLK_TREE="${2%%+(/)}" # lop any trailing slashes as they break the rsync exclusions shift 2;; -r|--slackware-release-name) SLK_RELNAME="$2" shift 2;; -L|--linux-kernel-image) SLK_KERNEL_IMAGE="$2" shift 2;; -N|--slackware-installer-image) SLK_INST_IMAGE="$2" shift 2;; -O|--overlay-slackware-tree) SLK_TREE_OVERLAY="$2" shift 2;; -i|--iso-file-name) ISO_FILENAME="$2" shift 2;; -T|--tmpdir) TMPDIR="$2" shift 2;; -n|--no-cleanup) WORKSPACE_CLEANUP=No shift 1;; -l|--create-latest-symlink) ISO_CREATE_LATEST_SYMLINK=Yes shift 1;; -m|--md5sum-iso) ISO_CREATE_MD5=Yes shift 1;; -h|--help) display_help ; exit ;; --) shift; break;; esac done } # Prepare build settings - any overrides, etc. # Convert relative paths to absolute - required when working with the primary # "distributable" and "overlay" trees: function build_settings_finalise() { # Ensure the target architecture is known, so that we can configure GRUB appropriately: case "${ISO_ARCH}" in aarch64) GRUB_EFIARCH=arm64-efi GRUB_EFIBOOT=BOOTAA64.EFI ;; x86_64) GRUB_EFIARCH=x86_64-efi GRUB_EFIBOOT=BOOTX64.EFI ;; x86) GRUB_EFIARCH=i386-pc GRUB_EFIBOOT=BOOTIA32.EFI ;; esac if [ ! -z "${SLK_TREE_OVERLAY}" ]; then # If we are working with an overlay tree, we take the Kernel and Installer images from there: SLK_INST_IMAGE=${SLK_TREE_OVERLAY}/${SLK_INST_IMAGE} SLK_KERNEL_IMAGE=${SLK_TREE_OVERLAY}/${SLK_KERNEL_IMAGE} else # We take them from the main tree: SLK_INST_IMAGE=${SLK_TREE}/${SLK_INST_IMAGE} SLK_KERNEL_IMAGE=${SLK_TREE}/${SLK_KERNEL_IMAGE} fi } # Sanity check the settings. Set defaults if unset. function build_settings_sanity_check() { # If the user specified an overlay tree, check it exists: if [ ! -z "${SLK_TREE_OVERLAY}" ]; then [ ! -d "${SLK_TREE_OVERLAY}" ] && { echo "ERROR: Cannot find Slackware overlay tree." ; exit 3 ;} fi # Ensure the items we need are present either in the main tree or the overlay: [ ! -f "${ISO_GRUB_CFG}" ] && { echo "ERROR: Cannot find GRUB configuration file." ; exit 3 ;} [ ! -f "${SLK_INST_IMAGE}" ] && { echo "ERROR: Cannot find Slackware Installer image." ; exit 3 ;} [ ! -f "${SLK_KERNEL_IMAGE}" ] && { echo "ERROR: Cannot find Slackware Linux Kernel image." ; exit 3 ;} # Slackware trees: [ ! -d "${SLK_TREE}" ] && { echo "ERROR: Cannot find Slackware tree." ; exit 3 ;} # If building the AiO Installers, ensure that we have a Slackware release specified: if [ "${INSTALLER_TYPE}" = "AiO" ]; then [ -z "${SLK_RELNAME}" ] && { echo "ERROR: Slackware release name must be specified when building AiO Installer image." ; exit 3 ;} fi # Ensure the output directory exists, unless we opted to otherwise create it: if [ "${ISO_OUTPUTDIR_CREATE}" != "Yes" ]; then [ ! -d "${ISO_OUTPUTDIR}" ] && { echo "ERROR: Cannot find ISO output directory." ; exit 3 ;} fi # Ensure that we can configure GRUB properly, based on the specified architecture: [ -z "${GRUB_EFIARCH}" -o -z "${GRUB_EFIBOOT}" ] && { echo "ERROR: Unsupported architecture" ; exit 3 ;} # Ensure that we can find the GRUB Slackware package: [ ! -f ${SLK_TREE}/slackware*/a/grub-+([^-])-+([^-])-+([^-]).t[gblx]z ] && { echo "ERROR: Unable to find Slackware GRUB package" ; exit 3 ;} return 0 } # Create the temporary working space: function workspace_prepare() { local workspace="$1" rm -rf ${workspace} # Base work space: mkdir -pm755 ${workspace} # Temporary area within the working space - used to extract the GRUB package and other bits mkdir -pm755 ${workspace}/tmp # ISO root directory - will contain the Kernel, Installer image and the Slackware packages # (if building the AiO Installer): mkdir -pm755 ${workspace}/isoroot return 0 } # Build the EFI base: function build_efi_base() { local iso_rootdir="$1" # Enter the ISO root directory - this stuff will be within the shipped ISO: pushd ${iso_rootdir} || exit 1 # Create the standard structure: mkdir -p EFI/BOOT mkdir -p boot/grub/${GRUB_EFIARCH} # Install the Slackware Linux Kernel and Installer images: install -vpm644 ${SLK_INST_IMAGE} boot/ install -vpm644 ${SLK_KERNEL_IMAGE} boot/ # Unpack the Slackware GRUB package, as we need some items from it: mkdir -p ${TMPDIR}/tmp/grub-extract tar --wildcards -C \ ${TMPDIR}/tmp/grub-extract/ \ -xvvf ${SLK_TREE}/slackware*/a/grub-+([^-])-+([^-])-+([^-]).t[gblx]z \ "usr/lib*/grub/${GRUB_EFIARCH}/" || return 1 # Move into place within the EFI root: mv -fv ${TMPDIR}/tmp/grub-extract/usr/lib*/grub/${GRUB_EFIARCH}/*.mod boot/grub/${GRUB_EFIARCH}/ # Create a stub config: cat << EOF > grub-stub.cfg #insmod all_video insmod iso9660 insmod udf search --no-floppy --label EFIBOOTISO --set root set prefix=($root)/boot/grub configfile /grub.cfg EOF # Only enable EFI modules for EFI-capable hardware. [ "${ISO_ARCH}" != "x86" ] && GRUB_EFIMODS="efi_gop efinet efifwsetup lsefimmap lsefisystab lssal" # Build a bootable image of GRUB: grub-mkimage \ -o EFI/BOOT/${GRUB_EFIBOOT} \ -c grub-stub.cfg \ -p /boot/grub \ -O ${GRUB_EFIARCH} \ ${GRUB_EFIMODS} \ all_video video video_fb videoinfo \ serial \ terminfo terminal \ search search_fs_file search_fs_uuid search_label \ udf iso9660 ext2 fat exfat ntfs hfsplus \ part_gpt part_msdos msdospart lvm diskfilter parttool probe \ normal \ acpi \ cat ls chain configfile echo halt reboot \ ls lsmmap lsacpi \ linux || return 1 dd if=/dev/zero of=EFI/BOOT/efiboot.img bs=512 count=2880 || return 1 mkfs.msdos -F 12 -n 'EFIBOOTISO' EFI/BOOT/efiboot.img || return 1 mmd -i EFI/BOOT/efiboot.img ::EFI || return 1 mmd -i EFI/BOOT/efiboot.img ::EFI/BOOT || return 1 mcopy -i EFI/BOOT/efiboot.img EFI/BOOT/${GRUB_EFIBOOT} ::EFI/BOOT/${GRUB_EFIBOOT} || return 1 # Install the GRUB configuration that the user will interact with: install -vpm644 ${ISO_GRUB_CFG} ${iso_rootdir}/grub.cfg || return 1 # Adjust the volume name that contains the Slackware Installer and Kernel images. # The AiO Installer image has a different volume name to the 'bare' installer, so that # the Installer can detect if an AiO Installer is in use. sed -i 's?%SLK_ISO_VOLNAME%?'$ISO_VOL_ID'?g' ${iso_rootdir}/grub.cfg || return 1 popd return 0 } # Copy the Slackware packages into the ISO's root directory. # This is for creation of the AiO Slackware Installer images: function aio_copy_slackware_tree() { local iso_rootdir=$1 local slktree=$2 local arch_aio_slkdir [ ! -d "${slktree}" ] && { echo "ERROR: Slackware tree not found" ; return 1 ;} [ ! -d "${iso_rootdir}" ] && { echo "ERROR: ISO root directory not found" ; return 1 ;} pushd "${iso_rootdir}" || return 1 # The Slackware tree bundled is identified in this file. This file is read by # /usr/lib/setup/INS-all-in-one within the Slackware Installer to identify which # Slackware release/tree is in play for this particular A-i-O image: # Content of this file for reference: # root@honey:/tmp# cat /mnt/zip/slackware/.slktree # slackwareaarch64-current/slackware # root@honey:/tmp/# ls /mnt/zip/slackware/ # slackwareaarch64-current/ if [ "${ISO_ARCH}" = "x86_64" ]; then # Slackware64 has its packages within a dir named 'slackware64': arch_aio_slkdir=slackware64 else # Other ports' packages reside within a dir named "slackware": arch_aio_slkdir=slackware fi # Create the directory and populate the helper config file: mkdir -pm755 ${arch_aio_slkdir} # The root of the Slackware tree will reside in here. echo "${SLK_RELNAME}/${arch_aio_slkdir}" > ${arch_aio_slkdir}/.slktree # Copy Slackware tree. We'll include /extra and /testing even though they won't be # considered by the Slackware Installer; it's just so that the user has it available # if needed for subsequent installation: rsync -PavL \ --exclude '*/source' \ --exclude '*/installer' \ --exclude '*/isolinux' \ --exclude '*/kernels' \ --exclude '*/EFI' \ --exclude '*/usb-and-pxe-installers' \ ${slktree} \ ${arch_aio_slkdir}/ || return 1 popd return 0 } # Generate the ISO: # Example call usage: # iso_name /tmp/isocontentdir /tmp/generic_aarch64.iso "SLKins_aio-pkgs" "Slackware AArch64 Installer" function iso_make() { local iso_rootdir="$1" local iso_fileout="$2" local iso_vol_name="$3" local iso_app_name="$4" # We need the output directory to exist. Create it if necessary, because # we've already sanity checked it earlier. mkdir -pm755 ${ISO_OUTPUTDIR} # Ensure the temporary working space ISO data directory exists, as this contains all the data # to include in the ISO image: [ ! -d "${iso_rootdir}" ] && { echo "ERROR: Cannot find ISO data directory" ; return 1 ;} # Clean out any previous GPG signatures and md5sum for this # ISO: rm -fv ${iso_fileout}.{asc,md5} mkisofs \ -o "${iso_fileout}" \ -R -J -v -d -N \ -hide-rr-moved \ -no-emul-boot \ -eltorito-platform efi \ -eltorito-boot EFI/BOOT/efiboot.img \ -V "${iso_vol_name}" \ -A "${iso_app_name}" \ "${iso_rootdir}/" || return 1 return 0 } # Create an MD5 sum of the ISO: function iso_md5sum() { local iso_file="$1" [ ! -f "${iso_file}" ] && { echo "ERROR: ${iso_file} not found" ; return 1 ;} echo "Generating MD5 sum..." openssl md5 -r "${iso_file}" | awk '{print $1}' > "${iso_file}".md5 || return 1 return 0 } function workspace_cleanup() { local workspace="$1" echo "Cleaning up workspace ${workspace} ..." rm -rf "${workspace}" return 0 } ##################################################################################### # Trap errors and clean up: #trap cleanup 1 2 14 15 # trap CTRL+C and kill # Set sensible defaults. These can be overridden by environment variables # or command line operators: build_settings_set_defaults # Capture the parameters set at the command line: PARAMS="$( getopt -qn "$( basename $0 )" -o a:ABd:ci:g:I:O:S:L:N:T:r:lhmn -l iso-architecture:,installer-aio,installer-bare,output-dir:,create-output-dir,grub-config-file:,slackware-release-name:,slackware-tree:,overlay-slackware-tree:,linux-kernel-image:,slackware-installer-image:,iso-file-name:,image-app-id:,tmpdir:,create-latest-symlink,md5sum-iso,no-cleanup,help -- "$@" )" if [ $? -gt 0 ]; then display_help ; exit 2 ; fi # Parse the command line parameters: parse_params # Finalise any build settings which may require adjusting depending on whether we're # working with >1 Slackware tree: build_settings_finalise || exit 1 # Display settings: build_settings_display || exit 1 # Sanity check the settings prior to building: build_settings_sanity_check || exit 1 # Prepare the working space: workspace_prepare $TMPDIR || exit 1 # Create EFI bits: build_efi_base $TMPDIR/isoroot || exit 1 # If creating the AiO Installer, copy the Slakckware tree to the ISO's root: [ "${INSTALLER_TYPE}" = "AiO" ] && { aio_copy_slackware_tree $TMPDIR/isoroot ${SLK_TREE} || exit 1 ;} # Build the ISO: iso_make $TMPDIR/isoroot ${ISO_OUTPUTDIR}/${ISO_FILENAME} ${ISO_VOL_ID} ${ISO_APP_ID} || exit 1 # Generate MD5 sum if requested: [ "${ISO_CREATE_MD5}" = "Yes" ] && { iso_md5sum ${ISO_OUTPUTDIR}/${ISO_FILENAME} || exit 1 ;} # Clean up the working space? if [ "${WORKSPACE_CLEANUP}" = "Yes" ]; then workspace_cleanup "$TMPDIR" fi