#!/bin/bash
# Filename:      grml-live
# Purpose:       build process script for generating a (grml based) Linux Live-ISO
# Authors:       Grml Team (see https://grml.org/)
# Bug-Reports:   see http://grml.org/bugs/
# License:       This file is licensed under the GPL v2 or any later version.
################################################################################

# some misc and global stuff {{{
export LANG=C
export LC_ALL=C

# avoid leaking into chroots
unset TMPDIR

# exit on any error:
# disable for now since it seems to cause some problems
# set -e

# The line following this line is patched by debian/rules.
GRML_LIVE_VERSION='0.56.0'

# global variables
PN=$(basename "$0")
CMDLINE="$0 $*"
GRML_LIVE_INSTALL_PREFIX=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
if [ -e "${GRML_LIVE_INSTALL_PREFIX}"/usr/lib/grml-live ]; then
    # assume source checkout
    GRML_LIVE_LIB_DIR="${GRML_LIVE_INSTALL_PREFIX}"/usr/lib/grml-live
    if command -v git &>/dev/null; then
        GRML_LIVE_VERSION=$(git describe --always)
    fi
else
    GRML_LIVE_INSTALL_PREFIX=${GRML_LIVE_INSTALL_PREFIX}/../
    GRML_LIVE_LIB_DIR=${GRML_LIVE_INSTALL_PREFIX}/lib/grml-live
fi
# }}}

# usage information {{{
usage()
{
  echo "
$PN - build process script for generating a (grml based) Linux Live-ISO

Usage: $PN [options, see as follows]

   -a <architecture>       architecture; available values: i386, amd64 + arm64
   -A                      clean build directories before and after running
   -b                      build the ISO without updating the chroot
   -B                      build the ISO without touching the chroot (do not clean up)
   -c <class[es]>          classes to be used for building the ISO
   -C <configfile>         configuration file for grml-live
   -d <date>               use specified date instead of build time as date of release
   -D <configdir>          use specified configuration directory instead of /usr/share/grml-live/config
   -e <iso_name>           extract ISO and squashfs contents from iso_name
   -F                      force execution without prompting
   -g <grml_name>          set the grml flavour name
   -h                      display short usage information and exit
   -i <iso_name>           name of ISO
   -I <src_directory>      directory which provides files that should become
                           part of the chroot/ISO
   -n                      skip generation of ISO
   -N                      bootstrap (build chroot) only, do not create files for ISO
   -o <output_directory>   main output directory of the build process
   -q                      skip mksquashfs
   -Q                      skip netboot package build
   -r <release_name>       release name
   -R                      skip applying RELEASE clean up
   -s <suite>              Debian suite/release, like: stable, testing, unstable
   -S <arg>                deprecated - will be removed from grml-live
   -t <arg>                deprecated - will be removed from grml-live
   -u                      update existing chroot instead of rebuilding it from scratch
   -U <username>           arrange output to be owned by specified username
   -v <version_number>     specify version number of the release
   -V                      deprecated - will be removed from grml-live
   -w <date>               wayback machine, build system using Debian archives
                           from specified date
   -z                      use ZLIB instead of LZMA/XZ compression

Usage examples:

    $PN
    $PN -c GRML_FULL -o /dev/shm/grml
    $PN -c GRML_FULL -i grml_0.0-1.iso -v 0.0-1
    $PN -c GRML_FULL -s stable -V -r 'grml-ftw'

More details: man grml-live + /usr/share/doc/grml-live/grml-live.html
              http://grml.org/grml-live/

Please send your bug reports and feedback to the grml-team: http://grml.org/bugs/
"
   [ "$(id -u 2>/dev/null)" != 0 ] && echo "Please notice that this script requires root permissions."
}

# make sure it's possible to get usage information without being
# root or actually executing the script
if [ "$1" = '-h' ] || [ "$1" = '--help' ] ; then
   usage
   exit 0
fi
# }}}

# some runtime checks {{{
# we need root permissions for the build-process:
if [ "$(id -u 2>/dev/null)" != 0 ] ; then
   echo "Error: please run this script with uid 0 (root)." >&2
   exit 1
fi

# lsb-functions and configuration stuff {{{
# make sure they are not set by default
FORCE=''
UPDATE=''
BUILD_ONLY=''
BUILD_DIRTY=''
BOOTSTRAP_ONLY=''
HOSTNAME=''
USERNAME=''
CONFIGDUMP=''
FAI_PROGRAM=$GRML_LIVE_LIB_DIR/minifai
# }}}

# don't use colors/escape sequences
if [ -r /lib/lsb/init-functions ] ; then
  # shellcheck source=/dev/null
  . /lib/lsb/init-functions
  # shellcheck disable=SC2034
  ! log_use_fancy_output && NOCOLORS=true
fi

if [ -r /etc/grml/lsb-functions ] ; then
   # shellcheck source=/dev/null
   . /etc/grml/lsb-functions
else
   einfo()  { echo "  [*] $*" ;}
   eerror() { echo "  [!] $*">&2 ;}
   ewarn()  { echo "  [x] $*" ;}
   eend()   { return 0 ;}
fi

# source main configuration file:
[ -z "$LIVE_CONF" ] && LIVE_CONF='/etc/grml/grml-live.conf'
if ! [ -r "$LIVE_CONF" ] ; then
  ewarn "Configuration file $LIVE_CONF can not be read, ignoring"
else
  einfo "Sourcing configuration file $LIVE_CONF"
  # shellcheck source=/dev/null
  . "$LIVE_CONF"
  eend $?
fi
# }}}

# umount all directories {{{
umount_all() {
   [ -n "$MIRROR_DIRECTORY" ] && umount "${CHROOT_OUTPUT}/${MIRROR_DIRECTORY}"
}
# }}}

