#!/bin/ash
# Fatdog64 ISO build script
# (C) James Budiono 2012, 2014, 2016, 2017, 2018, 2020
# License: GPL Version 3 or later.
#
# $1 - building sfs: base/devx/nls/base32/single/multi
# $1 - building iso: iso [std/mini/micro/nano] [uefi/xorriso-uefi/grldr/isolinux/grub2]
# $1 - building initrd: initrd [std/mini/micro/nano]
#

### configuration
LANG=C
FATDOG_VERSION=${FATDOG_VERSION:-811}  # update for new release
KERNEL_VERSION=${KERNEL_VERSION-5.4.60} # update for new release
MICROCODE_DATE=${MICROCODE_DATE:-20200905} # update for new release
SQUASHFS_PARAM=${SQUASHFS_PARAM--comp xz -Xbcj x86} # inherit 

CHECKSUM=${CHECKSUM:-compute}  # compute, download-once, download-always
PKG_CACHE=${PKG_CACHE:-output}
PKG_URL=${PKG_URL:-http://distro.ibiblio.org/fatdog/packages/800}
OUTPUT_DIR=${OUTPUT_DIR:-iso}
FATDOG_INITRD=${FATDOG_INITRD:-fatdog-initrd}
FATDOG_ISO_ROOT=${FATDOG_ISO_ROOT:-fatdog-iso-root}
# default BASE_SFS as specified in BASE_SFS_DEFAULT_PATH in initrd
[ -e "$FATDOG_INITRD"/sbin/system-init ] &&
BASE_SFS=${BASE_SFS:-$(sed -n '/^BASE_SFS_DEFAULT_PATH/ {s|.*=.*\/||;p}' "$FATDOG_INITRD"/sbin/system-init)}
BASE_SFS=${BASE_SFS:-fd64.sfs}
TARGET_ISO=${TARGET_ISO:-fatdog.iso}
#USE_EXISTING=1  # don't build sfs-es, use existing
#DUAL_NANO=1     # create secondary nano initrd
#SGD2_LOOPBACK=1 # https://www.supergrubdisk.org/wiki/Loopback.cfg

# modules required for boot, separated by spaces
BASE_MODULES='cryptoloop|nbd|cifs|aufs|usb-storage|ohci|uhci|ehci|aes|xts|dm-crypt|dm-snapshot|sg|hid-generic' 
BUILD_CONFIG=${BUILD_CONF:-./build-iso.conf}

# working dirs
WORKDIR=${WORKDIR:-/tmp/build-iso.$$}

### standard configuration items - dont change unless you know what you're doing
[ -e "$BUILD_CONFIG" ] && . "$BUILD_CONFIG"
unset BUILD_CONFIG # don't pass through to slave scripts

# pass through all other build-multi-sfs.sh options (except those overriden above)
for p in PKG_EXT PKG_URL PKG_CACHE OUTPUT_DIR INSTALLER_PKG FATDOG_VERSION \
         BASE_PKG_LIST DEVX_PKG_LIST BASE_SFS DEVX_SFS NLS_SFS \
         BASE_ROOTFS_DIR DEVX_ROOTFS_DIR NLS_ROOTFS_DIR KEEP_ROOTFS \
         SQUASHFS_PARAM GEN_EXCLUSION_LIST; do
	eval test \"\$$p\" && eval export $p
done
### end of configuration



################ helpers ################

CLEANUP_DONE=
trap 'cleanup; exit' INT TERM HUP 0
cleanup() {
	if [ -z $CLEANUP_DONE ]; then
		rm -rf "$WORKDIR"
	fi
	CLEANUP_DONE=1
}

run_in_my_dir() {
        mydir=$(readlink -f "$0")
        mydir=${mydir%/*}
        cd $mydir
}

update_checksum() {
	case $CHECKSUM in
		compute)
			(
			echo updating md5sum
			cd $PKG_CACHE
			pkg2repo MD5
			) 
			;;
		download-once)
			! [ -f "${PKG_URL}/CHECKSUMS.md5" ] &&
			wget -N -P $PKG_CACHE "${PKG_URL}/CHECKSUMS.md5"
			;;
		download-always)
			wget -N -P $PKG_CACHE "${PKG_URL}/CHECKSUMS.md5"
			;;
	esac
}

# $* combine all sfses, the last one is target
combine_sfs() {
	echo combining sfs ...
	local target TMPDIR=$WORKDIR/squashfs-root

	for target; do :; done;	rm -f $target
	for p; do
		[ -r "$p" ] && unsquashfs -f -d $TMPDIR "$p" >/dev/null
	done
	mksquashfs $TMPDIR $target -noappend $SQUASHFS_PARAM >/dev/null
	rm -rf $TMPDIR
}

# $1-source, $2-target $3-module list
extract_base_modules() {
	echo extracting kernel modules ...
	local TMPDIR=$WORKDIR/squashfs-root OLD_DEP="$BASE_MODULES" DEP="" KVER
	
	# extract, get kernel ver, update base modules list
	unsquashfs -f -d $TMPDIR "$1" > /dev/null
	KVER=$(ls $TMPDIR/lib/modules | head -n1)
	update_base_module_list $TMPDIR/lib/modules/$KVER/modules.dep

	# delete unnecessary files
	#echo $(echo /$BASE_MODULES | sed 's%|%.ko|/%g')
	find $TMPDIR/lib/modules/$KVER -type f | grep -vE $(echo /$BASE_MODULES | sed 's%|%.ko|/%g; s%$%.ko%') | xargs rm 
	find $TMPDIR/lib/modules/$KVER -type d | sort -r | xargs rmdir 2>/dev/null # remove the empty dirs
	rm -r $TMPDIR/lib/firmware # hopefully no firmware is needed
	
	# re-pack
	depmod -b $TMPDIR $KVER
	mksquashfs $TMPDIR "$2" -noappend $SQUASHFS_PARAM >/dev/null
	rm -rf $TMPDIR
}
# input: $BASE_MODULES, $1-path to modules.dep, returns: BASE_MODULES
update_base_module_list() {
	local DEP OLD_DEP
	DEP="$BASE_MODULES"; OLD_DEP=""
	[ -z "$DEP" ] && return
	while [ "$OLD_DEP" != "$DEP" ]; do	
		OLD_DEP="$DEP"
		for p in $(grep -E $(echo "/$DEP" | sed 's%|%.ko:|/%g; s%$%.ko:%') "$1" | sed 's/.*://'); do
			p=${p##*/}; p=${p%.ko}
			case $DEP in 
				*\|$p*) ;;
				*) DEP="$DEP|$p" ;;
			esac
		done
	done
	BASE_MODULES="$DEP"
}



