Skip to content

Instantly share code, notes, and snippets.

@djkazic
Last active February 6, 2023 16:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djkazic/2f3d519639fbb78cf973fedc106f2663 to your computer and use it in GitHub Desktop.
Save djkazic/2f3d519639fbb78cf973fedc106f2663 to your computer and use it in GitHub Desktop.
zdefrag
#!/bin/bash
# Allows you to use a [tank] and [reserve] pool
# Set vars for best results
if [ "$#" != 4 ]; then
echo " Usage: ./defrag <src-pool> <src-dataset> <dest-pool> <grub-disk>"
echo ''
echo " Example: ./defrag tank os reserve /dev/sda"
exit 1
fi
PRIMARY="$1"
DATASET="$2"
SECONDARY="$3"
GRUB_DISK="$4"
echo "Performing switch from [$PRIMARY/$DATASET] -> [$SECONDARY/$DATASET]"
echo ''
echo "Pre-check (1/3) verifying pools existence"
if [ `zpool list | grep "^$PRIMARY " | wc -l` = 1 ] && [ `zpool list | grep "^$SECONDARY" | wc -l` = 1 ]; then
echo "[OK] Pools [$PRIMARY] and [$SECONDARY] exist!"
else
echo "Pools [$PRIMARY] and [$SECONDARY] do not exist on this machine ... aborting"
exit 1
fi
echo ''
echo "Pre-check (2/3) verifying pool sizes"
convert_to_abs_size() {
local SIZE="$1"
local TIER="$2"
if [[ "${TIER}" = "G" ]]; then
SIZE=`bc <<< "1000 * $SIZE"`
elif [[ "${TIER}" = "T" ]]; then
SIZE=`bc <<< "1000 * 1000 * $SIZE"`
fi
echo "$SIZE"
}
round_to_int() {
local NUM="$1"
echo "($NUM + 0.5) / 1" | bc
}
# Grabs the allocation used by current pool
PRIMARYSIZEFULL=`zpool list | grep "^$PRIMARY" | sed -e's/ */ /g' | cut -d' ' -f3`
PRIMARYSIZE=`echo "${PRIMARYSIZEFULL%?}"`
PRIMARYTIER=`echo "${PRIMARYSIZEFULL: -1}"`
PRIMARYSIZE=`convert_to_abs_size ${PRIMARYSIZE} ${PRIMARYTIER}`
# Grabs the available space for the destination pool
SECONDARYSIZEFULL=`zpool list | grep "^$SECONDARY" | sed -e's/ */ /g' | cut -d' ' -f4`
SECONDARYSIZE=`echo "${SECONDARYSIZEFULL%?}"`
SECONDARYTIER=`echo "${SECONDARYSIZEFULL: -1}"`
SECONDARYSIZE=`convert_to_abs_size ${SECONDARYSIZE} ${SECONDARYTIER}`
# Round both numbers to ensure integer calc
PRIMARYSIZE=`round_to_int "${PRIMARYSIZE}"`
SECONDARYSIZE=`round_to_int "${SECONDARYSIZE}"`
# Compares the sizes
if [ "$PRIMARYSIZE" -lt "$SECONDARYSIZE" ]; then
echo "[OK] Target free size is greater than source alloc size"
else
echo "Not enough free space on target pool! Aborting ..."
exit 1
fi
echo ''
echo "Pre-check (3/3) Checking current mountpoint"
if [[ `zfs mount | grep "^$SECONDARY/$DATASET */" | wc -l` = 1 ]]; then
echo "Target dataset [$SECONDARY/$DATASET] is already mounted to / ... aborting"
exit 1
else
echo "[OK] Target dataset is unmounted"
fi
echo ''
echo "(1/10) Clearing old [$PRIMARY@defrag] snapshot"
zfs destroy -r $PRIMARY@defrag
echo ''
echo "(2/10) Creating new [$PRIMARY@defrag] snapshot!"
zfs snap -r $PRIMARY@defrag
echo ''
echo "(3/10) Removing target pool [$SECONDARY] datasets"
zfs destroy -r $SECONDARY
echo ''
echo "(4/10) Replicating defrag snapshot [$PRIMARY] -> [$SECONDARY]"
zfs send -Rv $PRIMARY@defrag | zfs recv -vF $SECONDARY
echo ''
echo "(5/10) Preparing mountspace for /chroot"
umount -R /chroot >/dev/null
mkdir -p /chroot
rm -rf /chroot/*
echo ''
echo "(6/10) Changing target mountpoint to /chroot"
zfs set mountpoint=none $SECONDARY
zfs set mountpoint=/chroot $SECONDARY/$DATASET
echo ''
echo "(7/10) Mounting [$SECONDARY/$DATASET]"
zfs mount $SECONDARY/$DATASET
cd /chroot
echo ''
echo "(8/10) Binding mounts for chroot"
mount --bind /dev dev
mount --bind /proc proc
mount --bind /run run
mount --bind /sys sys
# EFI support
# mount --bind /boot/efi boot/efi
echo 'Done!'
echo ''
# We need to set mountpoints from within the chroot
echo "(9/10) Diving into chroot"
cat << EOF | chroot . /bin/bash
zfs list
update-grub
grub-install $GRUB_DISK
update-initramfs -u -t -k all
zfs set mountpoint=/ $SECONDARY/$DATASET
zfs set mountpoint=none $PRIMARY
zfs set mountpoint=none $PRIMARY/$DATASET
zfs list
EOF
echo ''
umount -R /chroot >/dev/null
echo "(10/10) Reboot prompt"
echo ''
read -p "Would you like to switch to your original pool, [$PRIMARY]? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "In order to do that, you'll have to destroy zpool [$PRIMARY] and re-create it on the next boot"
echo "Once you do that, re-run this script like this:"
echo " ./defrag $SECONDARY $DATASET $PRIMARY /dev/sda"
fi
echo ''
read -p "A reboot is required for the boot pool changes to take effect; would you like to reboot now? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
reboot
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment