add resize support, tested with 1 disk so far
authorIan Kelling <ian@iankelling.org>
Mon, 15 Feb 2016 23:14:28 +0000 (15:14 -0800)
committerIan Kelling <ian@iankelling.org>
Mon, 6 Feb 2017 06:21:41 +0000 (22:21 -0800)
fai/config/hooks/partition.DEFAULT
fresize [new file with mode: 0755]

index 5d4684c6bf79af7c3bbe6a00dc196885b7505380..ee19894fb1e46b9f03411795767527252a1218af 100755 (executable)
@@ -10,11 +10,11 @@ skiptask partition ||: # for running out of fai
 
 #### begin configuration
 
-bootn=1
+bootn=3
+rootn=1
 swapn=2
-rootn=3
 bios_grubn=4
-boot_end=804
+boot_mib=750
 lastn=$bios_grubn
 
 if ifclass VM; then
@@ -80,14 +80,17 @@ fi
 
 crypt=/dev/mapper/crypt_dev_${d##/dev/}a$rootn
 
-
-if ifclass frodo; then
-    # next upgrade is prolly 16 gigs of memory, across ~8 devices
-    swap_end=$((3500 + boot_end))
-else
-    # 1.5 x based on https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Installation_Guide/sect-disk-partitioning-setup-x86.html#sect-custom-partitioning-x86
-    swap_end=$(( $(grep ^MemTotal: /proc/meminfo| awk '{print $2}') * 3/(${#devs[@]} * 2 ) / 1000 + boot_end ))
-fi
+bios_grub_end=4
+# 1.5 x based on https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Installation_Guide/sect-disk-partitioning-setup-x86.html#sect-custom-partitioning-x86
+swap_mib=$(( $(grep ^MemTotal: /proc/meminfo | \
+                      awk '{print $2}') * 3/(${#devs[@]} * 2 ) / 1024 ))
+# parted will round up the disk size. Do -1 so we can have
+# fully 1MiB unit partitions for easy resizing of the last partition.
+# Otherwise we would pass in -0 for the end argument for the last partition.
+disk_mib=$(( $(parted -m ${devs[0]} unit MiB print | \
+                  sed -nr "s#^${devs[0]}:([0-9]+).*#\1#p") - 1))
+root_end=$(( disk_mib - swap_mib - boot_mib ))
+swap_end=$(( root_end + swap_mib))
 
 mkdir -p /tmp/fai
 shopt -s nullglob
@@ -97,19 +100,20 @@ if $partition; then
     done
     for dev in ${devs[@]}; do
         parted -s $dev mklabel gpt
-        # gpt ubuntu cloud image uses ~4. fai uses 1 MiB. ehh, i'll do 4.
-        # also, using MB instead of MiB causes complains about alignment.
+        # gpt ubuntu cloud image uses ~4. fai uses 1 MiB.
+        # I read something in the parted manual saying cheap flash media
+        # likes to start at 4.
+        # MiB because parted complains about alignment otherwise.
         pcmd="parted -a optimal -s -- $dev"
-        $pcmd mkpart primary "ext3" 4MB ${boot_end}MiB
-        $pcmd mkpart primary "linux-swap" ${boot_end}MiB ${swap_end}MiB
-        $pcmd mkpart primary "" ${swap_end}MiB -0
+        $pcmd mkpart primary "ext3" 4MiB ${root_end}MiB
+        $pcmd mkpart primary "linux-swap" ${root_end}MiB ${swap_end}MiB
+        $pcmd mkpart primary "" ${swap_end}MiB ${disk_mib}MiB
         $pcmd mkpart primary "" 1MiB 4MiB
         $pcmd set $bios_grubn bios_grub on
         $pcmd set $bootn boot on # generally not needed on modern systems
         # the mkfs failed randomly on a vm, so I threw a sleep in here.
         sleep .1
 
-
         luks_dev=$dev$rootn
         yes YES | cryptsetup luksFormat $luks_dev $luks_dir/host-$HOSTNAME \
                              -c aes-cbc-essiv:sha256 -s 256 || [[ $? == 141 ]]
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