#!/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 -sSfo /tmp/dietpi-globals "https://raw.githubusercontent.com/$G_GITOWNER/DietPi/$G_GITBRANCH/dietpi/func/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;; '14.'*|'forky/sid') G_DISTRO=9;; *) 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, aborting ..." 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_FAT_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= RAW_HASH_SIZE=0 # store SHA256 and size for the uncompressed image, mainly used for Raspberry Pi Imager JSON while (( $# )) do case $1 in '--skip-archive') SKIP_ARCHIVE=1;; '--upload-script') shift; UPLOAD_SCRIPT=$1;; '--add-fat-part') ADD_FAT_PART=1;; '--configs-to-boot') CONFIGS_TO_BOOT=1;; '--sign') shift; SIGN_PASS=$1;; '--raw-hash-size') RAW_HASH_SIZE=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" fi ;; esac shift done Unmount_tmp() { G_EXEC umount -R "$FP_MNT_TMP" } Delete_Loopback(){ [[ $FP_SOURCE_IMG ]] && losetup "$FP_SOURCE" &> /dev/null && G_EXEC losetup -d "$FP_SOURCE"; } # shellcheck disable=SC2329 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)" 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, please retry ...' 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 Enter or Return 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, please retry ...'; read -rp 'Press Enter or Return to return to menu ...'; return 1; } FP_SOURCE_IMG=$( $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 # Dependencies local deps=('fdisk' 'zerofree') (( $SKIP_ARCHIVE )) || deps+=('xz-utils') if [[ $CLONING_TOOL == 'Clonezilla' ]] then deps+=('unzip' 'parted' 'partclone' 'syslinux-common' 'xorriso' 'isolinux') # - Bullseye/Focal: We need Clonezilla 5.x for loop device support if (( $G_DISTRO < 7 )) then G_EXEC curl -sSfo clonezilla.deb 'https://deb.debian.org/debian/pool/main/c/clonezilla/clonezilla_5.3.17-2_all.deb' deps+=('./clonezilla') else deps+=('clonezilla') fi elif [[ $PART_TABLE_TYPE == 'gpt' ]] then deps+=('gdisk') fi G_AGI "${deps[@]}" [[ -f 'clonezilla.deb' ]] && G_EXEC rm clonezilla.deb # Shrink last filesystem to minimum # - resize2fs: "Please run 'e2fsck -f /dev/loop0p1' first." Run_fsck # - Use sfdisk to detect last partition, as lsblk with "-r" option on Bullseye does not sort partitions well: https://github.com/MichaIng/DietPi/issues/7527 local last_part_dev=$(sfdisk -lqo Device "$FP_SOURCE" | tail -1) # - If this is a tailing DietPi setup partition, remove it and let it be recreated freshly in case. This makes it easier for dietpi-build to reuse the same image for multiple variants. if [[ $(blkid -s LABEL -o value "$last_part_dev") == 'DIETPISETUP' ]] then G_EXEC_OUTPUT=1 G_EXEC sfdisk --delete "$FP_SOURCE" "${last_part_dev: -1}" last_part_dev=$(sfdisk -lqo Device "$FP_SOURCE" | tail -1) fi local last_fs_type=$(blkid -s TYPE -o value "$last_part_dev") if [[ $last_fs_type == 'ext'[234] ]] then if [[ $last_fs_type == 'ext'[34] ]] && tune2fs -l "$last_part_dev" | grep -q 'has_journal' then # Disable journal to allow further image size reduction and allow dietpi-fs_partition_resize to re-create it with proper size based final filesystem size G_EXEC tune2fs -O '^has_journal' "$last_part_dev" G_EXEC sync G_SLEEP 1 fi # 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' 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 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 [[ ( -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 if new size would be lower 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 -N${last_part_dev: -1} '$FP_SOURCE' <<< ',$FS_SIZE'" 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=$(sfdisk -d "$FP_SOURCE" | mawk '/^first-lba/{print $2}') # 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 boot 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_FAT_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 MBR 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 # Create a FAT filesystem and add config files to it local new_fat_part=$(sfdisk -l "$FP_SOURCE" | mawk "/ $start /{print \$1;exit}") G_EXEC_OUTPUT=1 G_EXEC mkfs.fat -n DIETPISETUP "$new_fat_part" local fat_mountpoint=$(mktemp -d) local root_mountpoint=$(mktemp -d) G_EXEC mount "$new_fat_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 [[ $(realpath "$FP_FINAL") != "$(realpath "$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' # 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' CLONEZILLA_URL="$CLONEZILLA_REPO/$CLONEZILLA_VERSION/clonezilla-live-$CLONEZILLA_VERSION-amd64.zip/download" # Check for sufficient free disk space to store the bundled Clonezilla 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' 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 # -q2: use partclone (default) # -z5p: multi-threaded xz-compression # -j2: save data between MBR and first partition, e.g. bootloader # -b: batch mode: no ocs prompts # --nogui: no partclone TUI # -fsck-y: auto check+repair source before cloning # -i 4096: split image to max 4 GiB volumes # -senc: do not encrypt image # -sc: skip check if image is restorable 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 # -e2: use CHS geometry info from EDD for partition creation with sfdisk # -icds: skip disk size check before creating partition table # -k1: resize partition after creation # -r: resize filesystem after partition resize # 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 [[ $CLONING_TOOL != 'Clonezilla' && $PART_TABLE_TYPE == 'gpt' ]] && 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" # Generate hashes, size info and compress image if requested local result_text="\nImage file: $FP_FINAL" local upload_list=() if (( $RAW_HASH_SIZE )) then G_EXEC_DESC='Generating image size file' G_EXEC eval "stat -c '%s' '$FP_FINAL' > '$FP_FINAL.size'" result_text+="\nImage size: $FP_FINAL.size" upload_list+=("$FP_FINAL.size") fi if (( $SKIP_ARCHIVE )) then G_EXEC_DESC='Generating image hash' G_EXEC eval "sha256sum '$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT' > '$FP_FINAL.sha256'" result_text+="\nImage hash: $FP_FINAL.sha256" upload_list+=("$FP_FINAL" "$FP_FINAL.sha256") else if (( $RAW_HASH_SIZE )) then G_EXEC_DESC='Generating image hash' G_EXEC eval "sha256sum '$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT' > '$FP_FINAL.sha256'" result_text+="\nImage hash: $FP_FINAL.sha256" upload_list+=("$FP_FINAL.sha256") fi [[ -f $FP_FINAL.xz ]] && G_EXEC rm "$FP_FINAL.xz" G_EXEC_DESC='Generating xz-compressed image' G_EXEC xz -9e -T0 -M75% -k "$FP_FINAL" result_text+="\nxz file: $FP_FINAL.xz" upload_list+=("$FP_FINAL.xz") G_EXEC_DESC='Generating xz hash' G_EXEC eval "sha256sum '$OUTPUT_IMG_NAME.$OUTPUT_IMG_EXT.xz' > '$FP_FINAL.xz.sha256'" result_text+="\nxz hash: $FP_FINAL.xz.sha256\n$(<"$FP_FINAL.xz.sha256")" upload_list+=("$FP_FINAL.xz.sha256") FP_FINAL+='.xz' fi # Generate GPG signature if requested if [[ $SIGN_PASS ]] then G_DIETPI-NOTIFY 2 "Signing $FP_FINAL ..." gpg --batch --pinentry-mode loopback --passphrase "$SIGN_PASS" -b --armor "$FP_FINAL" || Exit_Error 'Signing failed, please check your signing key and passphase' result_text+="\nSignature: $FP_FINAL.asc\n$(<"$FP_FINAL.asc")" upload_list+=("$FP_FINAL.asc") fi G_DIETPI-NOTIFY 0 "DietPi-Imager has successfully finished.$result_text" # Upload if requested if [[ $UPLOAD_SCRIPT ]] then [[ -x "$UPLOAD_SCRIPT" ]] || Error_Exit "Upload script $UPLOAD_SCRIPT does not exist or is not executable" G_EXEC_OUTPUT=1 G_EXEC ./upload.sh "${upload_list[@]}" fi } #///////////////////////////////////////////////////////////////////////////////////// # Main #///////////////////////////////////////////////////////////////////////////////////// Main #----------------------------------------------------------------------------------- exit 0 #----------------------------------------------------------------------------------- }