# store logfiles {{{
store_logfiles() {
  # move minifai logs into grml_logs directory
  mkdir -p "$LOG_OUTPUT"/fai/
  cp --preserve=timestamp -r "$CHROOT_OUTPUT"/grml-live/log/* "$LOG_OUTPUT"/fai/
  rm -rf "$CHROOT_OUTPUT"/grml-live/log

  # ensure files are readable.
  chmod a+rX "$LOG_OUTPUT"/fai/
}
# }}}

# clean exit {{{
bailout() {
  [ -n "$CONFIGDUMP"      ]  && rm -f  "$CONFIGDUMP"
  [ -n "$SQUASHFS_STDERR" ]  && rm -rf "$SQUASHFS_STDERR"
  umount_all
  [ -n "$1" ] && EXIT="$1" || EXIT="1"
  [ -n "$2" ] && eerror "$2">&2
  if [ -n "$CLEAN_ARTIFACTS" ]; then
    log "Cleaning up"
    einfo "Cleaning up"
    [ -n "${BUILD_OUTPUT}"  ] && [ -d "${BUILD_OUTPUT}"  ] && rm -r "${BUILD_OUTPUT}"
    [ -n "${CHROOT_OUTPUT}" ] && [ -d "${CHROOT_OUTPUT}" ] && rm -r "${CHROOT_OUTPUT}"
    eend 0
  fi

  if [ -n "$CHOWN_USER" ]; then
    log "Setting ownership"
    einfo "Setting ownership"
    [ -n "${OUTPUT}"         ] && [ -d "${OUTPUT}"         ] && chown -R "${CHOWN_USER}:" "${OUTPUT}"
    [ -n "${BUILD_OUTPUT}"   ] && [ -d "${BUILD_OUTPUT}"   ] && chown -R "${CHOWN_USER}:" "${BUILD_OUTPUT}"
    [ -n "${CHROOT_OUTPUT}"  ] && [ -d "${CHROOT_OUTPUT}"  ] && chown -R "${CHOWN_USER}:" "${CHROOT_OUTPUT}"
    [ -n "${ISO_OUTPUT}"     ] && [ -d "${ISO_OUTPUT}"     ] && chown -R "${CHOWN_USER}:" "${ISO_OUTPUT}"
    [ -n "${LOG_OUTPUT}"     ] && [ -d "${LOG_OUTPUT}"     ] && chown -R "${CHOWN_USER}:" "${LOG_OUTPUT}"
    [ -n "${NETBOOT}"        ] && [ -d "${NETBOOT}"        ] && chown -R "${CHOWN_USER}:" "${NETBOOT}"
    eend 0
  fi
  log "------------------------------------------------------------------------------"
  exit "$EXIT"
}
trap bailout 1 2 3 3 6 14 15
trap umount_all EXIT
# }}}

# some important functions {{{

# log output:
# usage: log "string to log"
log() { [ -n "$LOGFILE" ] && echo "$*" >> "$LOGFILE" ; }

# cut string at character number int = $1
# usage: cut_string 5 "1234567890" will output "12345"
cut_string() {
  [ -n "$2" ] || return 1
  echo "$2" | head -c "$1"; echo -ne "\n"
}

# append int = $1 spaces to string = $2
# usage: extend_string_end 5 "123" will output "123  "
extend_string_end() {
  [ -n "$2" ] || return 1
  local text
  text=$(echo -n "$2" | head -c "$1")
  while [ "$1" -gt "${#text}" ] ; do
    text="${text} "
  done
  echo -n "${text}"
}

# Returns success if a given class was requested.
# This is not called `ifclass`, as ifclass supports a broader syntax.
hasclass() {
  local expected_class="$1"
  case $CLASSES in *,${expected_class},*) return 0 ;; esac
  case $CLASSES in *,${expected_class}) return 0 ;; esac
  case $CLASSES in ${expected_class},*) return 0 ;; esac
  return 1
}
# }}}

# command line parsing {{{
while getopts "a:C:c:d:D:e:g:i:I:o:r:s:S:t:U:v:w:AbBFhnNqQRuVz" opt; do
  case "$opt" in
    a) ARCH="$OPTARG" ;;
    A) CLEAN_ARTIFACTS=1 ;;
    b) BUILD_ONLY=1 ;;
    B) BUILD_DIRTY=1; SKIP_RELEASE=1 ;;
    c) CLASSES="$OPTARG" ;;
    C) LOCAL_CONFIG="$(readlink -f "$OPTARG")" ;;
    d) DATE="$OPTARG" ;;
    D) GRML_FAI_CONFIG="$(readlink -f "$OPTARG")" ;;
    e) EXTRACT_ISO_NAME="$(readlink -f "$OPTARG")" ;;
    g) GRML_NAME="$OPTARG" ;;
    h) usage ; bailout 0 ;;
    i) ISO_NAME="$OPTARG" ;;
    I) CHROOT_INSTALL="$OPTARG" ;;
    n) SKIP_MKISOFS=1 ;;
    N) BOOTSTRAP_ONLY=1; SKIP_MKISOFS=1; SKIP_MKSQUASHFS=1 ;;
    o) OUTPUT="$(readlink -m "$OPTARG")" ;;
    q) SKIP_MKSQUASHFS=1 ;;
    Q) SKIP_NETBOOT=1 ;;
    r) RELEASENAME="$OPTARG" ;;
    R) SKIP_RELEASE=1 ;;
    s) SUITE="$OPTARG" ;;
    S) echo "grml-live option -S is deprecated, please remove it." ;;
    t) echo "grml-live option -t is deprecated, please remove it." ;;
    v) VERSION="$OPTARG" ;;
    F) FORCE=1 ;;
    u) UPDATE=1 ;;
    U) CHOWN_USER="$OPTARG" ;;
    V) echo "grml-live option -V is deprecated, please remove it." ;;
    w) export WAYBACK_DATE="$OPTARG" ;;
    z) SQUASHFS_ZLIB=1 ;;
    ?) echo "invalid option -$OPTARG" >&2; usage; bailout 1 ;;
  esac
done
shift $((OPTIND - 1))  # set ARGV to the first not parsed commandline parameter

if [ -n "$1" ] ; then
  echo "Error: unknown argument '$1' in options. Exiting to avoid possible data loss." >&2
  bailout 1
fi
# }}}

# read local (non-packaged) configuration {{{
if [ -z "$LOCAL_CONFIG" ]; then
  if [ -r "/etc/grml/grml-live.local" ]; then
    LOCAL_CONFIG="/etc/grml/grml-live.local"
  fi
fi
if [ -n "$LOCAL_CONFIG" ]; then
  if [ -r "$LOCAL_CONFIG" ]; then
    # shellcheck source=/dev/null
    . "$LOCAL_CONFIG"
  else
    eerror "Could not read specified local configuration file \"$LOCAL_CONFIG\"."
    bailout 1
  fi
  LOCAL_CONFIG=$(readlink -f "$LOCAL_CONFIG")
else
  LOCAL_CONFIG=''
fi
# }}}

# setup SOURCE_DATE_EPOCH {{{
if ! [[ "$CLASSES" == *NO_ONLINE* ]]; then
  ewarn "Class NO_ONLINE NOT requested. Output will not be reproducible."; eend 1
fi
if [ -z "$SOURCE_DATE_EPOCH" ] ; then
  if [ -n "$DATE" ] ; then
    # Convert user-provided DATE to epoch timestamp
    SOURCE_DATE_EPOCH=$(date -d "$DATE" +%s 2>/dev/null || date +%s)
  else
    # Fallback to current time
    SOURCE_DATE_EPOCH=$(date +%s)
  fi
fi
export SOURCE_DATE_EPOCH
# }}}

# find programs
[ -n "$DPKG_BINARY" ]             || DPKG_BINARY='dpkg'
if ! type "$DPKG_BINARY" >/dev/null 2>&1 ; then
  eerror "Error: $DPKG_BINARY not available - cannot determine architecture." ; eend 1
  bailout
fi
[ -n "$MKSQUASHFS_BINARY" ]       || MKSQUASHFS_BINARY='mksquashfs'
if [ -z "$SKIP_MKSQUASHFS" ] && ! type "$MKSQUASHFS_BINARY" >/dev/null 2>&1 ; then
  eerror "Error: $MKSQUASHFS_BINARY not available - cannot build squashfs." ; eend 1
  bailout
fi
[ -n "$OSIRROX_BINARY" ]          || OSIRROX_BINARY='osirrox'
if [ -n "$EXTRACT_ISO_NAME" ] && ! type "$OSIRROX_BINARY" >/dev/null 2>&1 ; then
  eerror "Error: $OSIRROX_BINARY not available - cannot extract ISO." ; eend 1
  bailout
fi
[ -n "$UNSQUASHFS_BINARY" ]       || UNSQUASHFS_BINARY='unsquashfs'
if [ -n "$EXTRACT_ISO_NAME" ] && ! type "$UNSQUASHFS_BINARY" >/dev/null 2>&1 ; then
  eerror "Error: $UNSQUASHFS_BINARY not available - cannot extract ISO." ; eend 1
  bailout
fi
[ -n "$XORRISO_BINARY" ]          || XORRISO_BINARY='xorriso'
if [ -z "$SKIP_MKISOFS" ] && ! type "$XORRISO_BINARY" >/dev/null 2>&1 ; then
  eerror "Error: $XORRISO_BINARY not available - cannot build ISO." ; eend 1
  bailout
fi

# assume sane defaults (if not set already) {{{
[ -n "$ARCH" ]                    || ARCH="$("$DPKG_BINARY" --print-architecture)"
[ -n "$CLASSES" ]                 || CLASSES="GRML_FULL,$(echo "${ARCH}" | tr '[:lower:]' '[:upper:]')"
[ -n "$DATE" ]                    || DATE="$(date --utc -d "@${SOURCE_DATE_EPOCH}" +%Y-%m-%d)"
[ -n "$DEFAULT_BOOTOPTIONS" ]     || DEFAULT_BOOTOPTIONS=''
[ -n "$DISTRI_INFO" ]             || DISTRI_INFO='Grml - Live Linux for system administrators'
[ -n "$DISTRI_NAME" ]             || DISTRI_NAME="grml"
[ -n "$GRML_FAI_CONFIG" ]         || GRML_FAI_CONFIG='/usr/share/grml-live/config'
[ -n "$GRML_NAME" ]               || GRML_NAME='grml'
[ -n "$HOSTNAME" ]                || HOSTNAME='grml'
[ -n "$NO_ADDONS" ]               || NO_ADDONS=''
[ -n "$NO_BOOTID" ]               || NO_BOOTID=''
[ -n "$RELEASENAME" ]             || RELEASENAME='grml-live rocks'
[ -n "$SECURE_BOOT" ]             || SECURE_BOOT='disable'
[ -n "$SQUASHFS_EXCLUDES_FILE" ]  || SQUASHFS_EXCLUDES_FILE="${GRML_FAI_CONFIG}/grml/squashfs-excludes"
[ -n "$SUITE" ]                   || SUITE='testing'
[ -n "$USERNAME" ]                || USERNAME='grml'
[ -n "$VERSION" ]                 || VERSION='0.0.1'

# output specific stuff, depends on $OUTPUT:
[ -n "$OUTPUT" ]           || OUTPUT="$PWD/grml/"
[ -n "$BUILD_OUTPUT" ]     && echo "W: setting BUILD_OUTPUT is deprecated, please remove it"
[ -n "$BUILD_OUTPUT" ]     || BUILD_OUTPUT="$OUTPUT/grml_cd"
[ -n "$CHROOT_OUTPUT" ]    && echo "W: setting CHROOT_OUTPUT is deprecated, please remove it"
[ -n "$CHROOT_OUTPUT" ]    || CHROOT_OUTPUT="$OUTPUT/grml_chroot"
[ -n "$ISO_OUTPUT" ]       && echo "W: setting ISO_OUTPUT is deprecated, please remove it"
[ -n "$ISO_OUTPUT" ]       || ISO_OUTPUT="$OUTPUT/grml_isos"
[ -n "$LOG_OUTPUT" ]       && echo "W: setting LOG_OUTPUT is deprecated, please remove it"
[ -n "$LOG_OUTPUT" ]       || LOG_OUTPUT="$OUTPUT/grml_logs"
[ -n "$NETBOOT" ]          && echo "W: setting NETBOOT is deprecated, please remove it"
[ -n "$NETBOOT" ]          || NETBOOT="${OUTPUT}/netboot"
# }}}

# some misc checks before doing work {{{
[ -n "$CLASSES" ] || bailout 1 "Error: \$CLASSES unset, please set it in $LIVE_CONF or
specify it on the command line using the -c option."
[ -n "$OUTPUT" ] || bailout 1 "Error: \$OUTPUT unset, please set it in $LIVE_CONF or
specify it on the command line using the -o option."

if [ "$ARCH" != "i386" ] && [ "$ARCH" != "amd64" ] && [ "$ARCH" != "arm64" ] ; then
  eerror "Error: Unsupported ARCH '$ARCH', sorry. Want to support it? Contribute!"
  eend 1
  bailout
fi

if [[ "$(dpkg --print-architecture)" != "arm64" ]] && [[ "$ARCH" == "arm64" ]] ; then
  eerror "Failure: trying to build for arm64, but not running on arm64."
  eend 1
  bailout
fi

if [ -e "$GRML_FAI_CONFIG"/fai.conf ] ; then
  ewarn "The file ${GRML_FAI_CONFIG}/fai.conf exists but will be ignored."
  eend 1
fi

if [ -n "$FAI_ARGS" ] ; then
  eerror "The variable \$FAI_ARGS is set, but it is unsupported." ; eend 1
  eerror "Please unset it. Current value: \$FAI_ARGS=$FAI_ARGS" ; eend 1
  bailout
fi

if [ -n "$FAI_DEBOOTSTRAP" ] ; then
  ewarn "The variable \$FAI_DEBOOTSTRAP is set, but it is deprecated." ; eend 1
  ewarn "If you want to specify a mirror, please set BOOTSTRAP_MIRROR." ; eend 1
  ewarn "Current value: \$FAI_DEBOOTSTRAP=${FAI_DEBOOTSTRAP}" ; eend 1
  BOOTSTRAP_MIRROR="${FAI_DEBOOTSTRAP#* }"
fi

if [ -n "$FAI_DEBOOTSTRAP_OPTS" ] ; then
  eerror "The variable \$FAI_DEBOOTSTRAP_OPTS is set, but it is unsupported." ; eend 1
  eerror "Current value: \$FAI_DEBOOTSTRAP_OPTS=${FAI_DEBOOTSTRAP_OPTS}" ; eend 1
  bailout
fi

if [ -n "$HYBRID_METHOD" ] ; then
  eerror "The variable \$HYBRID_METHOD is set, but it is unsupported." ; eend 1
  eerror "Please unset it. Current value: \$HYBRID_METHOD=$HYBRID_METHOD" ; eend 1
  bailout
fi

if [ -e /etc/grml/fai/config ] && [ -z "$GRML_FAI_CONFIG" ] ; then
  eerror "Found old configuration files in /etc/grml/fai/config (while \$GRML_FAI_CONFIG was empty)." ; eend 1
  eerror "You should check your configuration and move these files into a new path, and set \$GRML_FAI_CONFIG." ; eend 1
  bailout
fi

if [ -e "$GRML_FAI_CONFIG"/config ] ; then
  eerror "The path ${GRML_FAI_CONFIG}/config exists, very likely your \$GRML_FAI_CONFIG is invalid." ; eend 1
  eerror "Either set \$GRML_FAI_CONFIG=${GRML_FAI_CONFIG}/config or delete ${GRML_FAI_CONFIG}/config." ; eend 1
  bailout
fi
# }}}

# automatic classes {{{
if hasclass GRMLBASE ; then
  ewarn "Class GRMLBASE explicitly requested. This is now automatic, please remove it."; eend 1
else
  CLASSES="GRMLBASE,${CLASSES}"
fi
if hasclass SECURE_BOOT ; then
  ewarn "Class SECURE_BOOT explicitly requested. This is unsupported, please remove it."; eend 1
elif [[ "$SECURE_BOOT" == "debian" ]]; then
  CLASSES="${CLASSES},SECURE_BOOT"
fi
if hasclass RELEASE ; then
  ewarn "Class RELEASE explicitly requested. This is now automatic, please remove it."; eend 1
  if [[ "$SKIP_RELEASE" == "1" ]]; then
    eerror "Class RELEASE explicitly requested, while conflicting option -R is also given. Please remove either."
    bailout 1
  fi
elif [[ -z "$SKIP_RELEASE" ]]; then
  CLASSES="${CLASSES},RELEASE"
fi
if hasclass AMD64 ; then
  ewarn "Class AMD64 explicitly requested. This is now automatic, please remove it."; eend 1
elif [[ "$ARCH" == "amd64" ]]; then
  CLASSES="${CLASSES},AMD64"
fi
if hasclass ARM64 ; then
  ewarn "Class ARM64 explicitly requested. This is now automatic, please remove it."; eend 1
elif [[ "$ARCH" == "arm64" ]]; then
  CLASSES="${CLASSES},ARM64"
fi
# }}}

# Warn user if addons from grml-live-addons are absent {{{
if [ -z "${NO_ADDONS:-}" ] && [ ! -r "$GRML_FAI_CONFIG"/media-files/GRMLBASE/addons/arch ] ; then
  ewarn "Boot addons not found (Consider installing package grml-live-addons)" ; eend 0
fi
# }}}

# Show configuration and ask user whether to continue {{{
echo
echo "${PN} [${GRML_LIVE_VERSION}] Build Configuration:"
echo
echo "  FAI classes:       $CLASSES"
[ -n "$LOCAL_CONFIG" ]        && echo "  Configuration:     $LOCAL_CONFIG"
[ -n "$GRML_FAI_CONFIG" ]     && echo "  Config directory:  $GRML_FAI_CONFIG"
echo "  Output directory:  $OUTPUT"
[ -n "$EXTRACT_ISO_NAME" ]    && echo "  Extract ISO:       $EXTRACT_ISO_NAME"
[ -n "$GRML_NAME" ]           && echo "  Grml name:         $GRML_NAME"
[ -n "$RELEASENAME" ]         && echo "  Release name:      $RELEASENAME"
[ -n "$DATE" ]                && echo "  Build date:        $DATE"
echo "  SOURCE_DATE_EPOCH: $SOURCE_DATE_EPOCH"
[ -n "$WAYBACK_DATE" ]        && echo "  Wayback date:      $WAYBACK_DATE"
[ -n "$VERSION" ]             && echo "  Grml version:      $VERSION"
[ -n "$SUITE" ]               && echo "  Debian suite:      $SUITE"
[ -n "$ARCH" ]                && echo "  Architecture:      $ARCH"
[ -n "$SECURE_BOOT" ]         && echo "  Secure Boot:       $SECURE_BOOT"
[ -n "$CHROOT_INSTALL" ]      && echo "  Install files from directory to chroot:  $CHROOT_INSTALL"
[ -n "$BOOTID" ]              && echo "  Boot identifier:   $BOOTID"
[ -n "$NO_BOOTID" ]           && echo "  Skipping bootid feature."
[ -n "$CHOWN_USER" ]          && echo "  Output owner:      $CHOWN_USER"
[ -n "$DEFAULT_BOOTOPTIONS" ] && echo "  Adding default bootoptions: \"$DEFAULT_BOOTOPTIONS\""
[ -n "$LOGFILE" ]             && echo "  Logging to file:   $LOGFILE"
[ -n "$SQUASHFS_ZLIB" ]       && echo "  Using ZLIB (instead of LZMA/XZ) compression."
[ -n "$SQUASHFS_OPTIONS" ]    && echo "  Using SQUASHFS_OPTIONS ${SQUASHFS_OPTIONS}"
[ -n "$CLEAN_ARTIFACTS" ]     && echo "  Will clean output before and after running."
[ -n "$UPDATE" ]              && echo "  Executing UPDATE instead of fresh installation."
if [ -n "$BOOTSTRAP_ONLY" ] ; then
    echo "  Bootstrapping only and not building (files for) ISO."
else
    [ -n "$SKIP_MKSQUASHFS" ]     && echo "  Skipping creation of SQUASHFS file."
    [ -n "$SKIP_NETBOOT" ]        && echo "  Skipping creation of NETBOOT package."
    [ -n "$SKIP_MKISOFS" ]        && echo "  Skipping creation of ISO file."
    [ -n "$NO_ADDONS" ]           && echo "  Skipping boot addons."
    [ -n "$BUILD_ONLY" ]          && echo "  Executing BUILD_ONLY instead of fresh installation or UPDATE."
    [ -n "$BUILD_DIRTY" ]         && echo "  Executing BUILD_DIRTY to leave chroot untouched."
fi
[ -n "$SKIP_RELEASE" ]        && echo "  Skipping RELEASE clean up."
echo
if [ -z "$FORCE" ] ; then
   echo "Check the configuration above, or use -F to force execution."
   echo
   echo -n "Continue? [y/N] "
   read -r a
   if ! [ "$a" = 'y' ] || [ "$a" = 'Y' ] ; then
      CLEAN_ARTIFACTS=0
      echo "Exiting as requested."
      exit 0
   fi
   echo
fi
# }}}

# setup derived variables {{{
# trim characters that are known to cause problems inside $GRML_NAME;
# for example isolinux does not like '-' inside the directory name
SHORT_NAME="$(echo "$GRML_NAME" | tr -d ',./;\- ')"

RELEASE_INFO="$GRML_NAME $VERSION - Release Codename $RELEASENAME"
# ensure this has a specific length
RELEASE_INFO68="$(cut_string 68 "$RELEASE_INFO")"
RELEASE_INFO68="$(extend_string_end 68 "$RELEASE_INFO68")"

SQUASHFS_NAME="$GRML_NAME.squashfs"
# ensure this has a specific length
SQUASHFS_NAME20="$(cut_string 20 "$SQUASHFS_NAME")"
SQUASHFS_NAME20="$(extend_string_end 20 "$SQUASHFS_NAME20")"

# shellcheck disable=SC2034 # Calculated just to write it into CONFIGDUMP.
BOOT_FILE="/conf/bootfile_${SHORT_NAME}_${SOURCE_DATE_EPOCH}"

if [ -n "$NO_BOOTID" ] ; then
  BOOTID=""
else
  if [ -z "$BOOTID" ] ; then
    BOOTID=$(echo "${GRML_NAME}${VERSION}" | tr -d ',./;\- ')
  fi
fi
# }}}

# clean up before start {{{
if [ -n "${CLEAN_ARTIFACTS}" ]; then
  echo "Wiping old artifacts"
  [ -n "${CHROOT_OUTPUT}"  ] && [ -d "${CHROOT_OUTPUT}"  ] && rm -r "${CHROOT_OUTPUT}"
  [ -n "${BUILD_OUTPUT}"   ] && [ -d "${BUILD_OUTPUT}"   ] && rm -r "${BUILD_OUTPUT}"
  [ -n "${ISO_OUTPUT}"     ] && [ -d "${ISO_OUTPUT}"     ] && rm -r "${ISO_OUTPUT}"
  [ -n "${LOG_OUTPUT}"     ] && [ -d "${LOG_OUTPUT}"     ] && rm -r "${LOG_OUTPUT}"
  [ -n "${NETBOOT}"        ] && [ -d "${NETBOOT}"        ] && rm -r "${NETBOOT}"
fi
# }}}

# create log file {{{
[ -n "$LOGFILE" ] || LOGFILE=${LOG_OUTPUT}/grml-live.log
mkdir -p "$(dirname "${LOGFILE}")"
touch "$LOGFILE"
chown root:adm "$LOGFILE"
chmod 664 "$LOGFILE"
if [ -n "$PRESERVE_LOGFILE" ] ; then
   echo "Preserving logfile $LOGFILE as requested via \$PRESERVE_LOGFILE"
else
   # make sure it is empty
   echo -n > "$LOGFILE"
fi
# }}}

# source config and startup {{{
if [ -n "$CONFIG" ] ; then
   if ! [ -f "$CONFIG" ] ; then
      log    "Error: $CONFIG could not be read. Exiting. [$(date)]"
      eerror "Error: $CONFIG could not be read. Exiting." ; eend 1
      bailout 1
   else
      log "Sourcing $CONFIG"
      # shellcheck source=/dev/null
      . "$CONFIG"
   fi
fi

SECONDS=unknown
start_seconds="$(date +%s)"
log "------------------------------------------------------------------------------"
log "Starting grml-live [${GRML_LIVE_VERSION}] run on $(date)"
log "Using local config file: $LOCAL_CONFIG"
log "Executed grml-live command line:"
log "$CMDLINE"

einfo "Logging actions to logfile $LOGFILE"
# }}}

# dump config variables into file, for hooks/scripts access {{{
CONFIGDUMP=$(mktemp)
set | grep -E \
  '^(GRML_NAME|SHORT_NAME|RELEASENAME|RELEASE_INFO|RELEASE_INFO68|DATE|VERSION|SUITE|ARCH|DISTRI_INFO|DISTRI_NAME|SQUASHFS_NAME|SQUASHFS_NAME20|USERNAME|HOSTNAME|APT_PROXY|BUILD_ONLY|BOOTSTRAP_ONLY|WAYBACK_DATE|GRML_LIVE_DEBUG_APT|GRML_FAI_CONFIG|SOURCE_DATE_EPOCH|NO_BOOTID|BOOTID|BOOT_FILE|DEFAULT_BOOTOPTIONS|SECURE_BOOT|NO_ADDONS)=' \
  > "${CONFIGDUMP}"
# }}}

# unpack iso/squashfs {{{
extract_iso() {
if [ -n "$EXTRACT_ISO_NAME" ]; then
  log "Unpacking ISO from ${EXTRACT_ISO_NAME}"
  einfo "Unpacking ISO from ${EXTRACT_ISO_NAME}"
  local rc=0
  local tempdir
  tempdir=$(mktemp -d)
  mkdir -p "${tempdir}/live/"
  "$OSIRROX_BINARY" -indev "${EXTRACT_ISO_NAME}" -extract live "${tempdir}/live/" ; rc=$?
  if [ "$rc" != 0 ]; then
    rm -rf "$tempdir"
    log "osirrox failed"
    eerror "osirrox failed"
    eend 1
    bailout 1
  fi

  local squashfs
  squashfs=( "${tempdir}"/live/*/*.squashfs )
  if (( ${#squashfs[@]} != 0 )) && [ -r "${squashfs[0]}" ]; then
    log "Will $UNSQUASHFS_BINARY ${squashfs[0]}"
    "$UNSQUASHFS_BINARY" -d "${CHROOT_OUTPUT}" "${squashfs[0]}" ; rc=$?
  else
    log "Error: Could not find any *.squashfs files on the ISO"
    eerror "Error: Could not find any *.squashfs files on the ISO"
    eend 1
    bailout 1
  fi

  rm -rf "$tempdir"
  if [ "$rc" != 0 ]; then
    log "unsquashfs failed"
    eerror "unsquashfs failed"
    eend 1
    bailout 1
  fi
fi
}
extract_iso
# }}}

# helper to cap mtime on all files in a folder {{{
cap_timestamps() {
  # ensure that no timestamps are newer than $SOURCE_DATE_EPOCH
  find "$1" -newermt "@${SOURCE_DATE_EPOCH}" -print0 | TZ=UTC xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
}
# }}}

# on-the-fly configuration {{{

case "${SUITE}" in
  # avoid having to maintain DEBIAN_UNSTABLE *and* DEBIAN_SID class files:
        sid)               CLASSES="DEBIAN_UNSTABLE,$CLASSES" ;;
  # otherwise map e.g. bookworm to DEBIAN_BOOKWORM:
          *)               CLASSES="DEBIAN_$(echo "$SUITE" | tr '[:lower:]' '[:upper:]'),$CLASSES";;
