Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save UnconnectedBedna/8ef639c933a7616d65403025f03e65f9 to your computer and use it in GitHub Desktop.
Save UnconnectedBedna/8ef639c933a7616d65403025f03e65f9 to your computer and use it in GitHub Desktop.
Hotfixed version of shrink-backup v0.9.4 with support for GPT partition table
#!/usr/bin/env bash
#
# shrink-backup
# version 0.9.4b - hotfixed for GPT partition table
# backup tool for backing up and updating .img files with autoexpansion on various operating systems
#
# 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 3 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.
#
# 10/2023
# Marcus Johansson
# https://github.com/UnconnectedBedna/shrink-backup
##############################################################################
# Function to clean up resources on script exit or termination
function cleanup() {
# exit 1 = later/normal error
# exit 2 = early/clean error
# exit 3 = stopped by user
local exit_code="$?"
if [ "$exit_code" -ne 2 ]; then
if [ "$exit_code" -eq 0 ]; then
debug 'DEBUG' 'Cleanup function called with exit 0'
elif [ "$exit_code" -eq 3 ]; then
echo -e '\n## Script stopped by user...'
debug 'WARNING' 'Script stopped by user, cleanup exit 3'
elif [ "$exit_code" -ne 0 ]; then
echo '## Cleanup function called with non zero exit code, something went wrong!!!'
debug 'ERROR' "Cleanup function called with non zero exit code: exit $exit_code"
fi
echo '## Exiting and cleaning up...'
echo '## Please stand by...'
if [ -n "$BOOT_PATH" ] && [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR}${BOOT_PATH} " /proc/mounts; then
umount "${TMP_DIR}${BOOT_PATH}"
debug 'DEBUG' "Unmounting boot partition in cleanup function: umount ${TMP_DIR}${BOOT_PATH}"
fi
if [ -n "$TMP_DIR" ] && grep -qs "$TMP_DIR " /proc/mounts; then
umount "$TMP_DIR"
debug 'DEBUG' "Unmounting root partition in cleanup function: umount $TMP_DIR"
fi
if losetup /dev/loop0 &>/dev/null; then
losetup -d /dev/loop0
debug 'DEBUG' 'Removing loop0 in cleanup function: losetup -d /dev/loop0'
fi
if losetup /dev/loop1 &>/dev/null; then
losetup -d /dev/loop1
debug 'DEBUG' 'Removing loop1 in cleanup function: losetup -d /dev/loop1'
fi
if [ -d "$TMP_DIR" ]; then
rm -rf "$TMP_DIR"
debug 'DEBUG' "Removing temp directory in cleanup function: rm -rf $TMP_DIR"
fi
if [ -f "$tmp_file" ]; then
rm "$tmp_file"
debug 'DEBUG' "Removing temp file in cleanup function: rm $tmp_file"
fi
echo '## Done.'
echo "## Elapsed time: $(date -d@$SECONDS -u +%M.%S)"
debug 'INFO' "Elapsed time: $(date -d@$SECONDS -u +%M.%S)"
debug 'DEBUG' 'Exiting script'
fi
echo '##############################################################################' >> "$LOG_FILE"
}
trap cleanup EXIT SIGTERM
trap "exit 3" SIGINT
# Function to pause script execution and prompt for user input
function pause() {
read -p "$*"
}
# Uncomment the following line to enable the pause function
#pause 'Press [Enter] key to continue...'
# Set default values for script options
PROMPTS=true
DEBUG=false
EXCLUDE_FILE=false
AUTOEXPAND=true
RESIZE2FS_RUN=false
UPDATE=false
LOG_FILE="$(dirname "$0")/shrink-backup.log"
WIGGLE_USE=false
# Function to display help information, debug is not configured/working
help() {
local help
read -r -d '' help << EOM
Script for creating an .img file and subsequently keeing it updated (-U), autoexpansion is enabled by default
Directory where .img file is created is automatically excluded in backup
########################################################################
Usage: sudo $(basename "$0") [-Uatyelh] imagefile.img [extra space (MB)]
-U Update the img file (rsync to existing img), [extra space] extends img size/root partition
-a Let resize2fs decide minimum space (extra space is ignored)
When used in combination with -U:
Expand if img is +256MB smaller resize2fs recommended minimum, shrink if +512MB bigger
-t Use exclude.txt in same folder as script to set excluded directories
One directory per line: "/dir" or "/dir/*" to only exclude contents
-y Disable prompts in script
-e DO NOT expand filesystem when image is booted
-l Write debug messages in logfile shrink-backup.log located in same directory as script
-h --help Show this help snippet
########################################################################
Examples:
sudo $(basename "$0") -a /path/to/backup.img (create img, resize2fs calcualtes size)
sudo $(basename "$0") -e -y /path/to/backup.img 1024 (create img, ignore prompts, do not autoexpand, add 1024MB extra space)
sudo $(basename "$0") -Utl /path/to/backup.img (update img backup, use exclude.txt and write log to shrink-backup.log)
sudo $(basename "$0") -Ua /path/to/backup.img (update img backup, resize2fs calculates and resizes img file if needed)
sudo $(basename "$0") -U /path/to/backup.img 1024 (update img backup, expand img size/root partition with 1024MB)
EOM
echo "$help"
exit 2
}
# Parse command-line options
while getopts ":Uatyelh" opt; do
case ${opt} in
U) UPDATE=true;;
a) RESIZE2FS_RUN=true;;
t) EXCLUDE_FILE=true;;
y) PROMPTS=false;;
e) AUTOEXPAND=false;;
l) DEBUG=true;;
h) help;;
*) help;;
esac
done
#shift $((OPTIND-1))
# Check if script is run as root
if [ "$EUID" != 0 ]; then
echo 'THIS SCRIPT MUST BE RUN WITH SUDO!'
help
fi
function debug() {
local log_level="$1"
local log_message="$2"
if [ "$DEBUG" == true ]; then
if [ "$log_level" == 'BREAK' ]; then
echo '------------------------------------------------------------------------------' >> "$LOG_FILE"
else
if [ $log_level == 'INFO' ]; then
echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE"
else
echo -e "$(date +"%Y-%m-%d %H:%M:%S") [$log_level] - $log_message" >> "$LOG_FILE"
fi
fi
fi
return 0
}
# Function to gather device information
function get_dev_variables() {
# Check if separate boot and root partition exists and set variables accordingly
#LOCAL_DEV_MAJ=$(lsblk -lpo mountpoint,maj:min | grep '/ ' | awk '{print $2}' | cut -d : -f 1)
LOCAL_DEV_PTUUID=$(lsblk -lpo mountpoint,ptuuid | grep '/ ' | awk '{print $2}')
LOCAL_DEV_PATH=$(lsblk -lpo ptuuid,type,path | grep "$LOCAL_DEV_PTUUID" | grep 'disk' | awk '{print $3}')
#LOCAL_ROOT_PARTN=$(lsblk -lpo mountpoint,partn | grep '/ ' | awk '{print $2}') # PARTN can only be used on arch :(
#LOCAL_DEV_PATH=$(lsblk -lpo maj:min,type,path | grep "$LOCAL_DEV_MAJ" | grep 'disk' | awk '{print $3}')
debug 'DEBUG' "LOCAL_DEV_PTUUID=$LOCAL_DEV_PTUUID | LOCAL_DEV_PATH=$LOCAL_DEV_PATH"
if [ $(lsblk | grep -c 'boot') -ne 0 ]; then
debug 'INFO' 'Separate boot partition detected'
#LOCAL_DEV_MIN=$(lsblk -lpo mountpoint,maj:min | grep '/boot' | awk '{print $2}' | cut -d : -f 2)
#LOCAL_DEV_MIN=$(( LOCAL_DEV_MIN - 1 ))
#LOCAL_DEV_PATH=$(lsblk -lpo maj:min,type,path | grep "${LOCAL_DEV_MAJ}:${LOCAL_DEV_MIN}" | grep 'disk' | awk '{print $3}')
LOCAL_DEV_BOOT_PATH=$(lsblk -lpo mountpoint,path | grep 'boot' | awk '{print $2}')
LOCAL_DEV_ROOT_PATH=$(lsblk -lpo mountpoint,path | grep '/ ' | awk '{print $2}')
debug 'DEBUG' "LOCAL_DEV_ROOT_PATH=${LOCAL_DEV_ROOT_PATH} | LOCAL_DEV_BOOT_PATH=$LOCAL_DEV_BOOT_PATH"
else
debug 'INFO' 'No boot partition detected'
#LOCAL_DEV_MIN=$(lsblk -lpo mountpoint,maj:min | grep '/ ' | awk '{print $2}' | cut -d : -f 2)
#LOCAL_DEV_MIN=$(( LOCAL_DEV_MIN - 1 ))
#LOCAL_DEV_PATH=$(lsblk -lpo maj:min,type,path | grep "${LOCAL_DEV_MAJ}:${LOCAL_DEV_MIN}" | grep 'disk' | awk '{print $3}')
LOCAL_DEV_ROOT_PATH=$(lsblk -lpo mountpoint,path | grep '/ ' | awk '{print $2}')
debug 'DEBUG' "LOCAL_DEV_ROOT_PATH=$LOCAL_DEV_ROOT_PATH"
fi
LOCAL_ROOT_PARTN=$(parted -sm "$LOCAL_DEV_PATH" print | tail -1 | cut -d : -f 1)
#debug 'DEBUG' "LOCAL_DEV_PATH=${LOCAL_DEV_PATH} | LOCAL_DEV_MAJ=$LOCAL_DEV_MAJ | LOCAL_ROOT_PARTN=$LOCAL_ROOT_PARTN"
debug 'DEBUG' "LOCAL_ROOT_PARTN=$LOCAL_ROOT_PARTN"
# Collecting data and making calculations
debug 'INFO' 'Calculating size for dd to cover bootsector in blocks (512B block size) and adding 256 blocks to overlap into root (only used in img creation)'
debug 'DEBUG' "Running: fdisk -lo start "$LOCAL_DEV_PATH" | tail -1"
LOCAL_ROOT_START=$(fdisk -lo start "$LOCAL_DEV_PATH" | tail -1 | awk '{print $1}') # blocks, 512B block size
LOCAL_BOOTSECTOR=$(( LOCAL_ROOT_START - 1 )) # blocks, to set the actual bootsector
LOCAL_DDBOOTSECTOR=$(( LOCAL_BOOTSECTOR + 256 )) # blocks, adding 256 extra to cover root filesystem
debug 'DEBUG' "LOCAL_ROOT_START=$LOCAL_ROOT_START Blocks | LOCAL_BOOTSECTOR=$LOCAL_BOOTSECTOR Blocks | LOCAL_DDBOOTSECTOR=$LOCAL_DDBOOTSECTOR Blocks"
LOCAL_ROOT_START=$(( LOCAL_ROOT_START * 512 )) # bytes
LOCAL_BOOTSECTOR=$(( LOCAL_BOOTSECTOR * 512 )) # bytes
debug 'DEBUG' "LOCAL_ROOT_START=$LOCAL_ROOT_START Bytes | LOCAL_BOOTSECTOR=$LOCAL_BOOTSECTOR Bytes"
BLOCKSIZE=$(dumpe2fs -h "$LOCAL_DEV_ROOT_PATH" | grep "Block size" | awk '{print $3}') # bytes
LOCAL_RESIZE2FS_MIN=$(resize2fs -P "$LOCAL_DEV_ROOT_PATH" | awk '{print $7}' | tail -1) # blocks
LOCAL_RESIZE2FS_MIN=$(( LOCAL_RESIZE2FS_MIN * BLOCKSIZE )) # bytes
debug 'DEBUG' "LOCAL_RESIZE2FS_MIN=${LOCAL_RESIZE2FS_MIN} Bytes"
# Method 1, using the value of "size - available"
LOCAL_DF_OUTPUT=( $(df / --output=size,avail | tail -1) ) # 1k blocks, 0 is the first position in an array
LOCAL_USED_SPACE=$(( (${LOCAL_DF_OUTPUT[0]} - ${LOCAL_DF_OUTPUT[1]}) * 1024 )) # bytes, df is in 1k blocks, 0 is the first position
# Method 2, using "used space" straight up
#LOCAL_DF_OUTPUT=( $(df / --output=used | tail -1) ) # 1k blocks
#LOCAL_USED_SPACE=$(( LOCAL_DF_OUTPUT * 1024 )) # bytes, df is in 1k blocks
ADDED_SPACE=$(( ADDED_SPACE * 1024 * 1024 )) # bytes
WIGGLEROOM=134217728 # 128MB = 134217728B, 192MB = 201326592B
# Use resize2fs to calulate size if option is selected
if [ "$RESIZE2FS_RUN" == true ]; then
debug 'INFO' 'Setting TOTAL (space needed for files on root) to size calculated by resize2fs'
TOTAL=$LOCAL_RESIZE2FS_MIN # bytes
else
debug 'INFO' 'Calculating TOTAL (space needed for files on root) by adding LOCAL_USED_SPACE and ADDED_SPACE'
debug 'DEBUG' "LOCAL_USED_SPACE=${LOCAL_USED_SPACE} Bytes"
debug 'DEBUG' "ADDED_SPACE=${ADDED_SPACE} Bytes"
TOTAL=$(( LOCAL_USED_SPACE + ADDED_SPACE )) # bytes
fi
debug 'DEBUG' "TOTAL=${TOTAL} Bytes"
TRUNCATE_TOTAL=$(( LOCAL_BOOTSECTOR + TOTAL )) # bytes
debug 'INFO' 'Calculating .img file size by adding LOCAL_BOOTSECTOR to TOTAL (only used in img creation)'
debug 'DEBUG' "TRUNCATE_TOTAL=${TRUNCATE_TOTAL} Bytes"
#TRUNCATE_TOTAL=$(( BLOCKSIZE + LOCAL_BOOTSECTOR + TOTAL )) # bytes, this was when I was using end instead of start with fdisk
#TRUNCATE_TOTAL=$(( ( 1 + 33 ) * BLOCKSIZE + TOTAL + LOCAL_BOOTSECTOR )) # bytes, something about GPT needing 33 extra blocks
# Add 128MB extra space if resize2fs reports bigger minimum than created
if [ "$UPDATE" == false ] && [ "$TOTAL" -lt "$LOCAL_RESIZE2FS_MIN" ]; then
debug 'WARNING' 'Adding WIGGLEROOM (128MB) because manually requested ADDED_SPACE is less than resize2fs caluclated size'
WIGGLE_USE=true
TRUNCATE_TOTAL=$(( TRUNCATE_TOTAL + WIGGLEROOM ))
debug 'DEBUG' "WIGGLE_USE=true | WIGGLEROOM=${WIGGLEROOM} Bytes | TRUNCATE_TOTAL=${TRUNCATE_TOTAL} Bytes"
fi
return 0
}
# Function to gather image information
function get_img_variables() {
debug 'INFO' 'Using ls to find img size and convert to MB'
debug 'DEBUG' "Running: ls -l $IMG_FILE | awk '{print \$5}'"
IMG_SIZE=$(ls -l "$IMG_FILE" | awk '{print $5}')
debug 'DEBUG' "IMG_SIZE=$IMG_SIZE Bytes"
return 0
}
# Function for shared variables
function get_shared_variables() {
LOOP='/dev/loop0'
debug 'DEBUG' "LOOP=$LOOP"
if [ $(lsblk | grep -c 'boot') -ne 0 ]; then
debug 'INFO' 'Separate boot partition detected'
debug 'INFO' 'Fetching boot mount path from fstab'
debug 'DEBUG' "Running: cat /etc/fstab | grep '/boot' | awk '{print \$2}'"
BOOT_PATH=$(cat /etc/fstab | grep '/boot' | awk '{print $2}')
IMG_DEV_BOOT_PATH='/dev/loop0p1'
IMG_DEV_ROOT_PATH='/dev/loop0p2'
debug 'DEBUG' "BOOT_PATH=$BOOT_PATH | IMG_DEV_BOOT_PATH=$IMG_DEV_BOOT_PATH | IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH"
else
debug 'INFO' 'No boot partition detected'
IMG_DEV_ROOT_PATH='/dev/loop0p1'
debug 'DEBUG' "IMG_DEV_ROOT_PATH=$IMG_DEV_ROOT_PATH"
fi
return 0
}
# Function to loop img file
function do_loop() {
echo '## Looping img file...'
sleep 1
debug 'DEBUG' "Running: losetup -P $LOOP $IMG_FILE"
if ! output=$(losetup -P "$LOOP" "$IMG_FILE" 2>&1); then
echo -e "$output\n## LOSETUP FAILED!!!"
debug 'BREAK'
debug 'ERROR' "LOSETUP FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
return 0
}
# Function to loop and mount img file
function do_mount() {
# Check for temp directory and create if needed
if ! [ -d "$TMP_DIR" ]; then
echo '## Creating temp directory...'
sleep 1
debug 'INFO' 'Creating temp directory'
debug 'DEBUG' "Running: mktemp -d -t backup-XXXXXXXXXX"
TMP_DIR=$(mktemp -d -t backup-XXXXXXXXXX)
debug 'DEBUG' "TMP_DIR=$TMP_DIR"
fi
echo '## Mounting img root partition...'
sleep 1
debug 'INFO' 'Mounting root partition from loop'
debug 'DEBUG' "Running: mount $IMG_DEV_ROOT_PATH $TMP_DIR"
if ! output=$(mount "$IMG_DEV_ROOT_PATH" "$TMP_DIR" 2>&1); then
echo -e "$output\n## ROOT MOUNT FAILED!!!"
debug 'BREAK'
debug 'ERROR' "ROOT MOUNT FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
# Checking if boot partition exists and if true mount boot
if [ $(lsblk | grep -c 'boot') -ne 0 ]; then
echo '## Mounting img boot partition...'
sleep 1
debug 'INFO' 'Separate boot partition detected, mounting boot inside root'
if ! [ -d ${TMP_DIR}${BOOT_PATH} ]; then
debug 'INFO' 'Boot directory did not exist'
debug 'DEBUG' "Running: mkdir -p ${TMP_DIR}${BOOT_PATH}"
mkdir -p ${TMP_DIR}${BOOT_PATH}
fi
debug 'DEBUG' "Running: mount $IMG_DEV_BOOT_PATH ${TMP_DIR}${BOOT_PATH}"
if ! output=$(mount "$IMG_DEV_BOOT_PATH" "${TMP_DIR}${BOOT_PATH}" 2>&1); then
echo -e "$output\n## BOOT MOUNT FAILED!!!"
debug 'BREAK'
debug 'ERROR' "BOOT MOUNT FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
fi
return 0
}
# Function to check filesystem
function do_e2fsck() {
if [ -n "$BOOT_PATH" ] && [ -n "$TMP_DIR" ] && grep -qs "${TMP_DIR}${BOOT_PATH} " /proc/mounts; then
debug 'INFO' 'Unmounting boot partition'
debug 'DEBUG' "Running: umount ${TMP_DIR}${BOOT_PATH}"
umount "${TMP_DIR}${BOOT_PATH}"
fi
if [ -n "$TMP_DIR" ] && grep -qs "$TMP_DIR " /proc/mounts; then
debug 'INFO' 'Unmounting root partition'
debug 'DEBUG' "Running: umount $TMP_DIR"
umount "$TMP_DIR"
fi
if [ "$*" = 'final' ]; then
# Final check of filesystem
echo '## Finalizing filesystem...'
sleep 1
output=$(e2fsck -p -f -v "$IMG_DEV_ROOT_PATH" 2>&1)
echo "$output"
debug 'BREAK'
debug 'DEBUG' "Running: e2fsck -p -f -v $IMG_DEV_ROOT_PATH\n$output\n------------------------------------------------------------------------------"
sleep 1
# Remounting if autoexpansion is requested
if [ "$AUTOEXPAND" == true ]; then
echo '## Remounting for autoexpansion...'
debug 'INFO' 'Remounting for autoexpansion function'
debug 'DEBUG' 'Running function: do_mount'
sleep 1
do_mount
fi
else
echo '## Checking img filesystem...'
sleep 1
output=$(e2fsck -p -f "$IMG_DEV_ROOT_PATH" 2>&1)
echo "$output"
debug 'BREAK'
debug 'DEBUG' "Running: e2fsck -p -f $IMG_DEV_ROOT_PATH\n$output\n------------------------------------------------------------------------------"
sleep 1
fi
return 0
}
# Function to resize image
function do_resize() {
# Reading offset for img root partition
debug 'INFO' 'Using fdisk to find img root partition offset'
debug 'DEBUG' "Running: fdisk -lo start $IMG_FILE | tail -1"
IMG_ROOT_START=$(fdisk -lo start "$IMG_FILE" | tail -1 | awk '{print $1}') # blocks, 521B block size
debug 'DEBUG' "IMG_ROOT_START=$IMG_ROOT_START Blocks"
IMG_ROOT_START=$(( IMG_ROOT_START * 512 )) # bytes
debug 'DEBUG' "IMG_ROOT_START=$IMG_ROOT_START Bytes"
# Converting TOTAL > TOTALK (bytes > kilobytes)
debug 'INFO' 'Converting TOTAL > TOTALK (bytes > kilobytes)'
TOTALK=$(( TOTAL / 1024 )) # kilobytes
debug 'DEBUG' "TOTAL=$TOTAL Bytes | TOTALK=$TOTALK Kilobytes"
# Gather information
debug 'INFO' 'Using lsblk to fetch root partition number'
debug 'DEBUG' "Running: parted -sm "$LOOP" print | tail -1 | cut -d : -f 1"
IMG_ROOT_PARTN=$(parted -sm "$LOOP" print | tail -1 | cut -d : -f 1)
debug 'DEBUG' "IMG_ROOT_PARTN=$IMG_ROOT_PARTN"
# Check img filesystem
debug 'INFO' 'Scanning filesystem'
debug 'DEBUG' 'Running function: do_e2fsck'
do_e2fsck
if [ "$*" = 'expand' ]; then
echo '## Expanding img filesystem...'
sleep 1
# Removing loop for tuncate to take effect
echo '## Removing loop...'
sleep 1
debug 'INFO' 'Removing loop for truncate to take effect'
debug 'DEBUG' "Running: losetup -d $LOOP"
losetup -d "$LOOP"
echo "## Resizing image file..."
sleep 1
debug 'INFO' 'Using truncate to resize img file'
debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE"
if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then
echo -e "$output\n## TRUNCATE FAILED!!!"
debug 'BREAK'
debug 'ERROR' "TRUNCATE FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
# Loop img file
debug 'INFO' 'Re-looping img file to fetch new img size'
debug 'DEBUG' 'Running function: do_loop'
do_loop
echo '## Removing partition...'
sleep 1
debug 'INFO' 'Using sfdisk to remove partition'
debug 'DEBUG' "Running: sfdisk --delete $LOOP $IMG_ROOT_PARTN"
#sfdisk --delete -f "$LOOP" "$IMG_ROOT_PARTN" # seems to fail if img size is very big
#debug 'DEBUG' "Running: parted -s $LOOP rm $IMG_ROOT_PARTN"
#parted -s "$LOOP" rm "$IMG_ROOT_PARTN" # does not work, still asking for user confirmation even though --script is used
#debug 'INFO' 'Using parted to remove root partition'
#debug 'DEBUG' "Running: printf 'Ignore\\\n'$IMG_ROOT_PARTN | parted $LOOP rm $IMG_ROOT_PARTN ---pretend-input-tty"
if ! output=$(sfdisk --delete -f "$LOOP" "$IMG_ROOT_PARTN" 2>&1); then
#if ! output=$(printf 'Ignore\n'$IMG_ROOT_PARTN | parted $LOOP rm $IMG_ROOT_PARTN ---pretend-input-tty 2>&1); then
echo -e "$output\n## SFDISK FAILED!!!"
#echo -e "$output\n## PARTED FAILED!!!"
debug 'BREAK'
debug 'ERROR' "SFDISK FAILED:\n$output\n------------------------------------------------------------------------------"
#debug 'ERROR' "PARTED FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
echo '## Recreating partition...'
sleep 1
debug 'INFO' 'Using parted to recreate root partition'
debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary ext4 $IMG_ROOT_START 100%"
if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary ext4 "$IMG_ROOT_START" 100% 2>&1); then
echo -e "$output\n## PARTED FAILED!!!"
debug 'BREAK'
debug 'ERROR' "PARTED FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
debug 'DEBUG' "$output\n------------------------------------------------------------------------------"
echo '## Resizing filesystem...'
sleep 1
debug 'INFO' 'Using resize2fs to expand filesystem'
debug 'BREAK'
debug 'DEBUG' "Running: resize2fs -p -f $IMG_DEV_ROOT_PATH"
if ! output=$(resize2fs -p -f "$IMG_DEV_ROOT_PATH" 2>&1); then
echo -e "$output\n## RESIZE2FS FAILED!!!"
debug 'BREAK'
debug 'ERROR' "RESIZE2FS FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
# Check img filesystem
debug 'INFO' 'Scanning filesystem'
debug 'DEBUG' 'Running function: do_e2fsck'
do_e2fsck
elif [ "$*" = 'shrink' ]; then
echo '## Shrinking filesystem...'
sleep 1
debug 'INFO' 'Using resize2fs to shrink filesystem'
debug 'BREAK'
debug 'DEBUG' "Running: resize2fs -p -f $IMG_DEV_ROOT_PATH ${TOTALK}K"
if ! output=$(resize2fs -p -f "$IMG_DEV_ROOT_PATH" "$TOTALK"K 2>&1 | tee /dev/tty); then
echo -e "$output\n## RESIZE2FS FAILED!!!"
debug 'BREAK'
debug 'ERROR' "RESIZE2FS FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
debug 'DEBUG' "$output\n------------------------------------------------------------------------------"
echo '## Shrinking partition...'
sleep 1
debug 'INFO' 'Using parted to shrink partition'
#debug 'DEBUG' "Running: parted -s -a none $LOOP unit B resizepart $IMG_ROOT_PARTN $TRUNCATE_TOTAL"
debug 'DEBUG' "Running: printf 'Yes\\\n' | parted -a none $LOOP unit B resizepart $IMG_ROOT_PARTN $TRUNCATE_TOTAL ---pretend-input-tty"
#parted -s -a none "$LOOP" unit B resizepart "$IMG_ROOT_PARTN" "$TOTAL"
#if ! output=$(parted -s -a none "$LOOP" unit B resizepart "$IMG_ROOT_PARTN" "$TRUNCATE_TOTAL" 2>&1); then # does not work, still asking for user confirmation even though --script is used
if ! output=$(printf 'Yes\n' | parted -a none "$LOOP" unit B resizepart "$IMG_ROOT_PARTN" "$TRUNCATE_TOTAL" ---pretend-input-tty 2>&1); then
echo -e "$output\n## PARTED FAILED!!!"
debug 'BREAK'
debug 'ERROR' "PARTED FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
debug 'DEBUG' "$output\n------------------------------------------------------------------------------"
echo '## Shrinking img file...'
sleep 1
debug 'INFO' 'Using truncate to shrink img file'
debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE"
if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then
echo -e "$output\n## TRUNCATE FAILED!!!"
debug 'BREAK'
debug 'ERROR' "TRUNCATE FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
# Final check of img filesystem
debug 'INFO' 'Finalizing filesystem'
debug 'DEBUG' "Running do_e2fsck 'final'"
do_e2fsck 'final'
fi
return 0
}
# Function to rsync to img file
function do_rsync() {
echo '## Backing up files...'
sleep 1
IMG_PATH=$(dirname "$IMG_FILE")
debug 'INFO' 'Creating temporary file to store rsync output'
debug 'DEBUG' "Backing up to ${IMG_PATH}${IMG_FILE}"
debug 'DEBUG' 'Running: mktemp -t rsync-XXXXXXXXXX'
tmp_file=$(mktemp -t rsync-XXXXXXXXXX)
debug 'DEBUG' "tmp_file=$tmp_file"
if [ "$EXCLUDE_FILE" == true ]; then
debug 'DEBUG' "Running: rsync -ahvHAX --exclude-from=$(dirname $0)/exclude.txt --exclude={${IMG_PATH}/*,${TMP_DIR},${tmp_file}} --info=progress2 --stats --delete --force --partial --delete-excluded / $TMP_DIR"
rsync -ahvHAX --exclude-from=$(dirname "$0")/exclude.txt --exclude={${IMG_PATH}/*,${TMP_DIR},${tmp_file}} --info=progress2 --stats --delete --force --partial --delete-excluded / "$TMP_DIR" 2>&1 | tee /dev/tty > "$tmp_file"
# Get the exit status of rsync from PIPESTATUS
if [ "${PIPESTATUS[0]}" -ne 0 ]; then #code 23
output=$(tail -16 "$tmp_file")
echo -e "$output\n## RSYNC FAILED!!!"
debug 'BREAK'
debug 'ERROR' "RSYNC FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
else
debug 'DEBUG' "Running: rsync -ahvHAX --exclude={/lost+found,/proc/*,/sys/*,/dev/*,/tmp/*,/run/*,/mnt/*,/media/*,${IMG_PATH}/*} --info=progress2 --stats --delete --force --partial --delete-excluded / $TMP_DIR"
rsync -ahvHAX --exclude={/lost+found,/proc/*,/sys/*,/dev/*,/tmp/*,/run/*,/mnt/*,/media/*,${IMG_PATH}/*} --info=progress2 --stats --delete --force --partial --delete-excluded / "$TMP_DIR" 2>&1 | tee /dev/tty > "$tmp_file"
if [ "${PIPESTATUS[0]}" -ne 0 ]; then #code 23
output=$(tail -16 "$tmp_file")
echo -e "$output\n## RSYNC FAILED!!!"
debug 'BREAK'
debug 'ERROR' "RSYNC FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
fi
echo '## Rsync done...'
echo '## Please stand by...'
output=$(tail -16 "$tmp_file")
debug 'BREAK'
debug 'DEBUG' "Rsync report:\n$output\n------------------------------------------------------------------------------"
debug 'INFO' 'Rsync done'
sleep 4
return 0
}
# Function to create a backup img file
function make_img() {
debug 'DEBUG' 'Running function: get_dev_variables'
get_dev_variables
debug 'DEBUG' 'Running function: get_shared_variables'
get_shared_variables
# Display information
if [ "$PROMPTS" == true ]; then
echo ''
echo '##############################################################################'
echo "# A backup will be created at $IMG_FILE"
echo '# ----------------------------------------------------------------------------'
echo "# Write to logfile: $DEBUG"
echo "# Resize2fs decide size: $RESIZE2FS_RUN"
echo "# Autoexpand filesystem at boot: $AUTOEXPAND"
echo "# Use exclude.txt: $EXCLUDE_FILE"
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Estemated root usage: $(( $(df / --output=used | tail -1) / 1024 ))MB"
if [ "$RESIZE2FS_RUN" == true ]; then
echo "# Resize2fs decide minimum (root partition): $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
echo "# Total img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
else
echo "# Manually added space: $(( ADDED_SPACE / 1024 / 1024 ))MB"
echo "# Total img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB with $(( ADDED_SPACE / 1024 / 1024 ))MB extra space included."
if [ "$WIGGLE_USE" == true ]; then
echo '# ----------------------------------------------------------------------------'
echo "# WARNING!!! Manually added space is smaller than resize2fs recommended minimum"
echo "# This does NOT mean the backup WILL fail, but CAN fail due to lack of space"
echo "# Concider using the -a option or adding more space"
echo "# Requested root size: $(( TOTAL / 1024 / 1024 ))MB"
echo "# Resize2fs recommended minimum: $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
fi
fi
echo '##############################################################################'
# Confirm with user input
read -p "Do you want to continue? [y/n] " -n 1 -r
debug 'INFO' 'Do you want to continue? [y/n]'
if ! [[ "$REPLY" =~ ^[Yy]$ ]]; then
debug 'USER_INPUT' 'Aborted by user, clean exit 2'
echo ''
echo 'Aborting...'
exit 2
fi
echo ''
debug 'USER_INPUT' 'Y or y pressed to confirm'
if test -f "$IMG_FILE"; then
debug 'WARNING' "$IMG_FILE ALREADY EXISTS!"
echo 'WARNING!!! WARNING!!! WARNING!!!'
echo "$IMG_FILE"
echo 'FILE ALREADY EXISTS!!!'
read -p "Do you want to overwrite? [y/n] " -n 1 -r
debug 'INFO' 'Do you want to overwrite? [y/n]'
if ! [[ "$REPLY" =~ ^[Yy]$ ]]; then
debug 'USER_INPUT' 'Aborted by user, clean exit 2'
echo ''
echo 'Aborting...'
exit 2
fi
echo ''
debug 'USER_INPUT' 'Overwrite confirmed by user'
fi
else
debug 'INFO' '-y selected by user. prompts are disabled'
echo '##############################################################################'
echo '# DISABLE PROMPTS SELECTED (-y), NO WARNINGS ABOUT DELETION!!!'
echo "# A backup will be created at $IMG_FILE"
echo '# ----------------------------------------------------------------------------'
echo "# Write to logfile: $DEBUG"
echo "# Resize2fs decide size: $RESIZE2FS_RUN"
echo "# Autoexpand filesystem at boot: $AUTOEXPAND"
echo "# Use exclude.txt: $EXCLUDE_FILE"
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Estemated root usage: $(( $(df / --output=used | tail -1) / 1024 ))MB"
if [ "$RESIZE2FS_RUN" == true ]; then
echo "# Resize2fs decide minimum (root partition): $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
echo "# Total img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
else
echo "# Manually added space: $(( ADDED_SPACE / 1024 / 1024 ))MB"
echo "# Total img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB with $(( ADDED_SPACE / 1024 / 1024 ))MB extra space included."
if [ "$WIGGLE_USE" == true ]; then
echo '# ----------------------------------------------------------------------------'
echo "# WARNING!!! Manually added space is smaller than resize2fs recommended minimum"
echo "# This does NOT mean the backup WILL fail, but CAN fail due to lack of space"
echo "# Concider using the -a option or adding more space"
echo "# Requested root size: $(( TOTAL / 1024 / 1024 ))MB"
echo "# Resize2fs recommended minimum: $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
fi
fi
echo '# PRESS CTRL+C WITHIN 5s TO CANCEL!'
echo '##############################################################################'
sleep 6
debug 'INFO' '6 seconds passed, user did not stop operation'
fi
# Delete existing file if user validation above passed
if [ -f "$IMG_FILE" ]; then
debug 'DEBUG' "Removing: $IMG_FILE"
echo '## Removing old img file...'
rm "$IMG_FILE"
sleep 1
fi
# Create and dd bootsector
echo '## Creating bootsector...'
sleep 1
debug 'INFO' 'Using dd to create bootsector'
if ! output=$(dd bs=512 count=$LOCAL_DDBOOTSECTOR if="$LOCAL_DEV_PATH" of="$IMG_FILE" conv=noerror,sync status=progress 2>&1 | tee /dev/tty); then
echo -e "$output\n## DD TO LOCAL_BOOTSECTOR FAILED!!!"
debug 'BREAK'
debug 'ERROR' "DD TO LOCAL_BOOTSECTOR FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
output=$(echo "$output" | tail -3 )
debug 'BREAK'
debug 'DEBUG' "Running: dd bs=512 count=$LOCAL_DDBOOTSECTOR if=$LOCAL_DEV_PATH of=$IMG_FILE conv=noerror,sync status=progress\n$output\n------------------------------------------------------------------------------"
sleep 1
# Truncate file to correct size
echo "## Resizing img file..."
sleep 1
debug 'INFO' "Using truncate to resize img file' to $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
debug 'DEBUG' "Running: truncate --size=$TRUNCATE_TOTAL $IMG_FILE"
if ! output=$(truncate --size="$TRUNCATE_TOTAL" "$IMG_FILE" 2>&1); then
echo -e "$output\n## TRUNCATE FAILED!!!"
debug 'BREAK'
debug 'ERROR' "TRUNCATE FAILED:\n$output\n------------------------------------------------------------------------------"
exit 1
fi
sleep 1
# Loop img file
debug 'INFO' 'Looping img file'
debug 'DEBUG' 'Running function: do_loop'
do_loop
# Remove partition
echo '## Removing root partition...'
sleep 1
#pause 'Press [Enter] key to continue...'
#debug 'INFO' 'Using sfdisk to remove root partition'
#debug 'INFO' 'Using parted to remove root partition'
debug 'INFO' 'Using sgdisk to remove root partition'
#debug 'DEBUG' "Running: sfdisk --delete -f $LOOP $LOCAL_ROOT_PARTN"
#debug 'DEBUG' "Running: printf 'Ignore\\\n'$LOCAL_ROOT_PARTN | parted $LOOP rm $LOCAL_ROOT_PARTN ---pretend-input-tty"
#debug 'DEBUG' "Running: parted -s $LOOP rm $LOCAL_ROOT_PARTN"
debug 'DEBUG' "Running: sgdisk $LOOP -d $LOCAL_ROOT_PARTN"
#if ! output=$(sfdisk --delete -f "$LOOP" "$LOCAL_ROOT_PARTN" 2>&1); then # seems to fail if img size is very big
#if ! output=$(printf 'Ignore\n'$LOCAL_ROOT_PARTN | parted $LOOP rm $LOCAL_ROOT_PARTN ---pretend-input-tty 2>&1); then # fails on raspberry pi os
#if ! output=$(parted -s "$LOOP" rm "$LOCAL_ROOT_PARTN" 2>&1); then # does not work, still asking for user confirmation even though --script is used
#printf 'Ignore\nok\n'$LOCAL_ROOT_PARTN | parted $LOOP rm $LOCAL_ROOT_PARTN ---pretend-input-tty
sgdisk "$LOOP" -d "$LOCAL_ROOT_PARTN"
# echo -e "$output\n## SFDISK FAILED!!!"
# #echo -e "$output\n## PARTED FAILED!!!"
# debug 'BREAK'
# #debug 'ERROR' "SFDISK FAILED:\n$output\n------------------------------------------------------------------------------"
# debug 'ERROR' "PARTED FAILED:\n$output\n------------------------------------------------------------------------------"
# exit 1
# fi
sleep 1
# Recreate partition
echo '## Recreating root partition...'
sleep 1
#pause 'Press [Enter] key to continue...'
debug 'INFO' 'Using parted to recreate root partition'
debug 'DEBUG' "Running: parted -s -a none $LOOP unit B mkpart primary ext4 $LOCAL_ROOT_START 100%"
#if ! output=$(parted -s -a none "$LOOP" unit B mkpart primary ext4 "$LOCAL_ROOT_START" 100% 2>&1); then
parted -s -a none "$LOOP" unit B mkpart primary ext4 "$LOCAL_ROOT_START" 100%
# echo -e "$output\n## PARTED FAILED!!!"
# debug 'BREAK'
# debug 'ERROR' "PARTED FAILED:\n$output\n------------------------------------------------------------------------------"
# exit 1
# fi
sleep 1
# Format filesystem
echo '## Formatting filesystem...'
sleep 1
#pause 'Press [Enter] key to continue...'
debug 'INFO' 'Using mkfs.ext4 to format root filesystem'
LABEL=( $(lsblk -o label "$LOCAL_DEV_ROOT_PATH" | tail -1) )
UUID=( $(lsblk -o uuid "$LOCAL_DEV_ROOT_PATH" | tail -1) )
mkfs.ext4 "$IMG_DEV_ROOT_PATH" -U "$UUID" -L "$LABEL" -v 2>&1 | tee /dev/tty
# if ! output=$(mkfs.ext4 "$IMG_DEV_ROOT_PATH" -U "$UUID" -L "$LABEL" -v 2>&1 | tee /dev/tty); then
# echo -e "$output\n## MKFS.EXT4 FAILED!!!"
# debug 'BREAK'
# debug 'ERROR' "MKFS.EXT4 FAILED:\n$output\n------------------------------------------------------------------------------"
# exit 1
# fi
debug 'BREAK'
debug 'DEBUG' "Running: mkfs.ext4 $IMG_DEV_ROOT_PATH -U $UUID -L $LABEL -F -v\n$output\n------------------------------------------------------------------------------"
sleep 1
pause 'Press [Enter] key to continue...'
# Check img filesystem
debug 'INFO' 'Scanning filesystem'
debug 'DEBUG' 'Running function: do_e2fsck'
do_e2fsck
# Mount img file
debug 'INFO' 'Mounting img file'
debug 'DEBUG' 'Running function: do_mount'
do_mount
#pause 'Press [Enter] key to continue...'
# Copy files
debug 'INFO' 'Backing up files'
debug 'DEBUG' 'Running function: do_rsync'
do_rsync
# Final check of created img file
debug 'INFO' 'Finalizing filesystem'
debug 'DEBUG' "Running do_e2fsck 'final'"
do_e2fsck 'final'
return 0
}
# Function to update existing img file
function do_backup() {
# Making sure img file exists
if ! [ -f "$IMG_FILE" ]; then
echo "## ERROR. $IMG_FILE does not exist!"
debug 'ERROR' "$IMG_FILE does not exist, exit 1"
exit 1
fi
debug 'DEBUG' 'Running function: get_img_variables'
get_img_variables
debug 'DEBUG' 'Running function: get_shared_variables'
get_shared_variables
if [ "$RESIZE2FS_RUN" == true ] || [ "$ADDED_SPACE" -ne 0 ]; then
debug 'DEBUG' 'Running function: get_dev_variables'
get_dev_variables
fi
# Loop img file
debug 'INFO' 'Looping img file'
debug 'DEBUG' 'Running function: do_loop'
do_loop
# Checking if resizing should be performed
if [ "$RESIZE2FS_RUN" == true ]; then
debug 'DEBUG' "Running: fdisk --bytes -lo size $LOOP | tail -1"
IMG_ROOT_SIZE=$(fdisk --bytes -lo size "$LOOP" | tail -1)
diff=$(( ( LOCAL_RESIZE2FS_MIN - IMG_ROOT_SIZE ) / 1024 / 1024 ))
debug 'DEBUG' "IMG_ROOT_SIZE=$IMG_ROOT_SIZE Bytes | LOCAL_RESIZE2FS_MIN=$LOCAL_RESIZE2FS_MIN Bytes | diff=$diff MB"
if [ "$IMG_ROOT_SIZE" -lt "$LOCAL_RESIZE2FS_MIN" ] && (( LOCAL_RESIZE2FS_MIN - IMG_ROOT_SIZE >= 268435456 )); then # 256MB in Bytes
DIFFERENCE="$diff MB, Expanding img filesystem"
elif [ "$IMG_ROOT_SIZE" -gt "$LOCAL_RESIZE2FS_MIN" ] && (( IMG_ROOT_SIZE - LOCAL_RESIZE2FS_MIN >= 536870912 )); then # 512MB in Bytes
DIFFERENCE="$diff MB, Shrinking img filesystem"
else
DIFFERENCE="Too small ($diff MB), no resizing needed"
fi
elif [ "$ADDED_SPACE" -ne 0 ]; then
debug 'INFO' 'Manually added space provided by user, calculating img size (TRUNCATE_TOTAL) by adding ADDED_SPACE to IMG_SIZE'
TRUNCATE_TOTAL=$(( ADDED_SPACE + IMG_SIZE ))
debug 'DEBUG' "TRUNCATE_TOTAL=$TRUNCATE_TOTAL"
fi
# Display information
if [ "$PROMPTS" == true ]; then
echo '##############################################################################'
echo "# Updating $IMG_FILE"
echo '# ----------------------------------------------------------------------------'
echo "# Write to logfile: $DEBUG"
echo "# Resize2fs decide size: $RESIZE2FS_RUN"
echo "# Autoexpand filesystem at boot: $AUTOEXPAND"
echo "# Use exclude.txt: $EXCLUDE_FILE"
if [ "$RESIZE2FS_RUN" == true ]; then
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Resize2fs decide minimum (root partition): $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
echo "# Old img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
echo "# New img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
echo "# Difference: $DIFFERENCE"
elif [ "$ADDED_SPACE" -ne 0 ]; then
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Old img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
echo "# New img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
echo "# Difference: $(( ADDED_SPACE / 1024 / 1024 ))MB"
else
echo "# Used space on root: $(( $(df / --output=used | tail -1) / 1024 ))MB"
echo "# Total img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
fi
echo '##############################################################################'
# Confirm with user input
read -p "Do you want to continue? [y/n] " -n 1 -r
debug 'INFO' 'Do you want to continue? [y/n]'
if ! [[ "$REPLY" =~ ^[Yy]$ ]]; then
debug 'USER_INPUT' 'Aborted by user, cleanup exit 3'
echo ''
echo 'Aborting...'
exit 3
fi
echo ''
debug 'USER_INPUT' 'Y or y pressed to confirm'
else
echo '##############################################################################'
echo '# DISABLE PROMPTS SELECTED'
echo "# Updating $IMG_FILE"
echo '# ----------------------------------------------------------------------------'
echo "# Write to logfile: $DEBUG"
echo "# Resize2fs decide size: $RESIZE2FS_RUN"
echo "# Autoexpand filesystem at boot: $AUTOEXPAND"
echo "# Use exclude.txt: $EXCLUDE_FILE"
if [ "$RESIZE2FS_RUN" == true ]; then
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Resize2fs decide minimum (root partition): $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
echo "# Old img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
echo "# New img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
echo "# Difference: $DIFFERENCE"
elif [ "$ADDED_SPACE" -ne 0 ]; then
echo "# Bootsector size: $(( LOCAL_BOOTSECTOR / 1024 / 1024 ))MB"
echo "# Old img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
echo "# New img size: $(( TRUNCATE_TOTAL / 1024 / 1024 ))MB"
echo "# Difference: $(( ADDED_SPACE / 1024 / 1024 ))MB"
else
echo "# Used space on root: $(( $(df / --output=used | tail -1) / 1024 ))MB"
echo "# Total img size: $(( IMG_SIZE / 1024 / 1024 ))MB"
fi
echo '# PRESS CTRL+C WITHIN 5s TO CANCEL!'
echo '##############################################################################'
sleep 6
debug 'INFO' '6 seconds passed, user did not stop operation'
fi
# Checking if resizing should be performed
if [ "$RESIZE2FS_RUN" == true ]; then
if [ "$IMG_ROOT_SIZE" -lt "$LOCAL_RESIZE2FS_MIN" ] && (( LOCAL_RESIZE2FS_MIN - IMG_ROOT_SIZE >= 268435456 )); then # 256MB in Bytes
debug 'INFO' 'Img root partition size is smaller than resize2fs recommended minimum'
debug 'DEBUG' "Difference=$diff MB"
debug 'DEBUG' "Running function: do_resize 'expand'"
do_resize 'expand'
elif [ "$IMG_ROOT_SIZE" -gt "$LOCAL_RESIZE2FS_MIN" ] && (( IMG_ROOT_SIZE - LOCAL_RESIZE2FS_MIN >= 536870912 )); then # 512MB in Bytes
debug 'INFO' 'Img root partition size is bigger than resize2fs recommended minimum'
debug 'DEBUG' "diff=$diff MB"
# Mount img file
debug 'INFO' 'Mounting img file'
debug 'DEBUG' 'Running function: do_mount'
do_mount
# Copy files
debug 'INFO' 'Backing up files'
debug 'DEBUG' 'Running function: do_rsync'
do_rsync
# Shrink img file
debug 'INFO' 'Shrinking img filesystem'
debug 'DEBUG' "Running function: do_resize 'shrink'"
do_resize 'shrink'
return 0
else
echo '## Difference too small, no resizing needed...'
debug 'INFO' 'Img root partition is >=64MB smaller or <=512MB bigger compared size to resize2fs recommended minimum, not resizing'
sleep 1
fi
fi
# Expand img file if ADDED_SPACE not 0
if [ "$ADDED_SPACE" -ne 0 ]; then
debug 'DEBUG' "Running function: do_resize 'expand'"
do_resize 'expand'
fi
# Mount img file
debug 'INFO' 'Mounting img file'
debug 'DEBUG' 'Running function: do_mount'
do_mount
# Copy files
debug 'INFO' 'Backing up files'
debug 'DEBUG' 'Running function: do_rsync'
do_rsync
# Final check of img filesystem
debug 'INFO' 'Finalizing filesystem'
debug 'DEBUG' "Running do_e2fsck 'final'"
do_e2fsck 'final'
return 0
}
# Enabling autoexpansion for Manjaro
function autoexpansion_manjaro() {
if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then
debug 'DEBUG' "Systemd basic.arget.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants"
mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants
fi
# Creating autoexpansion systemd unit file expand-fs.service
debug 'DEBUG' 'Systemd unit expand-fs.service does not exist, creating it'
cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service"
[Unit]
Description=Extend root partition and resize ext4 file system
After=local-fs.target
Wants=local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c "/usr/bin/resize-fs || exit 0"
ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/bin/reboot -f || exit 0"
[Install]
WantedBy=basic.target
EOF
debug 'DEBUG' "Systemd service not enabled, creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service"
ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service
echo '## Manjaro filesystem autoresizing at boot...'
debug 'INFO' 'Manjaro filesystem autoresizing at boot'
sleep 1
return 0
}
# Enabling autoexpansion for Armbian
function autoexpansion_armbian() {
if ! test -f "${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service"; then
debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /lib/systemd/system/armbian-resize-filesystem.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service"
ln -s /lib/systemd/system/armbian-resize-filesystem.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/armbian-resize-filesystem.service
fi
echo '## Armbian filesystem autoresizing at boot...'
debug 'INFO' 'Armbian filesystem autoresizing at boot'
sleep 1
return 0
}
# Enabling autoexpansion for Raspberry pi
function autoexpansion_rpi() {
if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then
debug 'DEBUG' "Systemd basic.arget.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants"
mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants
fi
# Creating autoexpansion systemd unit file expand-fs.service
debug 'DEBUG' 'Creating systemd unit expand-fs.service'
cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service"
[Unit]
Description=Extend root partition and resize ext4 file system
After=local-fs.target
Wants=local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c "/expand-fs.sh || exit 0"
ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /expand-fs.sh && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/sbin/reboot -f || exit 0"
[Install]
WantedBy=basic.target
EOF
debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service"
ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service
# Creating script for autoexpansion
debug 'DEBUG' "Creating expansion script ${TMP_DIR}/expand-fs.sh"
cat << EOF2 > "${TMP_DIR}/expand-fs.sh"
#!/usr/bin/bash
LOCAL_DEV_PTUUID=\$(lsblk -lpo mountpoint,ptuuid | grep '/ ' | awk '{print \$2}')
LOCAL_DEV_PATH=\$(lsblk -lpo ptuuid,type,path | grep "\$LOCAL_DEV_PTUUID" | grep 'disk' | awk '{print \$3}')
LOCAL_ROOT_PARTN=\$(parted -sm "\$LOCAL_DEV_PATH" print | tail -1 | cut -d : -f 1)
LOCAL_DEV_ROOT_PATH=\$(lsblk -lpo mountpoint,path | grep '/ ' | awk '{print \$2}')
LOCAL_ROOT_START=\$(fdisk -lo start "\$LOCAL_DEV_PATH" | tail -1 | awk '{print \$1}') # blocks, 512B block size
LOCAL_ROOT_START=\$(( LOCAL_ROOT_START * 512 )) # bytes
sfdisk --delete "\$LOCAL_DEV_PATH" "\$LOCAL_ROOT_PARTN"
parted -s -a none "\$LOCAL_DEV_PATH" unit B mkpart primary ext4 "\$LOCAL_ROOT_START" 100%
resize2fs -f "\$LOCAL_DEV_ROOT_PATH"
sync
exit 0
EOF2
debug 'DEBUG' 'Making /expand-fs.sh executable'
chmod +x ${TMP_DIR}/expand-fs.sh
echo '## Raspberry pi filesystem autoresizing at boot...'
debug 'INFO' 'Raspberry pi filesystem autoresizing at boot'
sleep 1
return 0
}
# Enabling autoexpansion for ArchLinuxArm
function autoexpansion_arch() {
if ! [ -d "${TMP_DIR}/etc/systemd/system/basic.target.wants" ]; then
debug 'DEBUG' "Systemd basic.arget.wants directory does not exist, running: mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants"
mkdir ${TMP_DIR}/etc/systemd/system/basic.target.wants
fi
# Creating autoexpansion systemd unit file expand-fs.service
debug 'DEBUG' 'Creating systemd unit expand-fs.service'
cat << EOF > "${TMP_DIR}/etc/systemd/system/expand-fs.service"
[Unit]
Description=Extend root partition and resize ext4 file system
After=local-fs.target
Wants=local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c "/expand-fs.sh || exit 0"
ExecStop=/bin/bash -c "/usr/bin/rm /etc/systemd/system/basic.target.wants/expand-fs.service && /usr/bin/rm /expand-fs.sh && /usr/bin/rm /etc/systemd/system/expand-fs.service && /usr/bin/reboot -f || exit 0"
[Install]
WantedBy=basic.target
EOF
debug 'DEBUG' "Enabling systemd service by creating symlink: ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service"
ln -s /etc/systemd/system/expand-fs.service ${TMP_DIR}/etc/systemd/system/basic.target.wants/expand-fs.service
# Creating script for autoexpansion
debug 'DEBUG' 'Creating expansion script ${TMP_DIR}/expand-fs.sh'
cat << EOF2 > "${TMP_DIR}/expand-fs.sh"
#!/usr/bin/bash
LOCAL_DEV_MAJ=\$(lsblk -lpo mountpoint,maj:min,type,path | grep '/ ' | awk '{print \$2}' | cut -d : -f 1)
LOCAL_ROOT_PARTN=\$(lsblk -lpo mountpoint,maj:min,type,path | grep '/ ' | awk '{print \$2}' | cut -d : -f 2)
LOCAL_DEV_PATH=\$(lsblk -lpo maj:min,type,path | grep "\$LOCAL_DEV_MAJ" | grep 'disk' | awk '{print \$3}')
LOCAL_DEV_ROOT_PATH=\$(lsblk -lpo mountpoint,path | grep '/ ' | awk '{print \$2}')
LOCAL_ROOT_START=\$(fdisk --bytes -lo start "\$LOCAL_DEV_PATH" | tail -1)
sfdisk --delete "\$LOCAL_DEV_PATH" "\$LOCAL_ROOT_PARTN"
parted -s -a none "\$LOCAL_DEV_PATH" unit B mkpart primary ext4 "\$LOCAL_ROOT_START" 100%
resize2fs -f "\$LOCAL_DEV_ROOT_PATH"
sync
exit 0
EOF2
debug 'DEBUG' 'Making /expand-fs.sh executable'
chmod +x ${TMP_DIR}/expand-fs.sh
echo '## ArchLinuxArm filesystem autoresizing at boot...'
debug 'INFO' 'ArchLinuxArm filesystem autoresizing at boot'
sleep 1
return 0
}
# Function to print result
function print_result() {
declare -i AFTER_SIZE=$(ls -l "$IMG_FILE" | cut -d ' ' -f 5)
AFTER_SIZE=$(( AFTER_SIZE / 1024 / 1024 ))
if [ "$UPDATE" != true ]; then
echo '## Backup done.'
echo '##############################################################################'
echo "## Write to logfile: $DEBUG"
echo "## Autoexpand filesystem at boot: $AUTOEXPAND"
echo "## Use exclude.txt: $EXCLUDE_FILE"
if [ "$RESIZE2FS_RUN" = true ]; then
echo "## $IMG_FILE is ${AFTER_SIZE}MB with a root partition of $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB."
debug 'INFO' "$IMG_FILE is ${AFTER_SIZE}MB with a root partition of $(( LOCAL_RESIZE2FS_MIN / 1024 / 1024 ))MB"
else
echo "## $IMG_FILE is ${AFTER_SIZE}MB with $(( ADDED_SPACE / 1024 / 1024 ))MB extra space included."
debug 'INFO' "$IMG_FILE is ${AFTER_SIZE}MB with $(( ADDED_SPACE / 1024 / 1024 ))MB extra space included"
fi
if [ $AUTOEXPAND == true ]; then
echo "## Please wait for the system to reboot after restoring an image with autoexpansion."
fi
else
echo '## Backup done.'
echo '##############################################################################'
echo "## Write to logfile: $DEBUG"
echo "## Autoexpand filesystem at boot: $AUTOEXPAND"
echo "## Use exclude.txt: $EXCLUDE_FILE"
echo "## $IMG_FILE is ${AFTER_SIZE}MB"
debug 'INFO' "$IMG_FILE is ${AFTER_SIZE}MB"
debug 'INFO' 'Img file updated'
fi
if [ $AUTOEXPAND == true ]; then
echo "## Please wait for the system to reboot after restoring an image with autoexpansion."
fi
echo '##############################################################################'
debug 'INFO' 'Img file created and backup done'
return 0
}
# Process the non-option arguments, checks/fixes if the imagefile comes before the options
while [ "$#" -gt 0 ];
do
if [[ "$1" =~ ^- ]]; then
shift
else
IMG_FILE="$1"
ADDED_SPACE="$2"
break
fi
done
# Setting ADDED_SPACE to 0 if RESIZE2FS_RUN option is enabled
if [ "$RESIZE2FS_RUN" == true ]; then
ADDED_SPACE=0
debug 'INFO' '-a selected by user'
debug 'DEBUG' "ADDED_SPACE=$ADDED_SPACE"
fi
# Setting adding ADDED_SPACE to 0 if update is requested and variable ADDED_SPACE is a zero value
if [ $UPDATE == true ] && [ -z "$ADDED_SPACE" ]; then
ADDED_SPACE=0
debug 'INFO' '-U selected, -a not selected or extra space not provided by user, setting to ADDED_SPACE to 0 (non-zero value)'
debug 'DEBUG' "ADDED_SPACE=$ADDED_SPACE"
fi
# Regular expression for whole numbers
RE='^[0-9]+$'
# Validate the added space argument as a whole number
# -z = zero value, -n = non-zero value, || = or, && = and
if ! [[ "$ADDED_SPACE" =~ $RE ]]; then
debug 'WARNING' 'User defined ADDED_SPACE is not a regualar expression (whole number)'
COUNTER=0
while ! [[ "$ADDED_SPACE" =~ $RE ]]
do
#if [ "$COUNTER" -gt 0 ] || [ -n "$ADDED_SPACE" ]; then
if [ "$COUNTER" -gt 0 ]; then
echo 'ERROR!'
debug 'INFO' 'ERROR'
fi
echo 'Added space must be a whole number'
echo 'How much space in MB should be added?'
debug 'INFO' 'Added space must be a whole number'
debug 'INFO' 'How much space in MB should be added?'
read ADDED_SPACE
(( COUNTER++ ))
#(( COUNTER += 1 ))
debug 'USER_INPUT' "User requested ${ADDED_SPACE}MB as ADDED_SPACE"
done
#debug 'USER_INPUT' "User requested ${ADDED_SPACE}MB as ADDED_SPACE"
fi
# Check if the image file has the correct extension
if [[ "$IMG_FILE" != *.img ]]; then
echo 'ERROR! File must have .img extension'
debug 'ERROR' 'File must have .img extension, exit 2'
exit 2
fi
# Check if debugging is requested
if [ "$DEBUG" == true ]; then
echo '## Scanning filesystem and calculating...'
debug 'INFO' "Debugging requested, writing to log file $(basename "$LOG_FILE")..."
debug 'INFO' "Update existing img file, UPDATE=$UPDATE"
debug 'INFO' "Requesting size from resize2fs, RESIZE2FS_RUN=$RESIZE2FS_RUN"
debug 'INFO' "Prompt for user confirmation, PROMPTS=$PROMPTS"
debug 'INFO' "Auto expansion, AUTOEXPAND=$AUTOEXPAND"
fi
# Check if usage of exclude.txt is requested
if [ "$EXCLUDE_FILE" == true ]; then
debug 'INFO' '-f selected by user, using exclude.txt'
debug 'DEBUG' 'Checking if EXCLUDE_FILE exists'
if ! [ -f $(dirname "$0")/exclude.txt ]; then
echo 'ERROR! exclude.txt is not present in script directory!'
debug 'ERROR' 'exclude.txt does not exist in script directory, exit 2'
exit 2
fi
debug 'INFO' "$(dirname $0)/exclude.txt exists"
fi
if [ "$UPDATE" != true ]; then
debug 'DEBUG' 'Executing make_img function'
make_img
else
debug 'DEBUG' 'User selected -U, executing do_backup function'
do_backup
fi
# Check if autoexpansion is requested and run required function
if [ "$AUTOEXPAND" == true ]; then
echo '## Enabling fs-autoexpand...'
debug 'DEBUG' 'Executing expand_fs function'
sleep 1
if cat /etc/os-release | grep -i 'manjaro' >/dev/null; then
echo '## Manjaro os detected...'
debug 'INFO' 'Manjaro os detected'
sleep 1
autoexpansion_manjaro
elif [ -e /etc/armbian-release ]; then
debug 'INFO' 'Armbian os detected'
echo '## Armbian os detected...'
sleep 1
autoexpansion_armbian
elif uname -a | grep -q 'raspberry' || [ -e /etc/apt/sources.list.d/raspi.list ]; then
debug 'INFO' 'Raspberry pi detected'
echo '## Raspberry pi detected...'
sleep 1
autoexpansion_rpi
elif grep -q 'archlinuxarm' /etc/os-release; then
echo '## ArchLinuxArm os detected...'
debug 'INFO' 'ArchLinuxArm os detected'
sleep 1
autoexpansion_arch
else
echo '## No autoexpand option available for this os...'
debug 'WARNING' 'No autoexpand option available for this os'
AUTOEXPAND='failed'
sleep 1
fi
fi
print_result
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment