#!/bin/bash
-shopt -s extdebug
-bash-trace() {
- # shows function args when: shopt -s extdebug
- local -i argc_index=0 arg frame i start=${1:-1} max_indent=8 indent
- local source
- local extdebug=false
- if [[ $(shopt -p extdebug) == *-s* ]]; then
- extdebug=true
- fi
-
- for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do
- argc=${BASH_ARGC[frame]}
- argc_index+=$argc
- ((frame < start)) && continue
- if (( ${#BASH_SOURCE[@]} > 1 )); then
- source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:"
- fi
- indent=$((frame-start+1))
- indent=$((indent < max_indent ? indent : max_indent))
- printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}"
- if $extdebug; then
- for ((i=argc_index-1; i >= argc_index-argc; i--)); do
- printf " %s" "${BASH_ARGV[i]}"
- done
- fi
- echo \'
- done
-}
-
-
-errcatch() {
- set -E; shopt -s extdebug
- _err-trap() {
- err=$?
- exec >&2
- echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err"
- bash-trace 2
- echo "$0: exiting with code $err"
- exit $err
- }
- trap _err-trap ERR
- set -o pipefail
-}
+[[ $EUID == 0 ]] || exec sudo "$BASH_SOURCE" "$@"
-errcatch
-
-[[ $EUID == 0 ]] || sudo "$BASH_SOURCE" "$@"
+x="$(readlink -f "$BASH_SOURCE")"; source "${x%/*}/bash-trace"
usage() {
cat <<EOF
-Usage: ${0##*/} [OPTIONS] +/-SIZE[g] swap|boot
-
-Assuming Ian Kelling's partition scheme,
-Resize swap or boot, expanding or shrinking the root fs and partition to compensate.
-
--n Dry run
--r Reboot now if it's needed.
+Usage: ${0##*/} [OPTIONS] -- +/-SIZE[g] swap|boot
+
+Assuming Ian Kelling's partition scheme and we are currently into one of
+it's encrypted oses (we it's btrfs filesystem to be mounted), resize
+swap or boot, expanding or shrinking the root fs and partition to
+compensate. If it changes the partition tables incorrectly, the
+originals are stored in /root/backup_partition_table_<device_names>.
+
+TODO: only tested on stretch. deactivation of swap on reboot
+probably needs to be fixed on other oses. Even on stretch,
+we get 1.5 minutes of waiting for the crypt_dev and normal
+boot .device units.
+
+Warning!!! Backup your data. This script could have bugs in it.
+
+-n Dry run. Note, this likely won't be the exact commands,
+ for example, if you are running outside a vm, there will
+ probably be a reboot required in the middle so the kernel
+ can know about partition changes.
+-r Reboot right away if it's needed.
+-f Force running on a distro that has not been tested.
-h|--help Print help and exit.
SIZE is MiB, or if g is specified, GiB.
reboot_not=false
dry_run=false
+force=false
-temp=$(getopt -l opt o "$@") || usage 1
+temp=$(getopt -l help rnfh "$@") || usage 1
eval set -- "$temp"
while true; do
case $1 in
-r) reboot_now=true; shift ;;
-n) dry_run=true; shift ;;
+ -f) force=true; shift ;;
-h|--help) usage ;;
--) shift; break ;;
*) echo "$0: Internal error!" ; exit 1 ;;
case $2 in swap|boot) : ;; *) echo "$0: error: bad 2nd arg"; usage 1 ;; esac
+if ! $force && ! grep -q 'VERSION=.*stretch' /etc/os-release; then
+ echo "$0: error: This distro is untested. Only tested version atm is Stretch."
+ exit 1
+fi
+
#### end arg error checking ####
[[ $2 == boot ]] || boot=false
op_size=$1 # operator plus size
-[[ $op_size != *g ]] || op_size=$(( ${op_size%g} / 1024 ))
-size=${op_size#[+-]}
+if [[ $op_size == *g ]]; then
+ op_size=${op_size%g}
+ size=${op_size#?}
+ op=${op_size%$size}
+ size=$(( $size * 1024 ))
+ op_size=$op$size
+else
+ size=${op_size#?}
+fi
if [[ $op_size == +* ]]; then
op_size_rev=-$size # rev = reverse
grow=false
fi
-
##### end command line parsing ########
rootn=1
needs_reboot=false
reboot_script_initialized=false
-pmk() {
+pmk() { # partition make
part=$1
start_op=$2
end_op=$3
- p mkpart primary "$4" \
- $((${ptable[start$part]} $start_op)) $((${ptable[end$part]} $end_op))
+ fs_type="$4"
+
+ # This fails outside a vm, but actually succeeds. also prints this
+ # message:
+ # Error: Partition(s) 2 on /dev/sda have been written, but
+ # we have been unable to inform the kernel of the change, probably
+ # because it/they are in use. As a result, the old partition(s)
+ # will remain in use. You should reboot now before making further
+ # changes.
+
+ if ! p mkpart primary "$fs_type" \
+ $((${ptable[start$part]} $start_op)) $((${ptable[end$part]} $end_op)); then
+ echo "$0: warning: ignoring failure return of mkpart"
+ fi
}
def-e() {
}
def-e
-e swapoff -a
+
while read devid dev; do
+ case $dev in
+ /dev/dm-[0-9])
+ # older oses, it points to /dev/dm-x
+ dev=$(dmsetup info $dev | sed -rn 's/^\s*Name:\s*(\S*)/\1/p')
+ ;;
+ /dev/mapper/*)
+ dev=${dev#/dev/mapper/}
+ ;;
+ *)
+ echo "$0: error: could not find devicemapper root dev,
+make sure you are running from a encrypted root this script is resizing"
+ exit 1
+ ;;
+ esac
+ if [[ $dev != crypt_dev_*-part$rootn ]]; then
+ echo "$0: error: unexpected root device name,
+make sure you are running from a encrypted root this script is resizing"
+ exit 1
+ fi
+ dev=${dev#crypt_dev_}
+ dev=${dev%-part$rootn}
+ devpath=/dev/disk/by-id/$dev
echo skip=$size
def-e
declare -A ptable
ptable[start$id]=start=${start%%[^0-9]*}
ptable[end$id]=${end%%[^0-9]*}
ptable[size$id]=${psize%%[^0-9]*}
- done < <(parted -m /dev/$dev unit MiB print)
- parted /dev/$dev unit MiB print | tee /root/backup_partition_table_$dev
- p() { e parted -a optimal -s -- /dev/$dev unit MiB "$@"; }
- e systemctl stop systemd-cryptsetup@crypt_swap_$dev$swapn
+ done < <(parted -m $devpath unit MiB print)
+ parted $devpath unit MiB print | tee /root/backup_partition_table_$dev
+ p() { e parted -a optimal -s -- $devpath unit MiB "$@"; }
+ unit=systemd-cryptsetup@crypt_dev_$dev-part$swapn
+ # note systemctl show can test if a unit exists.
+ if ! e systemctl stop $unit; then
+ e swapoff -a
+ fi
+ # there is a bug in jessie. this and the .swap unit are
+ # generated from /etc/fstab, and it escapes - to x2d, then doesn't escape it
+ # when looking for the file to use as swap. so, no swap is working on jessie
+ # right now.
sleep 1 # dunno if this is needed,
# but systemd likes to do these kind of things in the background.
grep "but we have been unable to inform the kernel" &>/dev/null; then
needs_reboot=true
fi
- if ! $grow; then
- if $needs_reboot; then
- e systemctl mask dev-mapper-crypt_swap_$dev$swapn.swap
- e systemctl mask systemd-cryptsetup@crypt_swap_$dev$swapn.service
- e() { echo "$@" >> /root/finish-resize; }
- if ! $reboot_script_initialized; then
- reboot_script_initialized=true
- rm -rf /root/finish-resize
- cat >/root/finish-resize <<'EOF'
+ if $needs_reboot; then
+ # note: even if these units don't exist, this will succeed.
+ e systemctl mask dev-mapper-crypt_swap_$dev$swapn.swap
+ e systemctl mask systemd-cryptsetup@crypt_swap_$dev$swapn.service
+ e() { echo "$@" >> /root/finish-resize; }
+ if ! $reboot_script_initialized; then
+ reboot_script_initialized=true
+ rm -rf /root/finish-resize
+ cat >/root/finish-resize <<'EOF'
#!/bin/bash -x
set -eE -o pipefail
trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
EOF
- chmod +x /root/finish-resize
- fi
- e systemctl unmask systemd-cryptsetup@crypt_swap_$dev$swapn.service
- e systemctl unmask dev-mapper-crypt_swap_$dev$swapn.swap
+ chmod +x /root/finish-resize
fi
+ e swapoff -a
+ e systemctl unmask systemd-cryptsetup@crypt_swap_$dev$swapn.service
+ e systemctl unmask dev-mapper-crypt_swap_$dev$swapn.swap
+ fi
+ if ! $grow; then
$root_resize_cmd
fi
if $boot; then
+ # non by-id path, to match what btrfs fi show will tell us
+ boot_dev_path=$(readlink -f $devpath-part$bootn)
boot_devid=$(btrfs fi show /boot | \
- sed -rn "s#^\s*devid\s+(\S+)\s.*$dev[0-9]#\1#p")
+ sed -rn "s#^\s*devid\s+(\S+)\s.*$boot_dev_path#\1#p")
if ! $grow; then
# shrink boot, move it to a temp file
e btrfs fi resize $boot_devid:${op_size}M /boot
e umount /boot
temp_boot=/root/temp_boot_$dev
- e dd bs=1M if=/dev/$dev$bootn of=$temp_boot \
+ e dd bs=1M if=$boot_dev_path of=$temp_boot \
count=$((${ptable[size$bootn]} $op_size))
else
e umount /boot
pmk $bootn $op_size_rev ""
if $grow; then
- e dd bs=1M if=/dev/$dev$bootn of=/dev/$dev$bootn skip=$size
+ e dd bs=1M if=$boot_dev_path of=$boot_dev_path skip=$size
e mount /boot
e btrfs fi resize $boot_devid:${op_size}M /boot
else
- e dd bs=1M if=$temp_boot of=/dev/$dev$bootn
+ e dd bs=1M if=$temp_boot of=$boot_dev_path
e mount /boot
fi
else
pmk $swapn $op_size_rev "" "linux-swap"
e systemctl start systemd-cryptsetup@crypt_swap_$dev$swapn
fi
-done < <(btrfs fi show / | \
- sed -nr 's#^\s*devid\s*(\S+)\s.*_([^_ ]+)[0-9]\s*$#\1 \2#p')
+done < <(btrfs fi show / | sed -nr 's#^\s*devid\s*(\S+).* path (.*)$#\1 \2#p')
if $boot; then
fi
if $needs_reboot; then
- if ! $grow; then
- echo "$0: Reboot, run /root/finish-resize. It's contents:"
- cat /root/finish-resize
- else
- echo "$0: If you want to resize again later, a reboot is required first."
- fi
+ echo "$0: Reboot, run /root/finish-resize. It's contents:"
+ cat /root/finish-resize
if $reboot_now; then
echo "$0: rebooting now"
reboot now
exit
fi
fi
-