esac
export SUITE # make sure it's available in FAI scripts

# validate whether the specified architecture class matches the
# architecture (option), otherwise installation of kernel will fail
if hasclass I386 ; then
   if ! [[ "$ARCH" == "i386" ]] ; then
      log    "Error: You specified the I386 class but are trying to build something else (AMD64/ARM64?)."
      eerror "Error: You specified the I386 class but are trying to build something else (AMD64/ARM64?)."
      eerror "Tip:   Either invoke grml-live with '-a i386' or adjust the architecture class. Exiting."
      eend 1
      bailout
   fi
elif hasclass AMD64 ; then
   if ! [[ "$ARCH" == "amd64" ]] ; then
      log    "Error: You specified the AMD64 class but are trying to build something else (I386/ARM64?)."
      eerror "Error: You specified the AMD64 class but are trying to build something else (I386/ARM64?)."
      eerror "Tip:   Either invoke grml-live with '-a amd64' or adjust the architecture class. Exiting."
      eend 1
      bailout
   fi
elif hasclass ARM64 ; then
   if ! [[ "$ARCH" == "arm64" ]] ; then
      log    "Error: You specified the ARM64 class but are trying to build something else (I386/AMD64?)."
      eerror "Error: You specified the ARM64 class but are trying to build something else (I386/AMD64?)."
      eerror "Tip:   Either invoke grml-live with '-a arm64' or adjust the architecture class. Exiting."
      eend 1
      bailout
   fi
