add resize support, tested with 1 disk so far
[automated-distro-installer] / fresize
diff --git a/fresize b/fresize
new file mode 100755 (executable)
index 0000000..90f1998
--- /dev/null
+++ b/fresize
@@ -0,0 +1,240 @@
+#!/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
+}
+
+errcatch
+
+[[ $EUID == 0 ]] || sudo "$BASH_SOURCE" "$@"
+
+usage() {
+    cat <<EOF
+Usage: ${0##*/} [-n] +/-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
+
+SIZE is MiB, or if g is specified, GiB.
+
+If using multiple devices, SIZE is applied to each device, so total change is
+SIZE * devices.
+
+
+EOF
+    exit $1
+}
+
+dry_run=false
+case $1 in
+    -n) dry_run=true; shift ;;
+    -h|--help) usage ;;
+esac
+
+#### begin arg error checking ####
+
+if [[ $# != 2 ]]; then
+    echo "$0: error: expected 2 arguments"
+    usage 1
+fi
+
+if [[ $1 != [+-][0-9]* ]]; then
+    echo "$0: error: bad 1st arg: $1"
+    usage 1
+fi
+
+if ! which parted &>/dev/null; then
+    echo "$0: error: install parted"
+    exit 1
+fi
+
+case $2 in swap|boot) : ;; *) echo "$0: error: bad 2nd arg"; usage 1 ;; esac
+
+#### end arg error checking ####
+
+
+boot=true
+[[ $2 == boot ]] || boot=false
+
+#size=${x#[+-]}
+op_size=$1 # operator plus size
+[[ $op_size != *g ]] || op_size=$(( ${op_size%g} / 1024 ))
+size=${op_size#[+-]}
+
+if [[ $op_size == +* ]]; then
+    op_size_rev=-$size # rev = reverse
+    grow=true
+else
+    op_size_rev=+$size
+    grow=false
+fi
+
+##### end command line parsing ########
+
+rootn=1
+swapn=2
+bootn=3
+needs_reboot=false
+
+pmk() {
+    part=$1
+    start_op=$2
+    end_op=$3
+    read start end < <(echo ${ptable[$part]})
+    p mkpart primary "$4" $((start $start_op)) $((end $end_op))
+}
+
+
+swapoff -a
+
+while read devid dev; do
+    if $dry_run; then
+        e() { echo "+ $@"; }
+    else
+        e() { echo "+ $@"; "$@"; }
+    fi
+    ptable=()
+    while IFS=: read id start end _; do
+        [[ $id == [0-9] ]] || continue
+        end=${end%MiB}
+        start=${start%MiB}
+        start=${start%.*} # small enough number that parted uses a decimal
+        ptable[$id]="$start $end"
+    done < <(parted -m /dev/$dev unit MiB print)
+    parted /dev/$dev unit MiB print | tee /root/backup_partition
+    p() { e parted -a optimal -s -- /dev/$dev unit MiB "$@"; }
+    e systemctl stop systemd-cryptsetup@crypt_swap_$dev$swapn
+    sleep 1
+    # These partition comments seems a little verbose now, but I bet they
+    # will be helpfull if I read this in more than a week from now.
+    # <> = deleted partition, () = partition
+    p rm $swapn # ( root )< swap >( boot )
+
+    root_resize_cmd="e btrfs fi resize $devid:$op_size_rev /"
+    if $grow; then $root_resize_cmd; fi
+    # if $grow; then
+    # < root >< swap >( boot )
+    # ( root ) >< swap >( boot )
+    # else
+    # < root >< swap >( boot )
+    # ( root >< ) swap >( boot )
+
+    out=$(p rm $rootn 2>&1)
+    echo "$out"
+
+    pmk $rootn "" $op_size_rev
+    if ! $grow; then
+        if echo "$out" | \
+                grep "but we have been unable to inform the kernel" &>/dev/null; then
+            e systemctl mask dev-mapper-crypt_swap_$dev$swapn.swap
+            e systemctl mask systemd-cryptsetup@crypt_swap_$dev$swapn.service
+            if ! $needs_reboot; then
+                needs_reboot=true
+                echo "$0: reboot and run /root/finish-resize to finish.
+The following commands are what will be executed:"
+                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 $?"' ERR
+EOF
+                chmod +x /root/finish-resize
+            fi
+            e() { echo "$@" | tee -a /root/finish-resize; }
+            e systemctl unmask systemd-cryptsetup@crypt_swap_$dev$swapn.service
+            e systemctl unmask dev-mapper-crypt_swap_$dev$swapn.swap
+            # todo, disable swap for the next boot. currently have to wait
+            # 1:30 for it to fail.
+        fi
+        $root_resize_cmd
+    fi
+    if $boot; then
+        boot_devid=$(btrfs fi show /boot | \
+                            sed -rn "s#^\s*devid\s+(\S+)\s.*$dev[0-9]#\1#p")
+
+        if ! $grow; then
+            # shrink boot, move it to a temp file
+            e btrfs fi resize $boot_devid:$op_size /boot
+            temp_boot=/root/temp_boot_dd
+            e dd bs=1M if=/dev/$dev$bootn of=$temp_boot count=$size
+        fi
+        # if $grow; then
+        # ( root ) >< swap >< boot >
+        # ( root )( >< swap ) >< boot >
+        # ( root )( >< swap )( >< boot )
+        # else
+        # ( root >< ) swap >< boot >
+        # ( root >< )( swap >< ) boot >
+        # ( root >< )( swap >< )( boot )
+        e umount /boot
+        p rm $bootn
+        pmk $swapn $op_size_rev $op_size_rev "linux-swap"
+        pmk $bootn $op_size_rev ""
+
+        if $grow; then
+            e dd bs=1M if=/dev/$dev$bootn of=/dev/$dev$bootn skip=$size
+            e btrfs fi resize $boot_devid:$op_size /boot
+        else
+            e dd bs=1M if=$temp_boot of=/dev/$dev$bootn
+        fi
+        e mount /boot
+    else
+        # if $grow; then  ( root )( >< swap )( boot )
+        # else            ( root >< )( swap )( boot )
+        pmk $swapn $op_size_rev "" "linux-swap"
+    fi
+done < <(btrfs fi show / | \
+                sed -nr 's#^\s*devid\s*(\S+)\s.*_([^_ ]+)[0-9]\s*$#\1 \2#p')
+
+if $boot; then
+    e rm -rf $temp_boot
+    e rm /root/finish-resize
+fi
+
+if $needs_reboot; then
+    echo "$0: reminder, reboot then /root/finish-resize"
+fi
+
+#for dev in ${devs[@]}; do