Created
January 24, 2024 16:37
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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