fi

if [[ -n "${BOOT_METHOD:-}" ]] ; then
  log    "Error: You specified the unsupported BOOT_METHOD option. Please unset it."
  eerror "Error: You specified the unsupported BOOT_METHOD option. Please unset it."
  eend 1
  bailout
fi

if [ -z "$BOOTSTRAP_MIRROR" ] ; then
  if [ -n "$WAYBACK_DATE" ] ; then
    BOOTSTRAP_MIRROR="http://snapshot.debian.org/archive/debian/$WAYBACK_DATE/"
  else
    BOOTSTRAP_MIRROR="http://deb.debian.org/debian"
  fi
fi
# }}}

# execute minifai {{{
if [ -n "$BOOTSTRAP_ONLY" ] ; then
  FAI_ACTION=bootstrap
elif [ -n "$BUILD_ONLY" ] ; then
  FAI_ACTION=reconfigure
elif [ -n "$UPDATE" ] ; then
  FAI_ACTION=softupdate
elif [ -n "$BUILD_DIRTY" ] ; then
  FAI_ACTION=rebuild
elif [ -d "$CHROOT_OUTPUT/etc/debian_version" ] ; then
  log   "Skipping chroot bootstrap as $CHROOT_OUTPUT exists already."
  ewarn "Skipping chroot bootstrap as $CHROOT_OUTPUT exists already." ; eend 0
  FAI_ACTION=rebuild