############ sfs builders #################

# $1-all,multi,base,devx,nls,base32 (default "multi")
build_multi_sfs() {
	update_checksum
	SFS_TO_BUILD=$1 ./build-multi-sfs.sh
}

build_single_sfs() {
	update_checksum
	SFS_OUTPUT=$BASE_SFS ./build-sfs.sh
}




############# initrd builders ###############

# $1-std,mini,micro,nano $2-basesfs $3-output dir (=iso root)
build_initrd() {
	INITRD_DIR="$WORKDIR/initrd.$$"
	cp -a "$FATDOG_INITRD" $INITRD_DIR

	# static nodes, needed for embedded initrd in kernel
	mknod -m 600 $INITRD_DIR/dev/console c 5 1 # otherwise won't boot
	mknod -m 666 $INITRD_DIR/dev/null c 1 3    # nice to have
	
	# kernel modules
	[ -e kernel/kernel-modules.sfs ] &&
	cp kernel/kernel-modules.sfs "$INITRD_DIR"
	
	# microcode update
	[ -e kernel/microcode-update-${MICROCODE_DATE}.cpio ] &&
	cpio -i -d -D "$INITRD_DIR" < kernel/microcode-update-${MICROCODE_DATE}.cpio 
	
	# basesfs
	echo building initrd $1
	case $1 in
		std|"") # std is default
			# basesfs inside  initrd
			cp "$2" "$INITRD_DIR"/$BASE_SFS  
			;; 
			
		mini) 
			# basesfs outside initrd
			cp "$2" "$3"/$BASE_SFS
			set_external_initrd ram:local /$BASE_SFS
			DUAL_NANO= # disable 2nd nano initrd if our initrd is already small
			;;
			
		micro)
			# combine base + kernel modules, keep only important modules in initrd
			combine_sfs "$2" kernel/kernel-modules.sfs "$3"/$BASE_SFS
			extract_base_modules kernel/kernel-modules.sfs $INITRD_DIR/kernel-modules.sfs "$BASE_MODULES"
			set_external_initrd ram:local /$BASE_SFS
			DUAL_NANO= # disable 2nd nano initrd if our initrd is already small
			;;

		nano)
			cp "$2" "$3"/$BASE_SFS
			mv "$INITRD_DIR"/kernel-modules.sfs "$3"
			set_external_initrd ram:local /$BASE_SFS
			set_external_kernel_modules "$3" ram:local /kernel-modules.sfs 
			DUAL_NANO= # disable 2nd nano initrd if our initrd is already small
			;;
	esac
	
	# cleanup
	find $INITRD_DIR -type f -name 'placeholder_for_fossil' -delete
	
	# build initrd
	INITRD=$(readlink -f "$3")/initrd
	( cd $INITRD_DIR; find . | cpio -o -H newc > $INITRD )

	# Create loopback.cfg to support SGD2
	[ $SGD2_LOOPBACK ] &&
	mkdir -p "$3"/boot/grub &&
	echo "source /grub.cfg" > "$3"/boot/grub/loopback.cfg
	
	# build 2nd nano initrd if asked
	if [ $DUAL_NANO ]; then
		rm -f "$INITRD_DIR"/$BASE_SFS "$INITRD_DIR"/kernel-modules.sfs
		( cd $INITRD_DIR; find . | cpio -o -H newc > ${INITRD}-nano )
		add_dual_nano_menu_entries "$3"
	fi
}
# change default base_sfs from "initrd" to "local", increase waitdev delay to 3 seconds
# $1-basesfs-default-search $2-basesfs 
set_external_initrd() {
	sed -i -e "
	s|^BASE_SFS_DEFAULT=.*|BASE_SFS_DEFAULT=$1|	
	s|^BASE_SFS_DEFAULT_PATH=.*|BASE_SFS_DEFAULT_PATH=$2|
	s|^DEFAULT_DEVICE_DELAY=.*|DEFAULT_DEVICE_DELAY=3|
	" "$INITRD_DIR"/sbin/system-init 
}
# $1-output path, $2-prefix $3-kernel modules path
set_external_kernel_modules() {
	sed -i -e '/^append/ s|$| extrasfs='"$2:$3"'|' "$1"/isolinux.cfg
	sed -i -e '/vmlinuz/ s|$| extrasfs='"$2:$3"'|' "$1"/grub.cfg "$1"/menu.lst
}
# $1-output path
add_dual_nano_menu_entries() {
	>> "$1"/isolinux.cfg cat << EOF

label fatdog-nano
linux vmlinuz
initrd initrd-nano
append rootfstype=ramfs mergeinitrd1=local:/initrd waitdev=3
menu label For machines with slow BIOS or bootloaders
text help
Start Fatdog using nano-initrd - the smallest possible initrd.
This is meant for old machines with slow BIOS or bootloaders.
Be aware that nano-initrd lacks many drivers to support advanced
hardware; so it may fail to boot altogether.
endtext
EOF
	>> "$1"/grub.cfg cat << EOF

menuentry "Fatdog64 for slow BIOS and bootloaders" {
	echo Loading ...
	linux /vmlinuz rootfstype=ramfs mergeinitrd1=local:/initrd waitdev=3
	initrd /initrd-nano
	echo Booting ...
}
EOF
	>> "$1"/menu.lst cat << EOF

title Fatdog64 for slow BIOS and bootloaders
kernel /vmlinuz rootfstype=ramfs mergeinitrd1=local:/initrd waitdev=3
initrd /initrd-nano
EOF
}

