#!/usr/bin/env bash
#
#   Script name: ubiso
#   Description: Script for build iso UBLinux
#   GitLab: https://gitlab.ublinux.ru/
#   Author: asmeron@ublinux.ru
#   Contributors: asmeron@ublinux.ru
#
#   Copyright (c) 2021-2023 UBLinux Development Team <support@ublinux.ru>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

VERSION_SCRIPT="1.3"

# Exit Immediately if a command fails
#set -o errexit

#################################
###   :::   C O L O R S   :::   #
#################################
set_color() {
#http://abload.de/img/bash-color-chartmxjbp.png
    export BBC=$'\e[1;34m'
    export RBC=$'\e[1;31m'
    export WBC=$'\e[1m'
    export EC=$'\e[0m'

    export txtblk='\033[0;30m' # Black - Regular
    export txtred='\033[0;31m' # Red			# prompt: error color
    export txtgrn='\033[0;32m' # Green			# prompt: success color
    export txtylw='\033[0;33m' # Yellow			# prompt: waring color
    export txtblu='\033[0;34m' # Blue			
    export txtpur='\033[0;35m' # Purple
    export txtcyn='\033[0;36m' # Cyan			# prompt: info color
    export txtwht='\033[0;37m' # White
    export bldblk='\033[1;30m' # Black - Bold
    export bldred='\033[1;31m' # Red			# prompt: bold error color
    export bldgrn='\033[1;32m' # Green			# prompt: bold success color
    export bldylw="\033[1;33m" # Yellow                 # prompt: bold warning color
    export bldblu='\033[1;34m' # Blue				
    export bldpur='\033[1;35m' # Purple
    export bldcyn="\033[1;36m" # Cyan                   # prompt: bold info color
    export bldwht="\033[1;37m" # White			# prompt: bold default color

    export undblk='\033[4;30m' # Black - Underline
    export undred='\033[4;31m' # Red

    export bakblk='\033[40m'   # Black - Background
    export bakred='\033[41m'   # Red
    export badgrn='\033[42m'   # Green

    export txtrst='\033[0m'    # Text Reset		# prompt: default color
}


#######################################
###   :::   F U N C T I O N S   :::   #
#######################################

check_root() {
    if [[ ${EUID:-$(id -u)} > 0 ]]; then
        prompt -wq "Please run as root!"
    fi
}

# Check command availability
has_command() { command -v $1 &> /dev/null; }

# echo like ... with flag type and display message colors
prompt() {
    [[ -n ${QUIET} ]] && return
    case ${1} in
	-s  | --success)	echo -e "${bldgrn}${@/#-s/}${txtrst}" ;;    			# print success message
	-sq | --success-quit)	echo -e "${bldgrn}${@/#-sq/}${txtrst}" && exit 1 ;;    		# print success message
	-e  | --error)   	echo -e "${bldred}ERROR:${@/#-e/}${txtrst}" ;;   		# print error message
	-eq | --error-quit)   	echo -e "${bldred}ERROR:${@/#-eq/}${txtrst}" && exit 1 ;;	# print error message
	-w  | --warning) 	echo -e "${bldylw}WARNING:${bldwht}${@/#-w/}${txtrst}" ;; 	# print warning message
	-wq | --warning-quit) 	echo -e "${bldylw}WARNING:${bldwht}${@/#-wq/}${txtrst}" && exit 1 ;; # print warning message
	-i  | --info)    	echo -e "${bldcyn}INFO:${txtcyn}${@/#-i/}${txtrst}" ;;    	# print info message
	-iq | --info-quit)    	echo -e "${bldcyn}INFO:${txtcyn}${@/#-iq/}${txtrst}" && exit 1 ;;  # print info message
	*)    			echo -e "$@" ;;							# print all message
    esac
}


usage() {
    printf "%s  %s\n" "${0##*/}" "version: ${VERSION_SCRIPT}"
    cat <<EOF
Build iso image UBLinux

Usage: ${0##*/} {COMMAND} [OPTIONS...] ...

Meta Commands:
  help          	Show this help

OPTIONS:
  -p, --path=PATH  	Path to the ISO
			Without PATH for command 'create' PATH="./"
  -m, --manual		Manual build ISO from ./src path. Not take the current system settings and modules
			Include --nokernel --nosign --noskel --nomodules
  -n, --nokernel	Don't copy kernel vmlinuz and initrd to iso /ublinux/
#      --nosign		Don't update signature file
      --noskel		Don't update ublinux and ublinux-data skeleton
      --nomodules	Don't update modules from current system
  -c, --noclean		Don't clean source build
  -f, --postfix		Postfix text for file name .iso
      --hashsum		Generate hashsum:md5sum,sha1sum,sha256sum,b2sum,gost12sum from ISO file 
  -s, --sign-key=KEY	Signing key for ISO file
  -u, --sign-user=USER	User signing ISO file
      --sign-password=PASSWORD	
    			Password for sign user
      --ram		Build ISO in RAM
  -q, --quiet		Quiet mode
  -h, --help		Show this help
  -V, --version		Show package version

Examples:
${0##*/} --
${0##*/} -qp /home/iso
${0##*/} -p /home/iso -s 50BC1DB583B79706 -u superadmin
EOF
    exit 0
}

arguments() {
# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
    local ARGV=()
    local END_OF_OPT=
    while [[ $# -gt 0 ]]; do
	arg="$1"; shift
	case "${END_OF_OPT}${arg}" in
	    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
	    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
	    --*) ARGV+=("$arg") ;;
	    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
	    *) ARGV+=("$arg") ;;
	esac
    done
# Apply pre-processed options
    set -- "${ARGV[@]}"
# Parse options
    local END_OF_OPT=
    local POSITIONAL_ARGS=()
    [[ -z $@ ]] && usage && exit 0
    while [[ $# -gt 0 ]]; do
	case "${END_OF_OPT}${1}" in
	    -h | --help | help)	usage ;;
	    -p | --path)	shift; PATH_WORK=$(realpath "$1"); mkdir -p "${PATH_WORK}" ;;
	    -m | --manual)  	MANUAL=1 ;;
	    -n | --nokernel)   	NO_KERNEL=1 ;;
#		 --nosign)	NO_SIGN=1 ;;
		 --noskel)	NO_SKEL=1 ;;
		 --nomodules)	NO_MODULES=1 ;;
	    -c | --noclean)	NO_CLEAN=1 ;;
	    -r | --osrelease)	OSRELEASE=1 ;;
	    -f | --postfix)	shift; POSTFIX_TXT="_$1" ;;
		 --hashsum)     HASHSUM=1 ;;
	    -s | --sign-key)	shift; SIGN_KEY=$1 ;;
	    -u | --sign-user)	shift; SIGN_USER=$1 ;;
		 --sign-password) shift; SIGN_PASSWORD=$1 ;;
	         --ram)		BUILD_RAM=1 ;;
	    -q | --quiet)     	QUIET=1; QUIET_ARG="-q" ;;
	    -V | --version)	echo "Version: ${VERSION_SCRIPT}"; exit 0 ;;
	    --stdin)        	READ_STDIN=1 ;;
	    --)             	END_OF_OPT=1 ;;
	    -*|--*)         	echo "WARNING: Unrecognized argument, skiped: $1" >&2  ;;
	    *)              	POSITIONAL_ARGS+=("$1") ;;
	esac
	shift
    done
# Restore positional parameters
    set -- "${POSITIONAL_ARGS[@]}"
    [[ ${OSRELEASE} ]] && OS_RELEASE_PRETTY_NAME="$@"
}

detect_osrelease() {
    if [[ -z ${OS_RELEASE_PRETTY_NAME} ]]; then
	if [[ -n ${MANUAL} ]] && ls "${PATH_SRC_ISO}"/ublinux/VERSION_* &> /dev/null; then
	    OS_RELEASE_PRETTY_NAME="$(cat ${PATH_SRC_ISO}/ublinux/VERSION_* | head -1)"
	elif [[ -f /usr/lib/os-release ]]; then
	    OS_RELEASE_PRETTY_NAME="$(cat /usr/lib/os-release | grep "PRETTY_NAME=" | cut -d= -f2 | sed 's/"//g')"
	elif ls "${PATH_SRC_ISO}"/ublinux/VERSION_* &> /dev/null; then
	    OS_RELEASE_PRETTY_NAME="$(cat ${PATH_SRC_ISO}/ublinux/VERSION_* | head -1)"
	else
	    OS_RELEASE_PRETTY_NAME="UBLinux 0000 Unknown (x)"
	fi
    fi
    DISTRIB_NAME="$(${PATH_LIB_UBBOOT}/ubdistconv -n ${OS_RELEASE_PRETTY_NAME})"
    DISTRIB_CODENAME="$(${PATH_LIB_UBBOOT}/ubdistconv -d ${OS_RELEASE_PRETTY_NAME})"
    DISTRIB_VERSION="$(${PATH_LIB_UBBOOT}/ubdistconv -v ${OS_RELEASE_PRETTY_NAME})"
    prompt -i "OS detected: ${OS_RELEASE_PRETTY_NAME}"
}