else
  FAI_ACTION=dirinstall
fi

if ! [ "$FAI_ACTION" = dirinstall ] && ! [ -r "$CHROOT_OUTPUT/etc/debian_version" ] ; then
  log    "Error: does not look like you have a working chroot. Updating/building not possible."
  eerror "Error: does not look like you have a working chroot. Updating/building not possible. (Drop -u/-b/-B option?)"
  eend 1
  bailout 20
fi

mkdir -p "$CHROOT_OUTPUT" || bailout 5 "Problem with creating $CHROOT_OUTPUT"

if [ -n "${MIRROR_DIRECTORY}" ] ; then
  mkdir -p "${CHROOT_OUTPUT}/${MIRROR_DIRECTORY}"
  mount --bind "${MIRROR_DIRECTORY}" "${CHROOT_OUTPUT}/${MIRROR_DIRECTORY}"
fi

log "Executed FAI command line:"
log "${FAI_PROGRAM} ${GRML_FAI_CONFIG} ${CLASSES} ${FAI_ACTION} ${CHROOT_OUTPUT} ${CONFIGDUMP} ${SUITE} ${BOOTSTRAP_MIRROR}"
einfo "${FAI_PROGRAM} ${GRML_FAI_CONFIG} ${CLASSES} ${FAI_ACTION} ${CHROOT_OUTPUT} ${CONFIGDUMP} ${SUITE} ${BOOTSTRAP_MIRROR}"
"${FAI_PROGRAM}" "${GRML_FAI_CONFIG}" "${CLASSES}" "${FAI_ACTION}" "${CHROOT_OUTPUT}" "${CONFIGDUMP}" "${SUITE}" "${BOOTSTRAP_MIRROR}" 2>&1 | tee -a "${LOGFILE}"
RC="${PIPESTATUS[0]}" # notice: bash-only