########### iso builder #################

# $1-uefi,xorriso-uefi,grldr,isolinux,grub2 $2-iso-root $3-output dir
make_iso() {
	local mode=$1
	
	# if mode is uefi, check if efiboot.img exist
	# otherwise build bios iso
	case $mode in
		*uefi*|"")
			! [ -f "$2"/efiboot.img ] && 
			echo "efiboot.img missing, building BIOS isohybrid." &&
			mode=isolinux ;;
	esac
	
	case $mode in
		uefi|"") # uefi is default
			# standard uefi build using cdrtools' mkisofs
			rm -f "$2"/grldr "$2"/menu.lst "$3/$TARGET_ISO"
			mkisofs -o "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-iso-level 4 -D -R \
			-b isolinux.bin -no-emul-boot -boot-load-size 4 -boot-info-table \
			-eltorito-alt-boot -eltorito-platform efi -b efiboot.img -no-emul-boot \
			"$2"/
			isohybrid -u "$3/$TARGET_ISO"
			echo Standard UEFI ISO created.
			;;

		uefi-only)
			# non-hybrid uefi-only build using cdrtools' mkisofs
			rm -f "$3/$TARGET_ISO"
			( cd "$2"; rm -rf isolinux.* vesamenu.c32 help menu.lst grldr )
			mkisofs -o "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-iso-level 4 -D -R \
			-eltorito-platform efi -b efiboot.img -no-emul-boot \
			"$2"/
			echo Non-hybrid UEFI-only ISO created.
			;;

		xorriso-uefi) # for recent xorriso (tested with 1.4.0)
			# hybrid uefi build - Windows XP had problems interpreting iso:1999 spec
			type xorriso || return
			rm -f "$2"/grldr "$2"/menu.lst "$3/$TARGET_ISO"
			xorriso -dev "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-compliance "iso_9660_level=3:iso_9660_1999" \
			-map "$2" / \
			-boot_image isolinux dir=/	 \
			-boot_image isolinux system_area=/usr/share/syslinux/isohdpfx.bin \
			-boot_image isolinux partition_table=on \
			-boot_image isolinux next \
			-boot_image any efi_path=efiboot.img \
			-boot_image isolinux partition_entry=gpt_basdat
			echo Xorriso hybrid UEFI ISO created.
			;;

		
		xorriso-uefi-only) # for recent xorriso (tested with 1.5.2)
			# hybrid uefi-only build, probably same problem in Windows as above
			type xorriso || return
			rm -f "$3/$TARGET_ISO"
			( cd "$2"; rm -rf isolinux.* vesamenu.c32 help menu.lst grldr )
			rm -f "$2"/grldr "$2"/menu.lst "$3/$TARGET_ISO"
			xorriso -dev "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-compliance "iso_9660_level=3:iso_9660_1999" \
			-map "$2" / \
			-boot_image any efi_path=efiboot.img \
			-boot_image any cat_hidden=on        \
			-boot_image any efi_boot_part=--efi-boot-image # this makes it isohybrid
			echo Xorriso hybrid UEFI-only ISO created.
			;;

		xorriso-uefi-old)
			# hybrid uefi build for older xorriso (requires post-processing with isohybrid)
			type xorriso || return
			rm -f "$2"/grldr "$2"/menu.lst "$3/$TARGET_ISO"
			xorriso -dev "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-compliance "iso_9660_level=3:iso_9660_1999" \
			-map "$2" / \
			-boot_image isolinux dir=/	 \
			-boot_image isolinux next \
			-boot_image any efi_path=efiboot.img	 
			isohybrid -u "$3/$TARGET_ISO"
			echo Xorriso hybrid UEFI ISO created.
			;;

		grldr)
			# grub build - non-hybrid ISO
			rm -f "$3/$TARGET_ISO"
			( cd "$2"; rm -rf efiboot.img grub.cfg isolinux.* vesamenu.c32 help )
			mkisofs -o "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-iso-level 4 -D -R  \
			-b grldr -no-emul-boot -boot-load-size 4 -boot-info-table \
			"$2"/
			echo Grub4dos non-hybrid BIOS ISO created.
			;;

		grub2)
			# grub2 build - non-hybrid ISO
			rm -f "$3/$TARGET_ISO"
			( cd "$2"; rm -rf efiboot.img menu.lst grldr isolinux.* vesamenu.c32 help;
			fatdog-create-grub2-bootloader cdboot )
			mkisofs -o "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-iso-level 4 -D -R  \
			-b grub2.cdboot -no-emul-boot -boot-load-size 4 -boot-info-table \
			"$2"/
			echo Grub2 non-hybrid BIOS ISO created.
			;;

		isolinux)
			# standard bios build
			rm -f "$2"/efiboot.img "$2"/grub.cfg "$2"/grldr "$2"/menu.lst "$3/$TARGET_ISO"
			mkisofs -o "$3/$TARGET_ISO" \
			-volid "FATDOG_LIVE" \
			-iso-level 4 -D -R  \
			-b isolinux.bin -no-emul-boot -boot-load-size 4 -boot-info-table \
			"$2"/
			isohybrid -o 64 "$3/$TARGET_ISO"
			echo Standard BIOS ISO created.	
			;;
	esac
}