prepare_structure() {
#    local FILE_EFI_GRUB=$(cat <<'EOF'
# Search for the volume
#if [ -z "${BOOT_UUID}" ]; then
#    if [ -z "${BOOT_HINT}" ]; then
#        regexp --set=1:BOOT_HINT '^\(([^)]+)\)' "${cmdpath}"
#    fi
#    search --no-floppy --set=root --file '/boot/grub/grub.cfg' --hint "${BOOT_HINT}"
#    probe --set BOOT_UUID --fs-uuid "${root}"
#fi
#if [ -e ($root)/boot/grub/grub.cfg ]; then
#    set prefix=($root)/boot/grub
#    configfile ($root)/boot/grub/grub.cfg
#else
#    search.file ($root)/grub.cfg root
#    set prefix=($root)
#    configfile ($root)/grub.cfg
#fi
#
#EOF
#)
#    local FILE_BOOT_GRUB=$(cat <<'EOF'
# Trying to remove embedded chainloader module and insert patched module with internal PE loader if it's exists
#if test -f /boot/grub/mmod/chain.mod; then rmmod chain; insmod /boot/grub/mmod/chain.mod; fi

# Now trying to start unsigned grub*.efi (secure boot isn't enabled)
#chainloader /boot/grub/ublinux/grubx64.efi
#boot
#chainloader /boot/grub/ublinux/grubia32.efi
#boot

# In case when grubx64.efi wasn't started (secure boot is enabled)

#configfile /boot/grub/ublinux/grub.cfg
#EOF
#)
    trim_src_iso() {
	find ${PATH_SRC_ISO} -name "*~" -delete 2>/dev/null
    }
    prepare_skel() {
	if [[ -z ${NO_SKEL} ]]; then
	    if [[ -d /usr/lib/grub  && -f ${PATH_LIB_UBBOOT}/ubboot-skel ]]; then
    		prompt -i "start update skeleton ublinux, ublinux-data, grub, grub-theme"
    		install -dm0755 "${PATH_SRC_ISO}"
		rm -rdf "${PATH_SRC_ISO}/boot" "${PATH_SRC_ISO}/EFI"
		${PATH_LIB_UBBOOT}/ubboot-skel create ${QUIET_ARG} -ug -s iso -t "${DISTRIB_CODENAME}" -p "${PATH_SRC_ISO}" || prompt -eq "skelet iso not create!"
		install -dm0755 "${PATH_SRC_ISO}/EFI/BOOT/"
		GRUB_CONF_EMBED="${GRUB_CONF_EMBED//'%SEARCH_FILENAME%'/${GRUB_SEARCH_FILENAME}}" #"
		printf '%s\n' "${GRUB_CONF_EMBED}" > "${PATH_SRC_ISO}/EFI/BOOT/grub.cfg"
		#echo "${FILE_EFI_GRUB}" > ${PATH_SRC_ISO}/EFI/BOOT/grub.cfg
		#echo "${FILE_BOOT_GRUB}" > ${PATH_SRC_ISO}/boot/grub/grub.cfg
		cp -rf --no-preserve=mode,ownership "${PATH_TAMPL_ISO}"/* "${PATH_SRC_ISO}"/
		rm -f ${PATH_SRC_ISO}/ublinux/VERSION_*
		echo "${OS_RELEASE_PRETTY_NAME}" > "${PATH_SRC_ISO}/ublinux/$(${PATH_LIB_UBBOOT}/ubdistconv -s ${OS_RELEASE_PRETTY_NAME})"
	    else
		prompt -eq "depends utility grub or ubboot not found!"
	    fi
	    # Write grubenv
#	    printf '%.1024s' \
#    		"$(printf '# GRUB Environment Block\nNAME=%s\nVERSION=%s\nARCHISO_LABEL=%s\nINSTALL_DIR=%s\nARCH=%s\nARCHISO_SEARCH_FILENAME=%s\n%s' \
#        	"${ISO_NAME}" \
#        	"${ISO_VERSION}" \
#        	"${ISO_LABEL}" \
#        	"${INSTALL_DIR}" \
#        	"${ARCH}" \
#        	"${SEARCH_FILENAME}" \
#        	"$(printf '%0.1s' "#"{1..1024})")" \
#    		> "${${PATH_SRC_ISO}/boot/grub/grubenv"

	fi
    }
    prepare_modules() {
	local PATH_CURRENTSYS_UBM="/mnt/livemedia/ublinux"
	local PATH_CURRENTSYSDATA_UBM="/mnt/livemedia/ublinux-data"
	if [[ -z ${NO_MODULES} && -d ${PATH_CURRENTSYS_UBM} ]]; then
    	    prompt -i "start copy all local modules *.ubm to '${PATH_SRC_ISO}/ublinux/{base,modules,optional,machines}'"
	    rm -rf "${PATH_SRC_ISO}/ublinux/base/*" "${PATH_SRC_ISO}/ublinux/modules/*" "${PATH_SRC_ISO}/ublinux/optional/*" \
		"${PATH_SRC_ISO}/ublinux/machines/dynamic/*" "${PATH_SRC_ISO}/ublinux/machines/static/*"
	    [[ -d ${PATH_CURRENTSYS_UBM}/base ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYS_UBM}"/base/*.ubm "${PATH_SRC_ISO}"/ublinux/base/ 2>/dev/null
	    [[ -d ${PATH_CURRENTSYS_UBM}/modules ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYS_UBM}"/modules/*.ubm "${PATH_SRC_ISO}"/ublinux/modules/ 2>/dev/null
	    [[ -d ${PATH_CURRENTSYS_UBM}/optional ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYS_UBM}"/optional/*.ubm "${PATH_SRC_ISO}"/ublinux/optional/ 2>/dev/null
	    [[ -d ${PATH_CURRENTSYS_UBM}/machines/dynamic ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYS_UBM}"/machines/dynamic/*.ubm "${PATH_SRC_ISO}"/ublinux/machines/dynamic/ 2>/dev/null
	    [[ -d ${PATH_CURRENTSYS_UBM}/machines/static ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYS_UBM}"/machines/static/*.ubm "${PATH_SRC_ISO}"/ublinux/machines/static/ 2>/dev/null
	    if [[ -d ${PATH_CURRENTSYSDATA_UBM} ]]; then
		[[ -d ${PATH_CURRENTSYSDATA_UBM}/modules ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/modules/*.ubm "${PATH_SRC_ISO}"/ublinux/modules/ 2>/dev/null
		[[ -d ${PATH_CURRENTSYSDATA_UBM}/optional ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/optional/*.ubm "${PATH_SRC_ISO}"/ublinux/optional/ 2>/dev/null
		[[ -d ${PATH_CURRENTSYSDATA_UBM}/machines/dynamic ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/machines/dynamic/*.ubm "${PATH_SRC_ISO}"/ublinux/machines/dynamic/ 2>/dev/null
		[[ -d ${PATH_CURRENTSYSDATA_UBM}/machines/static ]] && install -CDm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/machines/static/*.ubm "${PATH_SRC_ISO}"/ublinux/machines/static/ 2>/dev/null
		if [[ -f ${PATH_CURRENTSYSDATA_UBM}/ublinux.ini ]]; then 
		    local PATH_EXTRACT_UBLINUXDATA=$(mktemp -d --tmpdir ublinux.data.XXXX)
		    install -Dm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/ublinux.ini "${PATH_SRC_ISO}"/ublinux/ublinux.ini 2>/dev/null
		    tar xJf ${PATH_SRC_ISO}/ublinux/ublinux-data.tar.xz -C ${PATH_EXTRACT_UBLINUXDATA}/
		    install -Dm0400 -o root -g root "${PATH_CURRENTSYSDATA_UBM}"/ublinux.ini "${PATH_EXTRACT_UBLINUXDATA}"/ublinux-data/ublinux.ini 2>/dev/null
		    cd "${PATH_EXTRACT_UBLINUXDATA}"
		    tar -cvJf "${PATH_SRC_ISO}"/ublinux/ublinux-data.tar.xz "ublinux-data" >/dev/null 2>&1
		    rm -rdf ${PATH_EXTRACT_UBLINUXDATA}
		fi
	    fi
	fi
    }
    prepare_kernel() {
        local PATH_ROOT_UBLINUX="${PATH_SRC_ISO}/ublinux"
	if [[ -z ${NO_KERNEL} ]]; then
	    local PATH_EXTRACT_KERNEL=$(mktemp -d --tmpdir ublinux.kernel.XXXX)
    	    prompt -i "start copy ublinux-*, vmlinuz-* to '${PATH_ROOT_UBLINUX}'"
	    if ls "${PATH_ROOT_UBLINUX}"/base/001-linux-*-*-*.ubm &> /dev/null; then
		unsquashfs -q -f -d "${PATH_EXTRACT_KERNEL}" "${PATH_ROOT_UBLINUX}"/base/001-linux-*-*-*.ubm "usr/lib/modules/*-ublinux/ublinux-*" "usr/lib/modules/*-ublinux/vmlinuz-*"
		mv -f "${PATH_EXTRACT_KERNEL}"/usr/lib/modules/*-ublinux/ublinux-* "${PATH_EXTRACT_KERNEL}"/usr/lib/modules/*-ublinux/vmlinuz-* "${PATH_ROOT_UBLINUX}/" \
		    || prompt -eq "kernel and initrd 'ublinux-*, vmlinuz-*' from /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm not copy!"
		prompt -i "finish copy kernel and initrd 'ublinux-*, vmlinuz-*' from /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm"
	    elif ls /usr/lib/modules/*-ublinux/ublinux-* &> /dev/null && ls /usr/lib/modules/*-ublinux/vmlinuz-* &> /dev/null; then
		cp -f /usr/lib/modules/*-ublinux/ublinux-* /usr/lib/modules/*-ublinux/vmlinuz-* "${PATH_ROOT_UBLINUX}/" \
		    || prompt -eq "kernel and initrd '/usr/lib/modules/*-ublinux/{ublinux-*,vmlinuz-*}' not copy!"
		prompt -i "finish copy kernel and initrd '/usr/lib/modules/*-ublinux/{ublinux-*,vmlinuz-*}'"
	    elif ls /mnt/livemedia/ublinux/ublinux-* &> /dev/null && ls /mnt/livemedia/ublinux/vmlinuz-* &> /dev/null; then
		cp -f /mnt/livemedia/ublinux/ublinux-* /mnt/livemedia/ublinux/vmlinuz-* "${PATH_ROOT_UBLINUX}/" \
		    || prompt -eq "kernel and initrd '/mnt/livemedia/ublinux/{ublinux-*,vmlinuz-*}' not copy!"
		prompt -i "finish copy kernel and initrd '/mnt/livemedia/ublinux/{ublinux-*,vmlinuz-*}'"
	    elif ls /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm &> /dev/null; then
		unsquashfs -q -f -d "${PATH_EXTRACT_KERNEL}" /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm "usr/lib/modules/*-ublinux/ublinux-*" "usr/lib/modules/*-ublinux/vmlinuz-*"
		mv -f "${PATH_EXTRACT_KERNEL}"/usr/lib/modules/*-ublinux/ublinux-* "${PATH_EXTRACT_KERNEL}"/usr/lib/modules/*-ublinux/vmlinuz-* "${PATH_ROOT_UBLINUX}/" \
		    || prompt -eq "kernel and initrd 'ublinux-*, vmlinuz-*' from /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm not copy!"
		prompt -i "finish copy kernel and initrd 'ublinux-*, vmlinuz-*' from /mnt/livemedia/ublinux/base/001-linux-*-*-*.ubm"
	    else 
		prompt -eq "kernel 'vmlinuz' and initrd 'ublinux' not found!"
	    fi
	    rm -rdf ${PATH_EXTRACT_KERNEL}
	fi
        chmod -f 0400 "${PATH_ROOT_UBLINUX}"/ublinux-* "${PATH_ROOT_UBLINUX}"/vmlinuz-*
    }
    if [[ -z ${MANUAL} ]]; then
	trim_src_iso
	prepare_skel
	prepare_modules
	prepare_kernel
    fi
}

##########################################################################################################################################################################
# Создание core.img для USB образа
# Образ для загрузочного USB, используется вместе с /usr/lib/grub/i386-pc/boot.img
# 
# boot.img (из grub2) и core.img (делается grub-mkimage) записываются в MBR флешки или диска
# core.img представляет из себя загрузчик сделанный grub-mkimage
# В таком виде результатом работы команды будет файл core.img
##########################################################################################################################################################################
make_grub_core() {
    local PATH_GRUB_CORE=$(mktemp -d --tmpdir ublinux.grub.core.XXXX)
    local GRUB_MODULES=()
    local OUTPUT_FILE=$1
    GRUB_MODULES=(keylayouts loadenv minicmd at_keyboard usb usbserial_common usbserial_ftdi usbserial_pl2303 usbserial_usbdebug serial jpeg png \
		 part_apple part_gpt part_msdos disk memdisk fat exfat lvm ext2 ntfs ntfscomp iso9660 xfs luks2 btrfs f2fs hfsplus udf cryptodisk normal gzio xzio zstd \
		 test regexp search search_fs_file search_fs_uuid search_label configfile linux linux16 chain boot probe \
		 gcry_rijndael gcry_sha256 pbkdf2 loopback echo read cat all_video video file halt reboot sleep ls true gfxterm gfxmenu gettext font multiboot \
		 efiemu vbe vga biosdisk ntldr)
    prompt -i "generate core.img --format=i386-pc"
    grub-mkimage    --directory=/usr/lib/grub/i386-pc --compression=none \
		    --verbose --prefix=\(hd0,msdos1\)/EFI/BOOT \
		    --output="${OUTPUT_FILE}" --format=i386-pc \
		    ${GRUB_MODULES[*]} &>/dev/null
    rm -rdf ${PATH_GRUB_CORE}
}

##########################################################################################################################################################################
# Создание grubx64.efi	GPT загрузки (можно использовать при загрузке с HDD и ISO)
#
# grubx64.efi представляет из себя загрузчик сделанный grub-mkimage
# В таком виде результатом работы команды будет файл grubx64.efi который потом перименовывается в bootx64.efi grubx32.efi
#
# - ./EFI/BOOT/grub.cfg файл настроек который зашивается внутрь grubx64.efi grubx32.efi
##########################################################################################################################################################################
make_grub_grubx64() {
    local PATH_GRUBX64=$(mktemp -d --tmpdir ublinux.grubx64.efi.XXXX)
    local FILE_GRUB_CFG="${PATH_GRUBX64}/grub.cfg"
    local GRUB_MODULES=()
    local OUTPUT_FILE=$1
    cat <<'EOF' >${FILE_GRUB_CFG}
search.file --no-floppy --set=root /EFI/BOOT/grub.cfg
configfile /EFI/BOOT/grub.cfg
EOF
    GRUB_MODULES=(keylayouts loadenv minicmd at_keyboard usb usbserial_common usbserial_ftdi usbserial_pl2303 usbserial_usbdebug serial jpeg png \
		 part_apple part_gpt part_msdos disk memdisk fat exfat lvm ext2 ntfs ntfscomp iso9660 xfs luks2 btrfs f2fs hfsplus udf cryptodisk normal gzio xzio zstd \
		 test regexp search search_fs_file search_fs_uuid search_label configfile linux linux16 chain boot probe \
		 gcry_rijndael gcry_sha256 pbkdf2 loopback echo read cat all_video video file halt reboot sleep ls true gfxterm gfxmenu gettext font multiboot \
		 efifwsetup efinet lsefi lsefimmap efi_gop efi_uga tpm)
    prompt -i "generate bootx64.efi --format=x86_64-efi"
    grub-mkimage    --directory=/usr/lib/grub/x86_64-efi --compression=auto \
		    --config=${FILE_GRUB_CFG} --verbose --prefix=/EFI/BOOT \
		    --output="${OUTPUT_FILE}" --format=x86_64-efi \
		    ${GRUB_MODULES[*]} &>/dev/null

#    grub-mkstandalone -O x86_64-efi \
#               --modules="${GRUB_MODULES[*]}" \
#               --locales="en@quot" \
#               --themes="" \
#               --sbat=/usr/share/grub/sbat.csv \
#               --disable-shim-lock \
#               -o "${OUTPUT_FILE}" \
#		"boot/grub/grub.cfg=${FILE_GRUB_CFG}"

    rm -rdf ${PATH_GRUBX64}
}
##########################################################################################################################################################################
make_grub_grubia32() {
    local PATH_GRUBIA32=$(mktemp -d --tmpdir ublinux.grubia32.efi.XXXX)
    local FILE_GRUB_CFG="${PATH_GRUBIA32}/grub.cfg"
    local GRUB_MODULES=()
    local OUTPUT_FILE=$1
    cat <<'EOF' >${FILE_GRUB_CFG}
search.file --no-floppy --set=root /EFI/BOOT/grub.cfg
configfile /EFI/BOOT/grub.cfg
EOF

    GRUB_MODULES=(keylayouts loadenv minicmd at_keyboard usb usbserial_common usbserial_ftdi usbserial_pl2303 usbserial_usbdebug serial jpeg png \
		 part_apple part_gpt part_msdos disk memdisk fat exfat lvm ext2 ntfs ntfscomp iso9660 xfs luks2 btrfs f2fs hfsplus udf cryptodisk normal gzio xzio zstd \
		 test regexp search search_fs_file search_fs_uuid search_label configfile linux linux16 chain boot probe \
		 gcry_rijndael gcry_sha256 pbkdf2 loopback echo read cat all_video video file halt reboot sleep ls true gfxterm gfxmenu gettext font multiboot \
		 efifwsetup efinet lsefi lsefimmap efi_gop efi_uga tpm)
    prompt -i "generate bootia32.efi --format=i386-efi"
    grub-mkimage    --directory=/usr/lib/grub/i386-efi --compression=auto \
		    --config=${FILE_GRUB_CFG} --verbose --prefix=/EFI/BOOT \
		    --output="${OUTPUT_FILE}" --format=i386-efi \
		    ${GRUB_MODULES[@]} &>/dev/null

#    grub-mkstandalone -O i386-efi \
#        	    --modules="${GRUB_MODULES[*]}" \
#                    --locales="en@quot" \
#                    --themes="" \
#                    --sbat=/usr/share/grub/sbat.csv \
#                    --disable-shim-lock \
#                    -o "${OUTPUT_FILE}" "boot/grub/grub.cfg=${work_dir}/grub-embed.cfg"
    rm -rdf ${PATH_GRUBIA32}
}

##########################################################################################################################################################################
# Создание grub2.eltorito для CDROM
#
# grub2.eltorito представляет из себя загрузчик сделанный grub-mkimage
# В таком виде результатом работы команды будет файл grub2.eltorito
#
# - ./EFI/BOOT/grub.cfg файл настроек который зашивается внутрь grub2.eltorito
##########################################################################################################################################################################
make_grub_eltorito() {
    local PATH_GRUB_ELTORITO=$(mktemp -d --tmpdir ublinux.grub.eltorito.XXXX)
    local FILE_GRUB_CFG="${PATH_GRUB_ELTORITO}/grub.cfg"
    local GRUB_MODULES=()
    local OUTPUT_FILE=$1
    cat <<'EOF' >${FILE_GRUB_CFG}
search.file --no-floppy --set=root /EFI/BOOT/grub.cfg
configfile /EFI/BOOT/grub.cfg
EOF

    GRUB_MODULES=(keylayouts loadenv minicmd at_keyboard usb usbserial_common usbserial_ftdi usbserial_pl2303 usbserial_usbdebug serial jpeg png \
		 part_apple part_gpt part_msdos disk memdisk fat exfat lvm ext2 ntfs ntfscomp iso9660 xfs luks2 btrfs f2fs hfsplus udf cryptodisk normal gzio xzio zstd \
		 test regexp search search_fs_file search_fs_uuid search_label configfile linux linux16 chain boot probe \
		 gcry_rijndael gcry_sha256 pbkdf2 loopback echo read cat all_video video file halt reboot sleep ls true gfxterm gfxmenu gettext font multiboot \
		 efiemu vbe vga biosdisk ntldr)
    prompt -i "generate grub-eltorito --format=i386-pc-eltorito"
    grub-mkimage    --directory=/usr/lib/grub/i386-pc --compression=auto \
		    --config=${FILE_GRUB_CFG} --verbose --prefix=/EFI/BOOT \
		    --output="${OUTPUT_FILE}" --format=i386-pc-eltorito \
		    ${GRUB_MODULES[*]} &>/dev/null
    rm -rdf ${PATH_GRUB_ELTORITO}
}

make_grub_eltorito_img() {
    local PATH_GRUB_ELTORITO_IMG=$(mktemp -d --tmpdir ublinux.grub-eltorito.img.XXXX)
    local OUTPUT_FILE=$1
    local FILE_GRUBX64=$2
    local FILE_GRUBIA32=$3
    prompt -i "make grub-eltorito.img"
    dd if=/dev/zero of="${OUTPUT_FILE}" bs=1K count=4096 &> /dev/null
    mkfs.vfat -F12 -n EFI "${OUTPUT_FILE}" &>/dev/null
    install -dm0755 ${PATH_GRUB_ELTORITO_IMG}/EFI/BOOT

    GRUB_CONF_EMBED=${GRUB_CONF_EMBED//'%SEARCH_FILENAME%'/${GRUB_SEARCH_FILENAME}}
    printf '%s\n' "${GRUB_CONF_EMBED}" > "${PATH_GRUB_ELTORITO_IMG}/EFI/BOOT/grub.cfg"

    install -Dm0444 -o root -g root -t ${PATH_GRUB_ELTORITO_IMG}/EFI/BOOT/ \
	"${FILE_GRUBX64}" \
	"${FILE_GRUBIA32}"
    cd ${PATH_GRUB_ELTORITO_IMG}
    find ./* -type d -exec mmd -i "${OUTPUT_FILE}" "::{}" \;
    find ./* -type f -exec mcopy -i "${OUTPUT_FILE}" "{}" "::{}" \;
    rm -rdf ${PATH_GRUB_ELTORITO_IMG}
}

##########################################################################################################################################################################
#
# ${VOLID} - переменная задает метку диска, желательно для совместимости метку задавать в ВЕРХНЕМ регистре.
# -b grub2.eltorito - файл загрузчика в формате eltorito внутри образа, в данном случае файл находится в корне образа.
# -grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img - путь к образу mbr из состава grub2 (тоже самое по сути делает -isohybrid-mbr). Этот образ только заготовка.
# -grub2-boot-info grub2.eltorito -  пареметр имеет значение, которое задает имя загрузчика которое будет прописываться в загрузочную запись. В данном случае grub2.eltorito.
#  grub2.eltorito представляет из себя загрузчик сделанный grub-mkimage
# -append-partition 2 0xef /usr/lib/grub/efi.img - добавляет в iso образ раздел из нашего файла efi.img, тип раздела выставляется 0xef EFI.
# -eltorito-alt-boot --efi-boot EFI/BOOT/bootx64.efi - делает iso загружаемым через EFI. bootx64.efi находится внутри iso, 
#  рядом с ним можно разместить файлы конфигурации, локализации, шрифта.
#
# grub2.eltorito представляет из себя загрузчик сделанный grub-mkimage
##########################################################################################################################################################################
make_file_iso() {
    local FILE_ISO="$1/${DISTRIB_CODENAME}_${DISTRIB_VERSION}${POSTFIX_TXT}.iso"
    VOLID="${DISTRIB_CODENAME^^}_${DISTRIB_VERSION}"
    SYSID="LINUX"
    APPID="UBLinux LIVE/RESCUE CD"
    PUBLISHER="UBLinux <https://ublinux.com>"
    PREPARER="prepared by UBLinux Team"
    COPYRIGHT="LICENSE_ALL_HTML"
#   MOD_DATE=$(date -u +%Y-%m-%d-%H-%M-%S-00  | sed -e s/-//g)
    local XORRISO_OPTIONS=() XORRISOFS_OPTIONS=()
    if [[ -n ${QUIET} ]]; then
        # The when xorriso is run in mkisofs compatibility mode (xorrisofs), the mkisofs option -quiet is interpreted
        # too late (e.g. messages about SOURCE_DATE_EPOCH still get shown).
        # Instead use native xorriso option to silence the output.
        XORRISO_OPTIONS=('-report_about' 'SORRY' "${XORRISO_OPTIONS[@]}")
    fi

    XORRISOFS_OPTIONS+=(
	    # Required options to boot with GRUB
            "-no-emul-boot -boot-load-size 4"
            # Move the first partition away from the start of the ISO to match the expectations of partition editors
    	    # May allow booting on some systems
    	    # https://dev.lovelyhq.com/libburnia/libisoburn/src/branch/master/doc/partition_offset.wiki
            "-partition_offset 16"
	    # El Torito boot catalog file
	    "-hide boot.catalog"
	    "-boot-info-table"
	    "-b grub-eltorito" 
	    "--grub2-mbr /usr/lib/grub/i386-pc/boot_hybrid.img"
	    # El Torito boot image for x86 BIOS
	    "-boot-info-table" 
	    "--grub2-boot-info ${FILE_GRUBELTORITO}"
	    "-iso_mbr_part_type 0x00"
    	    # When GPT is used, create an additional partition in the MBR (besides 0xEE) for sectors 0–1 (MBR
    	    # bootstrap code area) and mark it as bootable
    	    # May allow booting on some systems
    	    # https://wiki.archlinux.org/title/Partitioning#Tricking_old_BIOS_into_booting_from_GPT
            "--mbr-force-bootable"
            # Attach efiboot.img as a second partition and set its partition type to "EFI system partition"
	    "-append_partition 2 0xEF ${FILE_GRUBELTORITO_IMG}"
	    # Start a new El Torito boot entry for UEFI
	    "-eltorito-alt-boot"
	    "-eltorito-platform 0xEF"
	    # Set the second partition as the El Torito UEFI boot image
	    "-e --interval:appended_partition_2:all::"
	    # Boot image is not emulating floppy or hard disk; required for all known boot loaders
	    "-no-emul-boot"
	    )
	    
    xorriso ${XORRISO_OPTIONS[@]} -as mkisofs \
	    -J -D -R \
	    -iso-level 3 \
    	    -full-iso9660-filenames \
    	    -joliet \
    	    -joliet-long \
    	    -rational-rock \
	    -sysid "${SYSID}" -appid "${APPID}" -volid "${VOLID}" -publisher "${PUBLISHER}" -preparer "${PREPARER}" -copyright "${COPYRIGHT}" \
	    ${XORRISOFS_OPTIONS[@]} \
	    -output ${FILE_ISO} \
	    ${PATH_SRC_ISO}

    XORRISOFS_OPTIONS_OTHER+=(
            "--modification-date=${MOD_DATE}"
            "-r -graft-points -no-pad"
            "--sort-weight 0 /"
            "--sort-weight 1 /boot"
            "-iso_mbr_part_type 0x83"
            "--protective-msdos-label"
            "-partition_cyl_align off"
	    # Use valid GPT if BIOS booting support will not be required
            "-appended_part_as_gpt -no-pad"
            "-eltorito-alt-boot"
            # Set efiboot.img as the El Torito UEFI boot image
            "--efi-boot EFI/BOOT/bootx64.efi"
            "-graft-points"
            # A valid GPT prevents BIOS booting on some systems, use an invalid GPT instead
            # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
            # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
            # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
	    "-isohybrid-gpt-basdat"
            "."
            "/boot/grub/bios.img=boot/grub/bios.img"
             "/EFI/BOOT/efiboot.img=boot/grub/grub-eltorito.img"
             )

    prompt -s "FINISH: build iso: ${FILE_ISO}"
}

##########################################################################################################################################################################
# Размещение загрузчика MBR на флешке или диске
# файл boot.img - стандартный файл из комплекта grub2, это содержимое нулевого сектора диска. Есть еще файл boot_hybrid.img
# файл core.img - этот файл делается grub-mkimage, только выходной формат файла выставляется другой.
# Командой grub-bios-setup или dd, примеры (/dev/sdb заменить на свою флешку):
# grub-bios-setup -d. -b /usr/lib/grub/i386-pc/boot.img -c ./core.img /dev/sdb
# или
# dd if=/usr/lib/grub/i386-pc/boot.img of=/dev/sdb bs=446 count=1
# dd if=./core.img of=/dev/sdb bs=512 seek=1
# 
##########################################################################################################################################################################
write_mbr() {
    echo
}

generate_sums_sign() {
    local FILE_ISO="${DISTRIB_CODENAME}_${DISTRIB_VERSION}${POSTFIX_TXT}.iso"
    cd $1
    echo
    if [[ -n ${HASHSUM} ]]; then
	prompt -i "generate md5 sums ${FILE_ISO}"
	md5sum ${FILE_ISO} > $1/md5sums.txt &
	prompt -i "generate sha1 sums ${FILE_ISO}"
	sha1sum ${FILE_ISO} > $1/sha1sums.txt &
	prompt -i "generate sha256 sums ${FILE_ISO}"
	sha256sum ${FILE_ISO} > $1/sha256sums.txt &
	prompt -i "generate b2 sums ${FILE_ISO}"
	b2sum ${FILE_ISO} > $1/b2sums.txt &
	prompt -i "generate gost12sum ${FILE_ISO}"
	gost12sum ${FILE_ISO} > $1/gost12sum.txt &
    fi
    if [[ -n ${SIGN_KEY} ]]; then
	prompt -i "sign the ${FILE_ISO} with signature ${SIGN_KEY}"
	[[ -n ${SIGN_USER} ]] && PATH_GNU_HOME="--homedir /home/${SIGN_USER}/.gnupg"
	[[ -n ${SIGN_PASSWORD} ]] && CMD_SIGN_PASSWORD="--pinentry-mode loopback --passphrase "${SIGN_PASSWORD}""
	gpg --yes ${PATH_GNU_HOME} ${CMD_SIGN_PASSWORD} --detach-sign --use-agent -u "${SIGN_KEY}" $1/*.iso
	    #GNUPGHOME=/home/${SIGN_USER}/.gnupg gpg --yes --detach-sign --use-agent -u "${SIGN_KEY}" $1/*.iso
    fi
    wait

}

###############################
###   :::   M A I N   :::   ###
###############################

    PKGNAME=${0##*/}
    PATH_WORK=${PWD}
    set_color

    PATH_SRC_ISO="${PATH_WORK}/src"
    PATH_TAMPL_ISO="/usr/share/ubiso/template"
    PATH_LIB_UBBOOT="/usr/lib/ubboot"
    FILE_GRUBX32="${PATH_WORK}/core.img"
    FILE_GRUBX64="${PATH_SRC_ISO}/EFI/BOOT/bootx64.efi"
    FILE_GRUBIA32="${PATH_SRC_ISO}/EFI/BOOT/bootia32.efi"
    FILE_GRUBELTORITO="${PATH_WORK}/grub-eltorito"
    FILE_GRUBELTORITO_IMG="${PATH_WORK}/grub-eltorito.img"
    GRUB_SEARCH_FILENAME="/boot/grub/grub.cfg"

########################################################
    # Prepare grub.cfg that will be embedded inside the GRUB binaries
    IFS='' read -r -d '' GRUB_CONF_EMBED <<'EOF' || true
if ! [ -d "${cmdpath}" ]; then
    # On some firmware, GRUB has a wrong cmdpath when booted from an optical disc. During El Torito boot, GRUB is
    # launched from a case-insensitive FAT-formatted EFI system partition, but it seemingly cannot access that partition
    # and sets cmdpath to the whole cd# device which has case-sensitive ISO 9660 + Rock Ridge + Joliet file systems.
    # See https://gitlab.archlinux.org/archlinux/archiso/-/issues/183 and https://savannah.gnu.org/bugs/?62886
    if regexp --set=1:BOOT_BOOTDEVICE '^\(([^)]+)\)\/?[Ee][Ff][Ii]\/[Bb][Oo][Oo][Tt]\/?$' "${cmdpath}"; then
        set cmdpath="(${BOOT_BOOTDEVICE})/EFI/BOOT"
        set BOOT_HINT="${BOOT_BOOTDEVICE}"
    fi
fi

# Prepare a hint for the search command using the device in cmdpath
if [ -z "${BOOT_HINT}" ]; then
    regexp --set=1:BOOT_HINT '^\(([^)]+)\)' "${cmdpath}"
fi

# Search for the volume
if search --no-floppy --set=BOOT_DEVICE --file '%SEARCH_FILENAME%' --hint "${BOOT_HINT}"; then
    set BOOT_HINT="${BOOT_DEVICE}"
    if probe --set BOOT_UUID --fs-uuid "${BOOT_HINT}"; then
        export BOOT_UUID
    fi
else
    echo "Could not find a volume with a '%SEARCH_FILENAME%' file on it!"
fi

# Load grub.cfg
if [ "${BOOT_HINT}" == 'memdisk' -o -z "${BOOT_HINT}" ]; then
    echo 'Could not find the a volume!'
elif [ -e "(${BOOT_HINT})/boot/grub/grub.cfg" ]; then
    export BOOT_HINT
    set root="${BOOT_HINT}"
    configfile "(${BOOT_HINT})/boot/grub/grub.cfg"
else
    echo "File '(${BOOT_HINT})/boot/grub/grub.cfg' not found!"
fi
EOF
########################################################

    arguments $@
    check_root
    detect_osrelease
    prepare_structure
    make_grub_core "${FILE_GRUBX32}"
    make_grub_grubx64 "${FILE_GRUBX64}"
    make_grub_grubia32 "${FILE_GRUBIA32}"
    make_grub_eltorito "${FILE_GRUBELTORITO}"
    make_grub_eltorito_img "${FILE_GRUBELTORITO_IMG}" "${FILE_GRUBX64}" "${FILE_GRUBIA32}"

    if [[ -n ${BUILD_RAM} ]]; then
	PATH_WORK_TMP=$(mktemp -d --tmpdir ublinux.iso.XXXX)
	make_file_iso "${PATH_WORK_TMP}"
	generate_sums_sign "${PATH_WORK_TMP}"
	prompt -i "copy files ${PATH_WORK_TMP}/ to the destination directory ${PATH_WORK}/"
	rsync -a --info=progress2 "${PATH_WORK_TMP}/" "${PATH_WORK}/"
	rm -rdf "${PATH_WORK_TMP}"
    else
	make_file_iso "${PATH_WORK}"
	generate_sums_sign "${PATH_WORK}"
    fi
    
    if [[ -z ${MANUAL} && -z ${NO_SKEL} && -z ${NO_MODULES} && -z ${NO_CLEAN} ]]; then 
	prompt -i "clean sources ${PATH_SRC_ISO}"
	rm -rdf ${PATH_SRC_ISO}
	rm -f ${FILE_GRUBX32}
	rm -f ${FILE_GRUBX64}
	rm -f ${FILE_GRUBIA32}
	rm -f ${FILE_GRUBELTORITO}
	rm -f ${FILE_GRUBELTORITO_IMG}
    fi
    sync; sync; sync; echo 3 > /proc/sys/vm/drop_caches