# Fetches logs from "${CHROOT_OUTPUT}"/grml-live/log.
# Also run this on failure.
store_logfiles

if [ "$RC" != 0 ] ; then
  log    "Error: critical error while executing fai [exit code ${RC}]. Exiting."
  eerror "Error: critical error while executing fai [exit code ${RC}]. Exiting." ; eend 1
  bailout 1
fi

mv "${CHROOT_OUTPUT}"/grml-live/grml_sources/ "${OUTPUT}"

# provide inform fai about the ISO we build, needs to be provided
# *after* FAI stage, otherwise FAI skips the debootstrap stage if
# there is not BASEFILE (as it checks for presence of /etc) :(
echo '# This file has been generated by grml-live.' > "$CHROOT_OUTPUT/etc/grml_live_version"
[ -n "$GRML_LIVE_VERSION" ] && echo "GRML_LIVE_VERSION=$GRML_LIVE_VERSION" >> "$CHROOT_OUTPUT/etc/grml_live_version"
[ -n "$SUITE" ] && echo "SUITE=$SUITE" >> "$CHROOT_OUTPUT/etc/grml_live_version"

umount_all

log "Finished execution of stage 'fai $FAI_ACTION' [$(date)]"
einfo "Finished execution of stage 'fai $FAI_ACTION'"
# }}}

# BUILD_OUTPUT - execute arch specific stuff and squashfs {{{
mkdir -p "$BUILD_OUTPUT" || bailout 6 "Problem with creating $BUILD_OUTPUT for stage ARCH"

# prepare ISO
if [ -n "$BOOTSTRAP_ONLY" ] ; then
  log   "Skipping stage 'boot' as building with bootstrap only."
  ewarn "Skipping stage 'boot' as building with bootstrap only." ; eend 0
else
  einfo "Installing media files from chroot build"
  log   "Installing media files from chroot build"
  cp --preserve=timestamp -r "$CHROOT_OUTPUT/grml-live/media/." "$BUILD_OUTPUT/"
  eend 0

  einfo "Finished execution of stage 'boot'" ; eend 0
fi # BOOTSTRAP_ONLY

# support installation of local files into the chroot/ISO
if [ -n "$CHROOT_INSTALL" ] ; then
  if ! [ -d "$CHROOT_INSTALL" ] ; then
     log "Configuration variable \$CHROOT_INSTALL is set but not a directory; ignoring"
     ewarn "Configuration variable \$CHROOT_INSTALL is set but not a directory; ignoring"
  else
     log "Copying local files to chroot as requested via \$CHROOT_INSTALL"
     einfo "Copying local files to chroot as requested via \$CHROOT_INSTALL"
     rsync -avz --inplace "$CHROOT_INSTALL"/ "$CHROOT_OUTPUT/"
     eend $?
  fi
fi

# The dynamic linker auxiliary cache is not reproducible and is always
# invalid at boot (see Debian bug #845034). Unfortunately this must be
# done from outside the chroot, as *any* program invocation inside will
# recreate the file.
rm -f "$CHROOT_OUTPUT"/var/cache/ldconfig/aux-cache
rmdir --ignore-fail-on-non-empty "$CHROOT_OUTPUT"/var/cache/ldconfig

cap_timestamps "$CHROOT_OUTPUT"

if [ -n "$SKIP_MKSQUASHFS" ] ; then
   log   "Skipping stage 'squashfs' as requested via option -q or -N"
   ewarn "Skipping stage 'squashfs' as requested via option -q or -N" ; eend 0