# $1-std,mini,micro,nano (default std) $2-uefi,xorriso-uefi,grldr,isolinux,grub2 (default uefi), or initrd
build_iso() {
	ISO_ROOT="$WORKDIR/iso-root.$$"
	cp -a "$FATDOG_ISO_ROOT" $ISO_ROOT

	# version info
	cat > $ISO_ROOT/VERSION << EOF
Fatdog version : $FATDOG_VERSION
Kernel version : $KERNEL_VERSION
Microcode      : $MICROCODE_DATE
Build date     : $(date -I)
EOF

	# kernel
	KERNEL_VERSION=$KERNEL_VERSION ./get-kernel.sh 
	! [ -e kernel/vmlinuz ]  && echo "Can't download kernel, exiting..." && exit
	cp kernel/vmlinuz $ISO_ROOT
	
	# sfs, initrd, and iso
	[ -z "$USE_EXISTING" ] && build_multi_sfs base
	if [ "$2" = "initrd" ]; then
		DUAL_NANO= # don't bother with this if we aren't going to use it
		build_initrd "$1" "$OUTPUT_DIR/$BASE_SFS" "$ISO_ROOT"
		mv "$ISO_ROOT"/initrd "$OUTPUT_DIR"
	else
		build_initrd "$1" "$OUTPUT_DIR/$BASE_SFS" "$ISO_ROOT"
		make_iso "$2" "$ISO_ROOT" "$OUTPUT_DIR"	
	fi
}



#### main ####
run_in_my_dir
mkdir -p "$WORKDIR" "$OUTPUT_DIR"
case $1 in
	single)	 build_single_sfs      ;;
	
	all)     build_multi_sfs all   ;;
	multi)   build_multi_sfs multi ;;
	base)    build_multi_sfs base  ;;
	devx)    build_multi_sfs devx  ;;
	nls)     build_multi_sfs nls   ;;
	base32)  build_multi_sfs base32 ;;
	
	iso)     build_iso "$2" "$3"  ;; # $2-initrd-type $3-iso-type
	initrd)  build_iso "$2" initrd "$WORKDIR" ;; # $2-initrd-type
	*) echo "Usage: ${0##*/} [iso|initrd|all|multi|single|base|devx|nls|base32]"
	   echo "  iso    options: [std|mini|micro|nano] [uefi|xorriso-uefi|grldr|isolinux|grub2]"
	   echo "  initrd options: [std|mini|micro|nano] "
	   ;;
esac
