#!/bin/bash
{
	#////////////////////////////////////
	# DietPi Image creation/finalise Script
	#
	#////////////////////////////////////
	# Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com
	# Updated by MichaIng / micha@dietpi.com / dietpi.com
	# Clonezilla integration by sal666
	#
	#////////////////////////////////////
	# - Create new .img file from drive
	#   or use an existing .img file
	#   or use Clonezilla to generate a bootable installer ISO from drive for x86_64 systems
	# - Minimises root partition and filesystem
	# - Compresses the final image ready for release
	#////////////////////////////////////

	# Import DietPi-Globals ---------------------------------------------------------------
	if [[ -f '/boot/dietpi/func/dietpi-globals' ]]
	then
		. /boot/dietpi/func/dietpi-globals
	else
		[[ $G_GITOWNER && $G_GITBRANCH ]] || { echo '[FAILED] You must export or pass G_GITOWNER and G_GITBRANCH to the script'; exit 1; }
		curl -sSf "https://raw.githubusercontent.com/$G_GITOWNER/DietPi/$G_GITBRANCH/dietpi/func/dietpi-globals" -o /tmp/dietpi-globals || exit 1
		# shellcheck disable=SC1091
		. /tmp/dietpi-globals
		G_EXEC rm /tmp/dietpi-globals
		read -r debian_version < /etc/debian_version
		case $debian_version in
			'11.'*|'bullseye/sid') G_DISTRO=6;;
			'12.'*|'bookworm/sid') G_DISTRO=7;;
			'13.'*|'trixie/sid') G_DISTRO=8;;
			*) G_DIETPI-NOTIFY 1 "Unsupported distro version \"$debian_version\". Aborting ..."; exit 1;;
		esac
		# Ubuntu ships with /etc/debian_version from Debian testing, hence we assume one version lower.
		grep -q '^ID=ubuntu' /etc/os-release && ((G_DISTRO--))
		(( $G_DISTRO < 6 )) && { G_DIETPI-NOTIFY 1 'Unsupported Ubuntu version. Aborting ...'; exit 1; }
	fi
	readonly G_PROGRAM_NAME='DietPi-Imager'
	G_CHECK_ROOT_USER
	G_CHECK_ROOTFS_RW
	readonly FP_ORIGIN=$PWD # Store origin dir
	G_INIT
	G_EXEC cd "$FP_ORIGIN" # Process everything in origin dir instead of /tmp/$G_PROGRAM_NAME
	# Import DietPi-Globals ---------------------------------------------------------------

	readonly FP_MNT_TMP="/tmp/${G_PROGRAM_NAME}_mnt"
	readonly CLONEZILLA_REPO='https://sourceforge.net/projects/clonezilla/files/clonezilla_live_stable'
	readonly DIETPI_REPO="https://raw.githubusercontent.com/$G_GITOWNER/DietPi/$G_GITBRANCH"

	Error_Exit()
	{
		G_DIETPI-NOTIFY 1 "$1"
		exit 1
	}

	##########################################
	# Process inputs
	##########################################
	SOURCE_TYPE='Drive'
	FP_SOURCE_IMG=
	PART_TABLE_TYPE=
	#FP_ROOT_DEV=
	ROOT_FS_TYPE=
	#CLONING_TOOL=
	OUTPUT_IMG_EXT='img'
	#OUTPUT_IMG_NAME=
	[[ $MOUNT_IT == 'On' ]] || MOUNT_IT='Off'
	[[ $SKIP_FIRSTBOOT_RESIZE == 1 ]] || SKIP_FIRSTBOOT_RESIZE=0
	[[ $SHRINK_ONLY == 1 ]] || SHRINK_ONLY=0
	[[ $SKIP_ARCHIVE == 1 ]] || SKIP_ARCHIVE=0
	UPLOAD_SCRIPT=
	ADD_DOS_PART=0 # trailing FAT partition for first boot config files, automatically imported and partition removed on first boot
	CONFIGS_TO_BOOT=0 # for new RPi kernel/firmware stack where a boot FAT partition exists, but is mounted to /boot/firmware instead of /boot
	SIGN_PASS=
	while (( $# ))
	do
		case $1 in
			'--skip-archive') SKIP_ARCHIVE=1;;
			'--upload-script') shift; UPLOAD_SCRIPT=$1;;
			'--add-dos-part') ADD_DOS_PART=1;;
			'--configs-to-boot') CONFIGS_TO_BOOT=1;;
			'--sign') shift; SIGN_PASS=$1;;
			*)
				if [[ -b $1 ]]
				then
					FP_SOURCE=$1

				elif [[ -f $1 ]]
				then
					SOURCE_TYPE='Image'
					FP_SOURCE_IMG=$1

				elif [[ $1 ]]
				then
					Error_Exit "Input source $1 does not exist, aborting..."
				fi
			;;
		esac
		shift
	done

	Unmount_tmp()
	{
		G_EXEC sync
		G_SLEEP 1 # Give the system 1 second to avoid "mount is busy"
		G_EXEC umount -R "$FP_MNT_TMP"
	}

	Delete_Loopback(){ [[ $FP_SOURCE_IMG ]] && losetup "$FP_SOURCE" &> /dev/null && G_EXEC losetup -d "$FP_SOURCE"; }

	G_EXIT_CUSTOM()
	{
		findmnt "$FP_MNT_TMP" > /dev/null && Unmount_tmp
		[[ -d $FP_MNT_TMP ]] && G_EXEC rmdir "$FP_MNT_TMP"
		Delete_Loopback
		[[ -e 'tmpiso' ]] && G_EXEC rm -R tmpiso
	}

	Run_fsck()
	{
		if [[ $ROOT_FS_TYPE == 'ext4' ]]
		then
			G_EXEC_OUTPUT=1 G_EXEC e2fsck -fyD "$FP_ROOT_DEV"

		elif [[ $ROOT_FS_TYPE == 'f2fs' ]]
		then
			G_EXEC_OUTPUT=1 G_EXEC fsck.f2fs -f "$FP_ROOT_DEV"

		elif [[ $ROOT_FS_TYPE == 'btrfs' ]]
		then
			G_EXEC_OUTPUT=1 G_EXEC btrfs check --repair "$FP_ROOT_DEV"
		else
			Error_Exit "Unsupported root filesystem type ($ROOT_FS_TYPE), aborting..."
		fi
	}

	Menu_Source_Type()
	{
		main_menu_choice='Source type' # On cancel, keep this entry selected

		G_WHIP_MENU_ARRAY=(
			'Drive' ': The OS is stored on an attached drive.'
			'Image' ': The OS is stored as an image file.'
		)
		G_WHIP_DEFAULT_ITEM=$SOURCE_TYPE
		G_WHIP_MENU 'Please select how the input OS is stored:' || return 0
		SOURCE_TYPE=$G_WHIP_RETURNED_VALUE
		Delete_Loopback
		FP_SOURCE_IMG=
		FP_SOURCE=
		FP_ROOT_DEV=

		Menu_Source_Path # Directly open this menu next
	}

	Menu_Source_Path()
	{
		main_menu_choice='Source path' # On cancel, keep this entry selected

		if [[ $SOURCE_TYPE == 'Drive' ]]
		then
			# Detect drives with a partition table, containing a partition with and ext4, F2FS or Btrfs filesystem, excluding the hosts root filesystem drive
			mapfile -t G_WHIP_MENU_ARRAY < <(mawk -v root="$(lsblk -npo PKNAME "$G_ROOTFS_DEV")" '$1!=root && $2~/^(ext4|f2fs|btrfs)$/ {print $1"\n"$1"_details"}' < <(lsblk -rnpo PKNAME,FSTYPE) | sort -u)

			if [[ ! ${G_WHIP_MENU_ARRAY[0]} ]]
			then
				G_DIETPI-NOTIFY 1 'No drive with an ext4, F2FS or Btrfs formatted partition found, aborting...'
				G_DIETPI-NOTIFY 2 'NB: This is the list of available block devices:'
				lsblk -npo NAME,SIZE,MAJ:MIN,FSTYPE,MOUNTPOINT,MODEL
				read -rp 'Press any key to return to menu...'
				return 1
			fi

			# Visually separate dev name and size and add model and serial
			for ((i=1;i<${#G_WHIP_MENU_ARRAY[@]};i+=2)); do G_WHIP_MENU_ARRAY[$i]=": $(lsblk -drno SIZE,MODEL,SERIAL "${G_WHIP_MENU_ARRAY[$i-1]}")"; done

			G_WHIP_DEFAULT_ITEM=$FP_SOURCE
			G_WHIP_MENU 'Please select the drive you wish to create the image from:
\nNB: All mounted partitions of the selected drive will be unmounted.' || return 0
			FP_SOURCE=$G_WHIP_RETURNED_VALUE
			FP_ROOT_DEV=

			G_DIETPI-NOTIFY 2 "Unmounting all filesystems below selected $FP_SOURCE ..."
			local mountpoint
			for i in "$FP_SOURCE"?*
			do
				mountpoint=$(findmnt -no TARGET "$i")
				[[ $mountpoint ]] && G_EXEC umount -R "$mountpoint"
			done
		else
			# Open DietPi-Explorer for image file selection
			/boot/dietpi/dietpi-explorer 1 || { G_DIETPI-NOTIFY 1 'No image file selected, aborting...'; read -rp 'Press any key to return to menu...'; return 1; }
			FP_SOURCE_IMG=$(</tmp/.dietpi-explorer_selected_location)
			FP_ROOT_DEV=
			rm /tmp/.dietpi-explorer_selected_location
			[[ -f $FP_SOURCE_IMG ]] || { G_DIETPI-NOTIFY 1 "Selected image file ($FP_SOURCE_IMG) does not exist, aborting..."; read -rp 'Press any key to return to menu...'; return 1; }

			# Create loopback device from .img file
			G_EXEC modprobe loop
			Delete_Loopback # Prevent doubled loop device
			FP_SOURCE=$(losetup -f)
			G_EXEC_NOEXIT=1 G_EXEC losetup "$FP_SOURCE" "$FP_SOURCE_IMG" || return 1
			G_EXEC_NOEXIT=1 G_EXEC partprobe "$FP_SOURCE" || return 1
			G_EXEC_NOEXIT=1 G_EXEC partx -u "$FP_SOURCE" || return 1
			G_DIETPI-NOTIFY 0 "Attached the image ($FP_SOURCE_IMG) as loopback device: $FP_SOURCE"
			G_SLEEP 0.5 # Give the root filesystem a little time to be detected
		fi

		Menu_Source_RootFS # Directly open this menu next
	}

	Menu_Source_RootFS()
	{
		main_menu_choice='Source rootfs' # On cancel, keep this entry selected

		# Detect partitions and list for selection
		# Coders NB: read/mapfile cannot be easily used here since we need to parse multiple lines and split at newline AND space.
		# shellcheck disable=SC2207
		G_WHIP_MENU_ARRAY=($(lsblk -rnpo NAME,FSTYPE "$FP_SOURCE"?* | mawk '$2~/^(ext4|f2fs|btrfs)$/'))
		# Visually separate dev name and size and add FS type
		for ((i=1;i<${#G_WHIP_MENU_ARRAY[@]};i+=2)); do G_WHIP_MENU_ARRAY[$i]=": $(lsblk -drno SIZE,FSTYPE "${G_WHIP_MENU_ARRAY[$i-1]}")"; done
		G_WHIP_DEFAULT_ITEM=$FP_ROOT_DEV
		G_WHIP_MENU 'Please select the OS root partition:' || return
		FP_ROOT_DEV=$G_WHIP_RETURNED_VALUE

		Menu_Target_Type # Directly open this menu next
	}

	Menu_Target_Type()
	{
		main_menu_choice='Target type' # On cancel, keep this entry selected

		G_WHIP_MENU_ARRAY=(
			'dd'         ': Create an .img file to flash to target system drive directly.'
			'Clonezilla' ': Create an installer .iso file to boot from removeable media. (x86_64 only!)'
		)
		G_WHIP_DEFAULT_ITEM=$CLONING_TOOL
		G_WHIP_MENU 'Please select which cloning tool to use:
 - dd: A regular system image is created which must be flashed to the target system drive directly.
	This can be compared with regular SD card images like Raspbian Lite or Armbian ones.
	Usually those can be flashed to and booted from eMMC as well.
 - Clonezilla: An installer ISO image is created which must be flashed to an external/USB/removeable drive.
	Boot from the external drive will launch Clonezilla and allow you to install DietPi to any internal drive.
	This is required e.g. for UEFI images.
	NB: Only compatible with x86_64 systems!' || return
		CLONING_TOOL=$G_WHIP_RETURNED_VALUE
		[[ $CLONING_TOOL == 'dd' ]] && OUTPUT_IMG_EXT='img' || OUTPUT_IMG_EXT='iso'

		Menu_Target_Name # Directly open this menu next
	}

	Menu_Target_Name()
	{
		main_menu_choice='Target name' # On cancel, keep this entry selected

		G_WHIP_DEFAULT_ITEM=$OUTPUT_IMG_NAME
		G_WHIP_INPUTBOX 'Please enter a name for the new image file (without file extension):' || return
		OUTPUT_IMG_NAME=$G_WHIP_RETURNED_VALUE

		# Check for existing file, in case offer backup. Skip if source is image file and matches output file already.
		if [[ $PWD/$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT != "$FP_SOURCE_IMG" && -f $OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT ]]
		then
			G_WHIP_BUTTON_OK_TEXT='Overwrite'
			G_WHIP_BUTTON_CANCEL_TEXT='Backup'
			G_WHIP_YESNO "[WARNING] $PWD/$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT already exists
\nDo you want to overwrite or backup the existing file to $PWD/$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT.bak?" || mv "$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT"{,.bak}
		fi

		main_menu_choice='Mount' # Select this entry next
	}

	Menu_Main()
	{
		G_WHIP_MENU_ARRAY=(
			'Source type' ": [${SOURCE_TYPE:=Drive}] Select how the input OS is stored"
			'Source path' ": [${FP_SOURCE_IMG:-$FP_SOURCE}] Select the input $SOURCE_TYPE"
		)
		[[ $FP_SOURCE ]] && G_WHIP_MENU_ARRAY+=('Source rootfs' ": [$FP_ROOT_DEV] Select input OS root partition")
		G_WHIP_MENU_ARRAY+=(
			'Target type' ": [${CLONING_TOOL:=dd}] Select output image type"
			'Target name' ": [${OUTPUT_IMG_NAME:=DietPi_RPi-ARMv8-Bookworm}] Choose the output image name"
			'' '●─'
			'Mount' ": [$MOUNT_IT] Review or edit drive content before image creation"
		)
		[[ $FP_SOURCE && $FP_ROOT_DEV ]] && G_WHIP_MENU_ARRAY+=('' '●─' 'Start' ": Start creating $OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT")

		G_WHIP_DEFAULT_ITEM=$main_menu_choice
		G_WHIP_BUTTON_CANCEL_TEXT='Exit'
		G_WHIP_MENU 'Select input parameters and hit "Start" to continue image creation:' || exit 0

		case $G_WHIP_RETURNED_VALUE in
			'Source type') Menu_Source_Type;;
			'Source path') Menu_Source_Path;;
			'Source rootfs') Menu_Source_RootFS;;
			'Target type') Menu_Target_Type;;
			'Target name') Menu_Target_Name;;
			'Mount') [[ $MOUNT_IT == 'Off' ]] && MOUNT_IT='On' || MOUNT_IT='Off';;
			'Start') main_menu_choice='Start';;
			*) :;;
		esac
	}

	Main(){

		# Dependencies
		G_AG_CHECK_INSTALL_PREREQ parted fdisk zerofree xz-utils

		# Skip menu if all inputs are provided via environment variables
		if [[ ( $SOURCE_TYPE$FP_SOURCE == 'Drive'?* || $SOURCE_TYPE$FP_SOURCE_IMG == 'Image'?* ) && $FP_ROOT_DEV && $CLONING_TOOL =~ ^(dd|Clonezilla)$ && $OUTPUT_IMG_NAME ]]
		then
			if [[ $SOURCE_TYPE == 'Image' ]]
			then
				# Create loopback device from .img file
				G_EXEC modprobe loop
				Delete_Loopback # Prevent doubled loop device
				FP_SOURCE=$(losetup -f)
				G_EXEC losetup "$FP_SOURCE" "$FP_SOURCE_IMG"
				G_EXEC partprobe "$FP_SOURCE"
				G_EXEC partx -u "$FP_SOURCE"
				G_DIETPI-NOTIFY 0 "Mounted the image ($FP_SOURCE_IMG) as loopback device: $FP_SOURCE"
				FP_ROOT_DEV="${FP_SOURCE}p${FP_ROOT_DEV: -1}"
			fi
			[[ $CLONING_TOOL == 'dd' ]] && OUTPUT_IMG_EXT='img' || OUTPUT_IMG_EXT='iso' SKIP_ARCHIVE=1 # Flashing tools do not support xz-compressed ISOs
			G_DIETPI-NOTIFY 0 "\e[0mCreating minified image from:
- $SOURCE_TYPE: ${FP_SOURCE_IMG:-$FP_SOURCE}
- Root device: $FP_ROOT_DEV
- Via $CLONING_TOOL to $OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT
- With intermediate mounting turned $MOUNT_IT"
			G_SLEEP 0.5 # Give the root filesystem a little time to be detected
		else
			local main_menu_choice
			Menu_Source_Type
			until [[ $main_menu_choice == 'Start' ]]
			do
				Menu_Main
			done
			unset -v main_menu_choice
		fi

		FP_FINAL="$FP_ORIGIN/$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT"

		# Detect partition table type, failsafe detection of MBR to debug possibly other/unknown wording/partition table types
		PART_TABLE_TYPE=$(lsblk -no PTTYPE "$FP_ROOT_DEV")
		if [[ $PART_TABLE_TYPE == 'dos' ]]
		then
			G_DIETPI-NOTIFY 2 'MBR partition table detected'

		# Move GPT backup partition table to end of drive
		elif [[ $PART_TABLE_TYPE == 'gpt' ]]
		then
			G_DIETPI-NOTIFY 2 'GPT partition table detected'
		else
			Error_Exit "Unknown partition table type ($PART_TABLE_TYPE), aborting..."
		fi

		# Detect root filesystem type
		ROOT_FS_TYPE=$(lsblk -no FSTYPE "$FP_ROOT_DEV")

		Run_fsck

		# Remount image for any required edits
		G_EXEC mkdir "$FP_MNT_TMP"
		G_EXEC mount "$FP_ROOT_DEV" "$FP_MNT_TMP"
		# - Remove bash history and DHCP leases, which are stored on shutdown, hence cannot be removed via DietPi-PREP
		G_EXEC rm -f "$FP_MNT_TMP/"{root,home/*}/.bash_history "$FP_MNT_TMP/var/lib/dhcp/"*.leases
		# - Enable DietPi-FS_partition_resize to have both, old and new image being resized on next boot automatically
		if [[ $SKIP_FIRSTBOOT_RESIZE != 1 && -f $FP_MNT_TMP/etc/systemd/system/dietpi-fs_partition_resize.service && ! -L $FP_MNT_TMP/etc/systemd/system/local-fs.target.wants/dietpi-fs_partition_resize.service ]]
		then
			[[ -d $FP_MNT_TMP/etc/systemd/system/local-fs.target.wants ]] || G_EXEC mkdir "$FP_MNT_TMP/etc/systemd/system/local-fs.target.wants"
			G_EXEC ln -s {'/etc/systemd/system',"$FP_MNT_TMP/etc/systemd/system/local-fs.target.wants"}'/dietpi-fs_partition_resize.service'
		fi
		if [[ $MOUNT_IT == 'On' ]] && G_WHIP_MSG "The ${SOURCE_TYPE,,} has been mounted to allow you reviewing or editing its content:
- $FP_ROOT_DEV > $FP_MNT_TMP
\nAn interactive bash subshell will open.
\nPlease use the \"exit\" command when you are finished, to return to $G_PROGRAM_NAME."
		then
			# Prevent dietpi-login call in subshell
			local reallow_dietpi_login=1
			[[ $G_DIETPI_LOGIN ]] && reallow_dietpi_login=0
			export G_DIETPI_LOGIN=1
			G_EXEC cd "$FP_MNT_TMP"
			bash &> /dev/tty < /dev/tty
			G_EXEC cd "$FP_ORIGIN"
			(( $reallow_dietpi_login )) && unset -v G_DIETPI_LOGIN
		fi
		Unmount_tmp
		# Failsafe
		G_EXEC partprobe "$FP_SOURCE"
		G_EXEC partx -u "$FP_SOURCE"
		Run_fsck

		# Shrink last filesystem to minimum
		local last_part_dev=$(lsblk -rnpo NAME "$FP_SOURCE" | tail -1)
		local last_fs_type=$(lsblk -no FSTYPE "$last_part_dev")
		if [[ $last_fs_type == 'ext4' ]]
		then
			# Disable and enable journal to clear it and allow further size reduction
			G_EXEC tune2fs -O '^has_journal' "$last_part_dev"
			G_EXEC sync
			G_SLEEP 1
			G_EXEC tune2fs -O 'has_journal' "$last_part_dev"
			G_EXEC sync
			G_SLEEP 1

			# Run multiple times until no change is done any more
			G_DIETPI-NOTIFY 2 'Shrinking last filesystem to minimum size...'
			local out last_fs_size=$(tune2fs -l "$last_part_dev" | mawk '/^Block count/{print $3;exit}') # blocks
			while :
			do
				resize2fs -Mp "$last_part_dev" 2>&1 | tee /tmp/resize2fs_out
				if out=$(grep -im1 'nothing to do!' /tmp/resize2fs_out)
				then
					rm /tmp/resize2fs_out
					FS_SIZE=$(mawk '{print $5}' <<< "$out") # blocks
					BLOCK_SIZE=${out%%k) *} BLOCK_SIZE=${BLOCK_SIZE##*\(} # KiB
					# Re-add 4 MiB if it would be still smaller than before, which was required on Raspbian Buster for successful boot, else leave original size
					if (( $last_fs_size > $FS_SIZE + 4096/$BLOCK_SIZE )) # blocks
					then
						FS_SIZE=$(( $FS_SIZE + 4096/$BLOCK_SIZE )) # blocks
						G_DIETPI-NOTIFY 0 "Reducing last filesystem size to $(( $FS_SIZE * $BLOCK_SIZE / 1024 + 1 )) MiB"
					else
						FS_SIZE=$last_fs_size
						G_DIETPI-NOTIFY 0 "Leaving last filesystem size at $(( $FS_SIZE * $BLOCK_SIZE / 1024 + 1 )) MiB"
					fi
					G_EXEC resize2fs "$last_part_dev" "$FS_SIZE"
					FS_SIZE=$(( $FS_SIZE * $BLOCK_SIZE * 2 )) # blocks => 512 byte sectors
					break

				elif out=$(grep -im1 'no such file or directory' /tmp/resize2fs_out)
				then
					Error_Exit 'Partition not found, aborting...'
				fi
			done

		# F2FS does not support shrinking: https://www.reddit.com/r/archlinux/comments/bpp77f/shrinking_a_f2fs_partition/
		# Hence copy all data outside, remove and re-create a smaller filesystem, then copy data back in, as long as disk usage is not >=95% already.
		### The UUID changes and there is currently no way to change it back, hence only store current filesystem size and skip shrinking...
		elif [[ $last_fs_type == 'f2fs' ]] # && ! $(lsblk -rno FSUSE% "$last_part_dev") =~ ^(9[5-9]|100)%$ ]]
		then
			FS_SIZE=$(lsblk -rnbo SIZE "$last_part_dev") # bytes
			FS_SIZE=$(( $FS_SIZE / 512 )) # bytes => sectors
			#local usage=$(lsblk -rnbo FSUSED "$last_part_dev") # bytes
			#local sector_size=$(lsblk -rnbo LOG-SEC "$last_part_dev") # bytes
			#FS_SIZE=$(( ( $usage + 4*1024**2 ) / $sector_size )) # bytes + 4 MiB buffer => sectors
			#G_DIETPI-NOTIFY 2 'Copying last filesystem content to temporary directory'
			#G_EXEC mkdir "${FP_MNT_TMP}_backup"
			#G_EXEC mount -o ro "$last_part_dev" "$FP_MNT_TMP"
			#G_EXEC cp -a "$FP_MNT_TMP/." "${FP_MNT_TMP}_backup/"
			#G_EXEC umount "$FP_MNT_TMP"
			#G_DIETPI-NOTIFY 2 'Purging last filesystem'
			#G_EXEC dd if=/dev/zero of="$last_part_dev" bs=4K count=10
			#G_DIETPI-NOTIFY 2 'Re-creating smaller last filesystem' # Probably sload.f2fs can replace this? https://manpages.debian.org/sload.f2fs
			#G_EXEC_OUTPUT=1 G_EXEC mkfs.f2fs -w "$sector_size" "$last_part_dev" "$FS_SIZE"
			#G_DIETPI-NOTIFY 2 'Moving last filesystem content back'
			#G_EXEC mount "$last_part_dev" "$FP_MNT_TMP"
			#G_EXEC cp -a "${FP_MNT_TMP}_backup/." "$FP_MNT_TMP/"
			#G_EXEC rm -R "${FP_MNT_TMP}_backup"
			#Unmount_tmp
			#FS_SIZE=$(( $FS_SIZE * $sector_size / 512 )) # sectors => 512 bytes sectors

		elif [[ $last_fs_type == 'btrfs' ]]
		then
			G_DIETPI-NOTIFY 2 'Shrinking last filesystem to minimum size...'
			G_EXEC mount "$last_part_dev" "$FP_MNT_TMP"
			# Obtain current filesystem size
			local last_fs_size=$(findmnt -Ufnrbo SIZE -M "$FP_MNT_TMP") # bytes
			# Obtain minimal filesystem size + 4 MiB buffer
			FS_SIZE=$(( $(btrfs inspect-internal min-dev-size "$FP_MNT_TMP" | mawk '{print $1}') + 4*1024**2 )) # bytes
			# Shrink filesystem only if it would actually become smaller
			if (( $FS_SIZE < $last_fs_size ))
			then
				G_EXEC_OUTPUT=1 G_EXEC btrfs filesystem resize "$FS_SIZE" "$FP_MNT_TMP"
			else
				FS_SIZE=$last_fs_size
			fi
			Unmount_tmp
			FS_SIZE=$(( $FS_SIZE / 512 )) # bytes => 512 byte sectors
		fi

		Run_fsck

		G_DIETPI-NOTIFY 2 'Overriding filesystems free space with zeros to purge removed data and allow better compression...'
		while read -r path type
		do
			[[ $type ]] || continue
			if [[ $type == 'ext'[234] ]]
			then
				# Disable and enable journal on ext3 and ext4 to clear it, when not last partition (where it was done already)
				if [[ $type != 'ext2' && $path != "$last_part_dev" ]]
				then
					G_EXEC tune2fs -O '^has_journal' "$path"
					G_EXEC sync
					G_SLEEP 1
					G_EXEC tune2fs -O 'has_journal' "$path"
					G_EXEC sync
					G_SLEEP 1
				fi
				[[ ( -t 0 || -t 1 ) && $TERM != 'dumb' ]] && G_EXEC_OUTPUT=1
				G_EXEC zerofree -v "$path"
			else
				G_EXEC mount "$path" "$FP_MNT_TMP"
				G_EXEC_NOHALT=1 G_EXEC_OUTPUT=1 G_EXEC fstrim -v --quiet-unsupported "$FP_MNT_TMP"
				Unmount_tmp
			fi
			# shellcheck disable=SC2015
			[[ $path == "$FP_ROOT_DEV" ]] && Run_fsck || G_EXEC_OUTPUT=1 G_EXEC fsck -y "$path"

		done < <(lsblk -rnpo NAME,FSTYPE "$FP_SOURCE"?*)
		G_EXEC rmdir "$FP_MNT_TMP"

		# Only resize partition when new size would be less than current size
		if (( $(<"/sys/class/block/${last_part_dev##*/}/size") > $FS_SIZE ))
		then
			G_DIETPI-NOTIFY 2 "Shrinking last partition to: $(( $FS_SIZE / 2048 + 1 )) MiB"
			G_EXEC_OUTPUT=1 G_EXEC eval "sfdisk --no-reread --no-tell-kernel -fN${last_part_dev: -1} '$FP_SOURCE' <<< ',$FS_SIZE'"
			G_EXEC partprobe "$FP_SOURCE"
			G_EXEC partx -u "$FP_SOURCE"
		fi

		# Derive target image size from last partition end
		# - WARNING: this assumes that the partitions in the table are in order (which we do in other places as well)
		local last_part_end=$(sfdisk -qlo End "$FP_SOURCE" | tail -1) # 512 byte sectors
		IMAGE_SIZE=$last_part_end
		# Add space for GPT backup partition table, or 1 sector for MBR
		if [[ $PART_TABLE_TYPE == 'gpt' ]]
		then
			# Obtain first usable LBA, which defines the size of the GPT backup, else use 34 sectors as default: https://github.com/MichaIng/DietPi/issues/7024
			local gpt_size=$(sgdisk -p "$FP_SOURCE" 2>&1 | mawk -F[\ ,] '/^First usable sector/{print $5}')
			# shellcheck disable=SC2015
			(( $gpt_size )) && ((IMAGE_SIZE+=$gpt_size)) || ((IMAGE_SIZE+=34))
		else
			((IMAGE_SIZE++))
		fi
		((IMAGE_SIZE*=512)) # 512 byte sectors => bytes

		# RPi: Move configs to boot FAT partition to allow easier edit from Windows/macOS
		if (( $CONFIGS_TO_BOOT ))
		then
			local fat_mountpoint=$(mktemp -d)
			local root_mountpoint=$(mktemp -d)
			G_EXEC mount "${FP_ROOT_DEV::-1}1" "$fat_mountpoint"
			G_EXEC mount "$FP_ROOT_DEV" "$root_mountpoint"
			G_DIETPI-NOTIFY 2 'Copying dietpi.txt and other config files to the DIETPISETUP partition'
			for f in 'dietpi.txt' 'dietpi-wifi.txt' 'Automation_Custom_PreScript.sh' 'Automation_Custom_Script.sh' 'unattended_pivpn.conf'
			do
				[[ -f $root_mountpoint/boot/$f ]] || continue
				G_EXEC cp "$root_mountpoint/boot/$f" "$fat_mountpoint/"
				TZ=UTC G_EXEC touch -t '197001010000' "$fat_mountpoint/$f"
				TZ=UTC G_EXEC touch -t '197001010001' "$root_mountpoint/boot/$f"
			done
			G_EXEC umount "$root_mountpoint" "$fat_mountpoint"
			G_EXEC rmdir "$root_mountpoint" "$fat_mountpoint"

		# Add trailing FAT partition to simplify first run setup if requested
		elif (( $ADD_DOS_PART ))
		then
			G_DIETPI-NOTIFY 2 'Adding a 1 MiB FAT partition to simplify first run setup'
			((IMAGE_SIZE+=1048576))
			# Increase source image size if required
			[[ $SOURCE_TYPE == 'Image' ]] && (( $(stat -c '%s' "$FP_SOURCE_IMG") < $IMAGE_SIZE )) && G_EXEC truncate -s "$IMAGE_SIZE" "$FP_SOURCE_IMG" && G_EXEC losetup -c "$FP_SOURCE"
			# Add new DOS partition
			local start=$(( $last_part_end + 1 ))
			local type='EBD0A0A2-B9E5-4433-87C0-68B6B72699C7' # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
			[[ $PART_TABLE_TYPE == 'dos' ]] && type='c'
			G_EXEC eval "sfdisk -a '$FP_SOURCE' <<< 'start=$start,size=2048,type=$type'" # size in sectors
			G_EXEC partprobe "$FP_SOURCE"
			G_EXEC partx -u "$FP_SOURCE"
			# create a FAT filesystem and add config files to it
			local new_dos_part=$(sfdisk -l "$FP_SOURCE" | mawk "/ $start /{print \$1;exit}")
			G_EXE_OUTPUT=1 G_EXEC mkfs.fat -n DIETPISETUP "$new_dos_part"
			local fat_mountpoint=$(mktemp -d)
			local root_mountpoint=$(mktemp -d)
			G_EXEC mount "$new_dos_part" "$fat_mountpoint"
			G_EXEC mount "$FP_ROOT_DEV" "$root_mountpoint"
			G_DIETPI-NOTIFY 2 'Copying dietpi.txt and other config files to the DIETPISETUP partition'
			for f in 'dietpi.txt' 'dietpi-wifi.txt' 'dietpiEnv.txt' 'boot.ini' 'extlinux/extlinux.conf' 'Automation_Custom_PreScript.sh' 'Automation_Custom_Script.sh' 'unattended_pivpn.conf'
			do
				[[ -f $root_mountpoint/boot/$f ]] || continue
				G_EXEC cp "$root_mountpoint/boot/$f" "$fat_mountpoint/"
				TZ=UTC G_EXEC touch -t '202101010001' "$fat_mountpoint/${f#extlinux/}"
				TZ=UTC G_EXEC touch -t '202101010002' "$root_mountpoint/boot/$f"
			done
			cat << '_EOF_' > "$fat_mountpoint/README.txt"
DietPi config partition

This FAT partition is a place for relevant configuration files to pre-configure and automate your DietPi setup.
Those files will be copied into the root filesystem on first boot, if modified, to become effective, and the partition will be removed.

Apart of editing the existing files, you can also create the following for further automation:
- Automation_Custom_PreScript.sh
- Automation_Custom_Script.sh
- unattended_pivpn.conf

For details, please check our documentation and dietpi.txt itself:
https://dietpi.com/docs/usage/#how-to-do-an-automatic-base-installation-at-first-boot-dietpi-automation
_EOF_
			G_EXEC umount "$root_mountpoint" "$fat_mountpoint"
			G_EXEC rmdir "$root_mountpoint" "$fat_mountpoint"
		fi

		# Exit now if source shall be shrunk only
		(( $SHRINK_ONLY )) && exit 0

		# Image file source and dd target
		if [[ $FP_SOURCE_IMG && $CLONING_TOOL == 'dd' ]]
		then
			# Clear loop
			Delete_Loopback

			G_DIETPI-NOTIFY 2 "Truncating final image file to actually used size: $(( $IMAGE_SIZE / 1024**2 + 1 )) MiB"
			G_EXEC truncate -s "$IMAGE_SIZE" "$FP_SOURCE_IMG"

			# Rename if source image != output image yet
			[[ $(readlink -f "$FP_FINAL") != "$(readlink -f "$FP_SOURCE_IMG")" ]] && G_EXEC mv "$FP_SOURCE_IMG" "$FP_FINAL"

			# Check for sufficient free disk space to store the xz archive with 100 MiB buffer
			(( $SKIP_ARCHIVE )) || G_CHECK_FREESPACE . $(( $IMAGE_SIZE * 15/100 / 1024**2 + 100 )) || exit 1

		# Drive source and dd target
		elif [[ $CLONING_TOOL == 'dd' ]]
		then
			# Check for sufficient free disk space to store the image and in case the xz archive with 100 MiB buffer
			local free_space_percent=100
			(( $SKIP_ARCHIVE )) || free_space_percent=115 # 15% image size for xz archive
			G_CHECK_FREESPACE . $(( $IMAGE_SIZE * $free_space_percent/100 / 1024**2 + 100 )) || exit 1

			G_DIETPI-NOTIFY 2 "Creating final image with actually used size: $(( $IMAGE_SIZE / 1024**2 + 1 )) MiB"
			G_EXEC_OUTPUT=1 G_EXEC dd if="$FP_SOURCE" of="$FP_FINAL" bs=1M status=progress count=$(( $IMAGE_SIZE / 1024**2 + 1 ))

		# Clonezilla target
		else
			G_DIETPI-NOTIFY 2 'Creating final image with Clonezilla'

			# Install required packages
			G_AG_CHECK_INSTALL_PREREQ unzip clonezilla partclone xz-utils syslinux-common xorriso isolinux
			# - Bullseye/Focal: We need Clonezilla 5.x for loop device support
			if dpkg --compare-versions "$(dpkg-query -Wf '${Version}' clonezilla 2> /dev/null)" lt 5
			then
				G_EXEC curl -sSf 'https://deb.debian.org/debian/pool/main/c/clonezilla/clonezilla_5.3.17-2_all.deb' -o clonezilla.deb
				G_EXEC dpkg -i ./clonezilla.deb
				G_EXEC rm clonezilla.deb
			fi

			# Get latest version of Clonezilla Live
			CLONEZILLA_VERSION=$(curl -sSf "$CLONEZILLA_REPO/" | mawk -F\" '/class="folder "/{print $2}' | head -1)
			[[ $CLONEZILLA_VERSION ]] || Error_Exit 'Could not retrieve latest Clonezilla Live version string, aborting...'
			CLONEZILLA_URL="$CLONEZILLA_REPO/$CLONEZILLA_VERSION/clonezilla-live-$CLONEZILLA_VERSION-amd64.zip/download"

			# Check for sufficient free disk space to store the bundled Clonezille Live + DietPi directory and ISO with 100 MiB buffer
			# - Get Clonezilla Live ISO size, which matches sufficiently well the unpacked archive: Remove trailing carriage return
			CLONEZILLA_SIZE=$(curl -sSfIL "${CLONEZILLA_URL/amd64.zip/amd64.iso}" | mawk '/^[Cc]ontent-[Ll]ength:/{print $2}' | tail -1); CLONEZILLA_SIZE=${CLONEZILLA_SIZE%$'\r'}
			(( $CLONEZILLA_SIZE )) || Error_Exit 'Could not retrieve Clonezilla Live size, aborting...'
			NEEDED_FREE_SPACE=$(( ( $CLONEZILLA_SIZE + $IMAGE_SIZE * 20/100 ) * 2 / 1024**2 + 100 )) REMOVE_IMG=0
			# If free space is insufficient and the source is an image on the same filesystem, check whether it fits when we remove the source image before generating the ISO
			if ! G_CHECK_FREESPACE . "$NEEDED_FREE_SPACE"
			then
				[[ $FP_SOURCE_IMG && $(findmnt -Ufnro TARGET -T "$FP_SOURCE_IMG") == $(findmnt -Ufnro TARGET -T .) ]] || exit 1
				G_DIETPI-NOTIFY 2 'Insufficient free space for ISO directory + file, checking whether it would be sufficient after removing source image file...'
				G_CHECK_FREESPACE . $(( $NEEDED_FREE_SPACE - $IMAGE_SIZE / 1024**2 )) || exit 1
				REMOVE_IMG=1
			fi

			# Download
			G_EXEC_DESC="Downloading Clonezilla Live v$CLONEZILLA_VERSION" G_EXEC_OUTPUT=1 G_EXEC curl -fL "$CLONEZILLA_URL" -o clonezilla.zip

			# Extract
			[[ -e 'tmpiso' ]] && G_EXEC rm -R tmpiso
			G_EXEC unzip clonezilla.zip -d 'tmpiso'
			G_EXEC rm clonezilla.zip

			# Clone disk
			G_EXEC mkdir -p tmpiso/home/partimag
			G_EXEC_DESC='Cloning disk with Clonezilla' G_EXEC_OUTPUT=1 G_EXEC ocs-sr -or "$PWD/tmpiso/home/partimag" -nogui -fsck-y -q2 -b -j2 -z5p -i 4096 -senc -sc savedisk "$OUTPUT_IMG_NAME" "${FP_SOURCE##*/}"

			# Remove loop device
			Delete_Loopback

			# For the sake of privacy, remove some non vital files that contain SNs and UUIDs
			G_EXEC rm -f "tmpiso/home/partimag/$OUTPUT_IMG_NAME/"{Info*txt,*list,clonezilla-img}

			# Check image
			G_EXEC_DESC='Checking Clonezilla image' G_EXEC_OUTPUT=1 G_EXEC ocs-chkimg -or "$PWD/tmpiso/home/partimag" -nogui -b "$OUTPUT_IMG_NAME"

			### Prepare custom files used by the installer when booting in UEFI mode ###
			# Create a GRUB theme for the main menu
			G_EXEC curl -sSf "$DIETPI_REPO/.build/images/Clonezilla/dietpi-background_768p.png" -o tmpiso/boot/grub/dietpibg.png
			G_EXEC curl -sSf "$DIETPI_REPO/.build/images/Clonezilla/select_bkg_c.png" -o tmpiso/boot/grub/select_bkg_c.png
			cat << '_EOF_' > tmpiso/boot/grub/theme.txt
title-text: ""
desktop-image: "dietpibg.png"
desktop-image-scale-method: "crop"
terminal-font: "Unifont Regular 16"
terminal-width: "100%"
terminal-height: "100%"
message-color: "#ffffff"
+ boot_menu {
    width = 60%
    height = 80%
    item_spacing = 10
    item_color = "#000000"
    selected_item_color = "#ffffff"
    selected_item_pixmap_style = "select_bkg_*.png"
}
_EOF_
			# Make the original Clonezilla Live menu a submenu of a simplified DietPi install main menu
			G_EXEC mv tmpiso/boot/grub/{grub,clonezilla}.cfg
			sed '/^menuentry /,$d' tmpiso/boot/grub/clonezilla.cfg > tmpiso/boot/grub/grub.cfg
			sed -n '/menuentry .*Safe graphic/,/}/s/^  //p' tmpiso/boot/grub/clonezilla.cfg >> tmpiso/boot/grub/grub.cfg
			# shellcheck disable=SC2016
			sed --follow-symlinks -i  -e '/^set timeout=/c\set timeout="-1"' -e '/^set pref=/a\set theme=\$pref/theme.txt' -e '/^menuentry /c\menuentry "Install DietPi" {' \
				-e 's/locales= /locales=C.UTF-8 /' -e 's/keyboard-layouts= /keyboard-layouts=gb /' -e 's/ocs-live-general/ocs-live-restore/' \
				-e "s|ocs_live_extra_param=\"\"|ocs_live_extra_param=\"-icds -k1 -r -e2 -j2 -b -p poweroff restoredisk $OUTPUT_IMG_NAME ask_user\"|" \
				-e 's/ocs_live_batch="no"/ocs_live_batch="yes"/' tmpiso/boot/grub/grub.cfg
			cat << '_EOF_' >> tmpiso/boot/grub/grub.cfg
submenu "Clonezilla live" { configfile /boot/grub/clonezilla.cfg }
menuentry "Power off" { halt }
_EOF_
			### Prepare custom files used by the installer when booting in BIOS/CSM mode ###
			# Make the original Clonezilla Live menu a submenu of a simplified DietPi install main menu
			G_EXEC curl -sSf "$DIETPI_REPO/.build/images/Clonezilla/dietpi-background_480p.png" -o tmpiso/syslinux/dietpibg.png
			G_EXEC cp /usr/lib/syslinux/modules/bios/poweroff.c32 tmpiso/syslinux/
			G_EXEC mv tmpiso/syslinux/{syslinux,clonezilla}.cfg
			sed --follow-symlinks -i '/^MENU TITLE/c\MENU TITLE Clonezilla live' tmpiso/syslinux/clonezilla.cfg
			sed '/^label /,$d' tmpiso/syslinux/clonezilla.cfg > tmpiso/syslinux/syslinux.cfg
			sed -n '/^label .*framebuffer/,/ENDTEXT/p' tmpiso/syslinux/clonezilla.cfg >> tmpiso/syslinux/syslinux.cfg
			sed --follow-symlinks -i  -e '/^timeout /c\timeout 0' -e 's|\(MENU BACKGROUND\) .*|\1 dietpibg.png|' -e '/^MENU TITLE /c\MENU TABMSG' \
				-e '/menu title/d' -e '/^say /d' -e '/MENU MARGIN/a\MENU HSHIFT 80\n MENU COLOR BORDER 0 #00000000 #00000000 none' \
				-e '/^label /c\label Install DietPi' -e '/^  MENU LABEL /c\  MENU LABEL Install DietPi' -e '/^  TEXT HELP/,/^  ENDTEXT/d' \
				-e 's/locales= /locales=C.UTF-8 /' -e 's/keyboard-layouts= /keyboard-layouts=gb /' -e 's/ocs-live-general/ocs-live-restore/' \
				-e "s|ocs_live_extra_param=\"\"|ocs_live_extra_param=\"-icds -k1 -r -e2 -j2 -b -p poweroff restoredisk $OUTPUT_IMG_NAME ask_user\"|" \
				-e 's/ocs_live_batch="no"/ocs_live_batch="yes"/' tmpiso/syslinux/syslinux.cfg
			cat << '_EOF_' >> tmpiso/syslinux/syslinux.cfg
MENU BEGIN Clonezilla live
  INCLUDE clonezilla.cfg
MENU END
label Power off
  MENU LABEL Power off
  COM32 poweroff.c32
MENU END
_EOF_
			G_EXEC cp tmpiso/syslinux/{syslinux,isolinux}.cfg

			# Removing source image if required
			(( $REMOVE_IMG )) && G_EXEC rm "$FP_SOURCE_IMG"

			# Generate ISO file with Clonezilla Live + DietPi image
			G_EXEC_DESC="Generating $FP_FINAL" G_EXEC_OUTPUT=1 G_EXEC xorriso \
				-as mkisofs -R -r -J -joliet-long -l -iso-level 3 -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -partition_offset 16 \
				-publisher 'DietPi - Lightweight justice for your SBC; https://dietpi.com/;' -volid 'DIETPI_INSTALLER' \
				-A "clonezilla-live-$CLONEZILLA_VERSION-amd64" -b syslinux/isolinux.bin -c syslinux/boot.cat -no-emul-boot -boot-load-size 4 \
				-boot-info-table -eltorito-alt-boot --efi-boot boot/grub/efi.img -isohybrid-gpt-basdat -isohybrid-apm-hfsplus \
				-o "$FP_FINAL" tmpiso
			G_EXEC rm -R tmpiso
		fi

		# Move GPT backup partition table to end of drive
		if [[ $CLONING_TOOL != 'Clonezilla' && $PART_TABLE_TYPE == 'gpt' ]]
		then
			G_EXEC_DESC='Re-creating GPT backup partition table and header at end of image' G_EXEC_OUTPUT=1 G_EXEC sgdisk -e "$FP_FINAL"
			G_EXEC sync
		fi

		# Generate xz archive if requested
		local archive_text=''
		if (( ! $SKIP_ARCHIVE ))
		then
			[[ -f $FP_FINAL.xz ]] && G_EXEC rm "$FP_FINAL.xz"
			G_EXEC_DESC='Creating final xz archive' G_EXEC xz -9e -T0 -M75% -k "$FP_FINAL"
			FP_FINAL+='.xz'
			archive_text="\nxz archive:  $FP_FINAL"
		fi

		# Generate SHA256 hash
		G_EXEC_DESC='Generating SHA256 hash' G_EXEC eval "sha256sum '$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT' > '$FP_FINAL.sha256'"

		# Generate GPG signature if requested
		local sig_text='' signature=()
		if [[ $SIGN_PASS ]]
		then
			G_DIETPI-NOTIFY 2 "Signing $FP_FINAL ..."
			gpg --batch --pinentry-mode loopback --passphrase "$SIGN_PASS" -b --armor "$FP_FINAL" || exit 1
			sig_text="\nSignature:   $FP_FINAL.asc" signature=("$FP_FINAL.asc")
		fi

		G_DIETPI-NOTIFY 0 "DietPi-Imager has successfully finished.
Image file:  ${FP_FINAL%.xz}$archive_text
SHA256 hash: $FP_FINAL.sha256$sig_text"

		# Upload if requested
		if [[ $UPLOAD_SCRIPT ]]
		then
			[[ -x "$UPLOAD_SCRIPT" ]] || { G_DIETPI-NOTIFY 1 "Upload script $UPLOAD_SCRIPT does not exist or is not executable. Aborting ..."; exit 1; }
			G_EXEC_OUTPUT=1 G_EXEC ./upload.sh "$FP_FINAL"{,.sha256} "${signature[@]}" && (( ! $SKIP_ARCHIVE )) && G_EXEC rm -R "$FP_FINAL"{,.sha256} "${signature[@]}"
		fi
	}

	#/////////////////////////////////////////////////////////////////////////////////////
	# Main
	#/////////////////////////////////////////////////////////////////////////////////////
	Main
	#-----------------------------------------------------------------------------------
	exit 0
	#-----------------------------------------------------------------------------------
}