else
   mkdir -p "$BUILD_OUTPUT"/live/"${GRML_NAME}"/

   # use sane defaults if $SQUASHFS_OPTIONS isn't set
   if [ -z "$SQUASHFS_OPTIONS" ] ; then
     # use block size 1m as this gives good result with regards to time + compression
     SQUASHFS_OPTIONS="-b 1m"

     # set lzma/xz compression by default, unless -z option has been specified on command line
     if [ -z "$SQUASHFS_ZLIB" ] ; then
        SQUASHFS_OPTIONS="$SQUASHFS_OPTIONS -comp xz"
     else
        SQUASHFS_OPTIONS="$SQUASHFS_OPTIONS -comp gzip"
     fi
   fi

   # Ignore all extended attributes. This avoids:
   # 1) leaking containerization supplied selinux attributes into the squashfs,
   # 2) prevents unpacking errors in a later build-only step in containers not supporting xattrs.
   SQUASHFS_OPTIONS="$SQUASHFS_OPTIONS -no-xattrs"

   # support exclusion of files via exclude-file:
   if [ -n "${SQUASHFS_EXCLUDES_FILE}" ] && [ -r "${SQUASHFS_EXCLUDES_FILE}" ] ; then
     SQUASHFS_OPTIONS="${SQUASHFS_OPTIONS} -wildcards -ef ${SQUASHFS_EXCLUDES_FILE}"
   fi

   # log stuff
   SQUASHFS_STDERR="$(mktemp -t grml-live.XXXXXX)"

   # informational stuff
   [ -n "$SQUASHFS_OPTIONS" ]  && SQUASHFS_INFO_MSG="$SQUASHFS_OPTIONS"
   [ -n "$SQUASHFS_INFO_MSG" ] && SQUASHFS_INFO_MSG="using options: $SQUASHFS_INFO_MSG"
   einfo "Squashfs build information: running $MKSQUASHFS_BINARY $SQUASHFS_INFO_MSG"

   log "$MKSQUASHFS_BINARY $CHROOT_OUTPUT/ $BUILD_OUTPUT/live/${GRML_NAME}/${GRML_NAME}.squashfs -noappend $SQUASHFS_OPTIONS"
   # shellcheck disable=SC2086 # $SQUASHFS_OPTIONS needs splitting
   if "$MKSQUASHFS_BINARY" "$CHROOT_OUTPUT/" "$BUILD_OUTPUT"/live/"${GRML_NAME}"/"${GRML_NAME}".squashfs \
      -noappend $SQUASHFS_OPTIONS 2>"${SQUASHFS_STDERR}" ; then
      echo "${GRML_NAME}.squashfs" > "$BUILD_OUTPUT"/live/"${GRML_NAME}"/filesystem.module
      log "Finished execution of stage 'squashfs' [$(date)]"
      einfo "Finished execution of stage 'squashfs'" ; eend 0
   else
      log    "Error: there was a critical error executing stage 'squashfs' [$(date)]:"
      log    "$(cat "$SQUASHFS_STDERR")"
      eerror "Error: there was a critical error executing stage 'squashfs':"
      cat    "${SQUASHFS_STDERR}"
      eend 1
      bailout
   fi
fi

# create md5sum file:
if [ -z "$BOOTSTRAP_ONLY" ] ; then
  # shellcheck disable=SC2094 # find execution ignores written file
  ( cd "$BUILD_OUTPUT"/GRML/"${GRML_NAME}" &&
  find ../.. -type f -not -name md5sums -exec md5sum {} \; > md5sums )
fi
# }}}

# information how the ISO was generated {{{
# shellcheck disable=SC2034
generate_build_info() {
  local output_dir_placeholder="<output_dir>"
  local configdump_placeholder="<configdump_file>"
  jo -p \
    bootstrap_only="${BOOTSTRAP_ONLY}" \
    build_date="${DATE}" \
    build_dirty="${BUILD_DIRTY}" \
    build_only="${BUILD_ONLY}" \
    chroot_install="${CHROOT_INSTALL}" \
    classes="${CLASSES}" \
    clean_artifacts="${CLEAN_ARTIFACTS}" \
    default_bootoptions="${DEFAULT_BOOTOPTIONS}" \
    distri_info="${DISTRI_INFO}" \
    distri_name="${DISTRI_NAME}" \
    extract_iso_name="${EXTRACT_ISO_NAME}" \
    fai_cmdline="${FAI_PROGRAM} ${GRML_FAI_CONFIG} ${CLASSES} ${FAI_ACTION} ${CHROOT_OUTPUT} ${configdump_placeholder} ${SUITE} ${BOOTSTRAP_MIRROR}" \
    fai_version="minifai" \
    grml_architecture="${ARCH}" \
    grml_bootid="${BOOTID}" \
    grml_build_output="${BUILD_OUTPUT}" \
    grml_chroot_output="${CHROOT_OUTPUT}" \
    grml_debian_version="${SUITE}" \
    grml_iso_name="${ISO_NAME}" \
    grml_iso_output="${ISO_OUTPUT}" \
    grml_live_cmdline="${CMDLINE}" \
    grml_live_config_file="${LIVE_CONF}" \
    grml_live_version="${GRML_LIVE_VERSION}" \
    grml_local_config="${LOCAL_CONFIG}" \
    grml_name="${GRML_NAME}" \
    grml_short_name="${SHORT_NAME}" \
    grml_username="${USERNAME}" \
    grml_version="${VERSION}" \
    host_architecture="$(dpkg --print-architecture || true)" \
    mkisofs_cmdline="${XORRISO_BINARY} -as mkisofs -V ${GRML_NAME} ${VERSION} -publisher 'grml-live | grml.org' -l -r -J ${BOOT_ARGS} ${EFI_ARGS} -o ${ISO_OUTPUT}/${ISO_NAME}" \
    mkisofs_version="$(${XORRISO_BINARY} --version 2>/dev/null | head -1 || true)" \
    mksquashfs_cmdline="${MKSQUASHFS_BINARY} ${CHROOT_OUTPUT}/ ${BUILD_OUTPUT}/live/${GRML_NAME}/${GRML_NAME}.squashfs -noappend ${SQUASHFS_OPTIONS}" \
    mksquashfs_version="$(${MKSQUASHFS_BINARY} -version | head -1 || true)" \
    output_owner="${CHOWN_USER}" \
    release_info="${RELEASE_INFO}" \
    release_name="${RELEASENAME}" \
    secure_boot="${SECURE_BOOT}" \
    skip_mkisofs="${SKIP_MKISOFS}" \
    skip_mksquashfs="${SKIP_MKSQUASHFS}" \
    skip_netboot="${SKIP_NETBOOT}" \
    squashfs_name="${SQUASHFS_NAME}" \
    timestamp="${SOURCE_DATE_EPOCH}" \
    update_only="${UPDATE}" \
    wayback_date="${WAYBACK_DATE}" \
  -- | \
   sed "s|$OUTPUT|${output_dir_placeholder}|g"
}
# }}}

# ISO_OUTPUT - iso build {{{
[ -n "$ISO_NAME" ] || ISO_NAME="${GRML_NAME}_${VERSION}.iso"

EFI_ARGS="-eltorito-alt-boot -e boot/efi.img -no-emul-boot -isohybrid-gpt-basdat"

if [ "$ARCH" = "arm64" ]; then
  # No BIOS boot on arm64, only UEFI
  BOOT_ARGS=""
else
  # Use GRUB for BIOS boot via El Torito
  BOOT_ARGS="-b boot/grub/i386-pc/eltorito.img -no-emul-boot -boot-load-size 4 -boot-info-table --grub2-boot-info --grub2-mbr ${CHROOT_OUTPUT}/usr/lib/grub/i386-pc/boot_hybrid.img"
fi

if [ -n "$SKIP_MKISOFS" ] ; then
   log   "Skipping stage 'iso build' as requested via option -n or -N"
   ewarn "Skipping stage 'iso build' as requested via option -n or -N" ; eend 0
else
   mkdir -p "$ISO_OUTPUT" || bailout 6 "Problem with creating $ISO_OUTPUT for stage 'iso build'"

   log   "Generating build information in conf/buildinfo.json"
   einfo "Generating build information in conf/buildinfo.json"
   mkdir -p "$BUILD_OUTPUT"/conf/
   generate_build_info > "$BUILD_OUTPUT"/conf/buildinfo.json
   eend $?

   cap_timestamps "$BUILD_OUTPUT"

   log "$XORRISO_BINARY -as mkisofs -V '${GRML_NAME} ${VERSION}' -publisher 'grml-live | grml.org' -l -r -J $BOOT_ARGS $EFI_ARGS -o ${ISO_OUTPUT}/${ISO_NAME} ."
   einfo "Generating ISO file..."
   touch -d @"$SOURCE_DATE_EPOCH" "$BUILD_OUTPUT"/
   # shellcheck disable=SC2086 # BOOT_ARGS and EFI_ARGS need splitting
   "$XORRISO_BINARY" -as mkisofs -V "${GRML_NAME} ${VERSION}" -publisher 'grml-live | grml.org' \
           -l -r -J $BOOT_ARGS $EFI_ARGS \
           -o "${ISO_OUTPUT}/${ISO_NAME}" "$BUILD_OUTPUT"/ ; RC=$?
   eend $RC

   # do not continue on errors, otherwise we might generate/overwrite the ISO with dd if=... stuff
   if [ "$RC" != 0 ] ; then
     log    "Error: critical error while generating ISO [exit code ${RC}]. Exiting."
     eerror "Error: critical error while generating ISO [exit code ${RC}]. Exiting." ; eend 1
     bailout "$RC"
   fi

   # generate ISO checksums if we are using class 'RELEASE':
   if hasclass RELEASE && [ "$RC" = 0 ] ; then
      (
        if cd "$ISO_OUTPUT" ; then
          sha256sum "${ISO_NAME}" > "${ISO_NAME}.sha256" && \
          touch -r "${ISO_NAME}" "${ISO_NAME}.sha256"
        fi
      )
   fi

   if [ "$RC" = 0 ] ; then
      log   "Finished execution of stage 'iso build' [$(date)]"
      einfo "Finished execution of stage 'iso build'" ; eend 0
   else
      log    "Error: there was a critical error ($RC) executing stage 'iso build' [$(date)]"
      eerror "Error: there was a critical error executing stage 'iso build'" ; eend 1
      bailout "$RC"
   fi
fi
# }}}

# netboot package {{{
create_netbootpackage() {
  local OUTPUT_NAME
  local OUTPUT_FILE
  OUTPUT_NAME=$(basename "${ISO_NAME}" .iso)-netboot
  OUTPUT_FILE="${NETBOOT}/${OUTPUT_NAME}.tar"

  if [ -n "$SKIP_NETBOOT" ] ; then
    log   "Skipping stage 'netboot' as requested via option -Q"
    ewarn "Skipping stage 'netboot' as requested via option -Q" ; eend 0
    return 0
  fi

  mkdir -p "$NETBOOT"

  local OUTPUTDIR="${NETBOOT}/build_tmp"

  local WORKING_DIR="${OUTPUTDIR}/${OUTPUT_NAME}/"
  mkdir -p "$WORKING_DIR"
  cp --preserve=timestamp -r "$CHROOT_OUTPUT/grml-live/netboot/." "$WORKING_DIR/"

  cap_timestamps "$OUTPUTDIR"
  if tar -C "$OUTPUTDIR" -cf "${OUTPUT_FILE}" "${OUTPUT_NAME}" ; then
    (
      # shellcheck disable=SC2164 # We just wrote there. If it disappeared, too bad.
      cd "$(dirname "${OUTPUT_FILE}")"
      sha256sum "$(basename "${OUTPUT_FILE}")" > "${OUTPUT_FILE}.sha256"
    )
    einfo "Generated netboot package ${OUTPUT_FILE}" ; eend 0
    rm -rf "${OUTPUTDIR}"
  else
    rm -rf "${OUTPUTDIR}"
    eerror "Could not generate netboot package ${OUTPUT_FILE}" ; eend 1
    bailout 21
  fi
}

create_netbootpackage
# }}}

# {{{
create_sourcespackages() {
  if ! hasclass SOURCES ; then
    log "Skipping source package generation, only enabled with class SOURCES"
    return 0
  fi

  local OUTPUT_FILE SOURCES_DIR
  OUTPUT_FILE="${OUTPUT}/$(basename "${ISO_NAME}" .iso)-sources.tar"
  SOURCES_DIR="${OUTPUT}/grml_sources/"

  if ! [ -d "${SOURCES_DIR}" ] ; then
    eerror "Base directory ${SOURCES_DIR} not present, can not generate source package" ; eend 1
    bailout 22
  fi

  if tar -C "${OUTPUT}" -cf "${OUTPUT_FILE}" "$(basename "${SOURCES_DIR}")" ; then
   (
     # shellcheck disable=SC2164 # We just wrote there. If it disappeared, too bad.
     cd "$(dirname "${OUTPUT_FILE}")"
     sha256sum "$(basename "${OUTPUT_FILE}")" > "${OUTPUT_FILE}.sha256"
   )
   einfo "Generated source package ${OUTPUT_FILE}" ; eend 0
 else
   eerror "Could not generate source package ${OUTPUT_FILE}" ; eend 1
   bailout 22
 fi
}

create_sourcespackages
# }}}

# finalize {{{
if [ -n "${start_seconds}" ] ; then
  end_seconds="$(date +%s)"
  SECONDS="$(( end_seconds - start_seconds ))"
fi
log "Successfully finished execution of $PN [$(date) - running ${SECONDS} seconds]"

einfo "Successfully finished execution of $PN [$(date) - running ${SECONDS} seconds]" ; eend 0
bailout 0
# }}}

## END OF FILE #################################################################
# vim:foldmethod=marker ts=2 ft=sh ai expandtab tw=80 sw=2
