various changes for libreboot having no pxe
[automated-distro-installer] / fresize
1 #!/bin/bash
2
3 shopt -s extdebug
4 bash-trace() {
5 # shows function args when: shopt -s extdebug
6 local -i argc_index=0 arg frame i start=${1:-1} max_indent=8 indent
7 local source
8 local extdebug=false
9 if [[ $(shopt -p extdebug) == *-s* ]]; then
10 extdebug=true
11 fi
12
13 for ((frame=0; frame < ${#FUNCNAME[@]}-1; frame++)); do
14 argc=${BASH_ARGC[frame]}
15 argc_index+=$argc
16 ((frame < start)) && continue
17 if (( ${#BASH_SOURCE[@]} > 1 )); then
18 source="${BASH_SOURCE[frame+1]}:${BASH_LINENO[frame]}:"
19 fi
20 indent=$((frame-start+1))
21 indent=$((indent < max_indent ? indent : max_indent))
22 printf "%${indent}s↳%sin \`%s" '' "$source" "${FUNCNAME[frame]}"
23 if $extdebug; then
24 for ((i=argc_index-1; i >= argc_index-argc; i--)); do
25 printf " %s" "${BASH_ARGV[i]}"
26 done
27 fi
28 echo \'
29 done
30 }
31
32
33 errcatch() {
34 set -E; shopt -s extdebug
35 _err-trap() {
36 err=$?
37 exec >&2
38 echo "${BASH_SOURCE[1]}:${BASH_LINENO[0]}:in \`$BASH_COMMAND' returned $err"
39 bash-trace 2
40 echo "$0: exiting with code $err"
41 exit $err
42 }
43 trap _err-trap ERR
44 set -o pipefail
45 }
46
47 errcatch
48
49 [[ $EUID == 0 ]] || exec sudo "$BASH_SOURCE" "$@"
50
51 usage() {
52 cat <<EOF
53 Usage: ${0##*/} [OPTIONS] +/-SIZE[g] swap|boot
54
55 Assuming Ian Kelling's partition scheme, Resize swap or boot, expanding
56 or shrinking the root fs and partition to compensate. If it changes
57 the partition tables incorrectly, the originals are stored in
58 /root/backup_partition_table_<device_names>.
59
60 -n Dry run
61 -r Reboot now if it's needed.
62 -h|--help Print help and exit.
63
64 SIZE is MiB, or if g is specified, GiB.
65
66 If using multiple devices, SIZE is applied to each device, so total change is
67 SIZE * devices.
68
69 Note: Uses GNU getopt options parsing style
70 EOF
71 exit $1
72 }
73
74 reboot_not=false
75 dry_run=false
76
77 temp=$(getopt -l help rnh "$@") || usage 1
78 eval set -- "$temp"
79 while true; do
80 case $1 in
81 -r) reboot_now=true; shift ;;
82 -n) dry_run=true; shift ;;
83 -h|--help) usage ;;
84 --) shift; break ;;
85 *) echo "$0: Internal error!" ; exit 1 ;;
86 esac
87 done
88
89 #### begin arg error checking ####
90
91 if [[ $# != 2 ]]; then
92 echo "$0: error: expected 2 arguments"
93 usage 1
94 fi
95
96 if [[ $1 != [+-][0-9]* ]]; then
97 echo "$0: error: bad 1st arg: $1"
98 usage 1
99 fi
100
101 if ! which parted &>/dev/null; then
102 echo "$0: error: install parted"
103 exit 1
104 fi
105
106 case $2 in swap|boot) : ;; *) echo "$0: error: bad 2nd arg"; usage 1 ;; esac
107
108 #### end arg error checking ####
109
110
111 boot=true
112 [[ $2 == boot ]] || boot=false
113
114 op_size=$1 # operator plus size
115 if [[ $op_size == *g ]]; then
116 op_size=${op_size%g}
117 size=${op_size#?}
118 op=${op_size%size}
119 size=$(( ${size} * 1024 ))
120 op_size=$op$size
121 else
122 size=${op_size#?}
123 fi
124
125 if [[ $op_size == +* ]]; then
126 op_size_rev=-$size # rev = reverse
127 grow=true
128 else
129 op_size_rev=+$size
130 grow=false
131 fi
132
133 ##### end command line parsing ########
134
135 rootn=1
136 swapn=2
137 bootn=3
138 needs_reboot=false
139 reboot_script_initialized=false
140
141 pmk() { # partition make
142 part=$1
143 start_op=$2
144 end_op=$3
145 fs_type="$4"
146
147 # This fails outside a vm, but actually succeeds. also prints this
148 # message:
149 # Error: Partition(s) 2 on /dev/sda have been written, but
150 # we have been unable to inform the kernel of the change, probably
151 # because it/they are in use. As a result, the old partition(s)
152 # will remain in use. You should reboot now before making further
153 # changes.
154
155 p mkpart primary "$fs_type" \
156 $((${ptable[start$part]} $start_op)) $((${ptable[end$part]} $end_op)) ||:
157 }
158
159 def-e() {
160 if $dry_run; then
161 e() { echo "+ $@"; }
162 else
163 e() { echo "+ $@"; "$@"; }
164 fi
165 }
166
167 def-e
168 e swapoff -a
169
170 while read devid dev; do
171 if [[ $dev != /dev/mapper/* ]]; then
172 # older oses, it points to /dev/dm-x
173 dev=$(dmsetup info $dev | sed -rn 's/^\s*Name:\s*(\S*)/\1/p')
174 else
175 dev=${dev#/dev/mapper/}
176 fi
177 dev=${dev#crypt_dev_}
178 r='-part[0-9]*$'
179 [[ $dev =~ $r ]] ||:
180 dev=${dev%$BASH_REMATCH}
181 devpath=/dev/disk/by-id/$dev
182 echo skip=$size
183 def-e
184 declare -A ptable
185 while IFS=: read id start end psize _; do
186 [[ $id == [0-9] ]] || continue
187 ptable[start$id]=start=${start%%[^0-9]*}
188 ptable[end$id]=${end%%[^0-9]*}
189 ptable[size$id]=${psize%%[^0-9]*}
190 done < <(parted -m $devpath unit MiB print)
191 parted $devpath unit MiB print | tee /root/backup_partition_table_$dev
192 p() { e parted -a optimal -s -- $devpath unit MiB "$@"; }
193 unit=systemd-cryptsetup@crypt_dev_$dev-part$swapn
194 # note systemctl show can test if a unit exists.
195 e systemctl stop $unit || [[ $? == 5 ]] # fail due to not loaded
196 # there is a bug in jessie. this and the .swap unit are
197 # generated from /etc/fstab, and it escapes - to x2d, then doesn't escape it
198 # when looking for the file to use as swap. so, no swap is working on jessie
199 # right now.
200 sleep 1 # dunno if this is needed,
201 # but systemd likes to do these kind of things in the background.
202
203 # These partition comments seems a little verbose now, but I bet they
204 # will be helpfull if I read this in more than a week from now.
205 # <> = deleted partition, () = partition
206 p rm $swapn # ( root )< swap >( boot )
207
208 root_resize_cmd="e btrfs fi resize $devid:${op_size_rev}M /"
209 if $grow; then $root_resize_cmd; fi
210 # if $grow; then
211 # < root >< swap >( boot )
212 # ( root ) >< swap >( boot )
213 # else
214 # < root >< swap >( boot )
215 # ( root >< ) swap >( boot )
216
217 out=$(p rm $rootn 2>&1)
218 echo "$out"
219
220 pmk $rootn "" $op_size_rev
221
222 if echo "$out" | \
223 grep "but we have been unable to inform the kernel" &>/dev/null; then
224 needs_reboot=true
225 fi
226 if ! $grow; then
227 if $needs_reboot; then
228 e systemctl mask dev-mapper-crypt_swap_$dev$swapn.swap
229 e systemctl mask systemd-cryptsetup@crypt_swap_$dev$swapn.service
230 e() { echo "$@" >> /root/finish-resize; }
231 if ! $reboot_script_initialized; then
232 reboot_script_initialized=true
233 rm -rf /root/finish-resize
234 cat >/root/finish-resize <<'EOF'
235 #!/bin/bash -x
236 set -eE -o pipefail
237 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
238 EOF
239 chmod +x /root/finish-resize
240 fi
241 e systemctl unmask systemd-cryptsetup@crypt_swap_$dev$swapn.service
242 e systemctl unmask dev-mapper-crypt_swap_$dev$swapn.swap
243 fi
244 $root_resize_cmd
245 fi
246 if $boot; then
247 # non by-id path, to match what btrfs fi show will tell us
248 boot_dev_path=$(readlink -f $devpath-part$bootn)
249 boot_devid=$(btrfs fi show /boot | \
250 sed -rn "s#^\s*devid\s+(\S+)\s.*$boot_dev_path#\1#p")
251
252 if ! $grow; then
253 # shrink boot, move it to a temp file
254 e btrfs fi resize $boot_devid:${op_size}M /boot
255 e umount /boot
256 temp_boot=/root/temp_boot_$dev
257 e dd bs=1M if=$boot_dev_path of=$temp_boot \
258 count=$((${ptable[size$bootn]} $op_size))
259 else
260 e umount /boot
261 fi
262 # if $grow; then
263 # ( root ) >< swap >< boot >
264 # ( root )( >< swap ) >< boot >
265 # ( root )( >< swap )( >< boot )
266 # else
267 # ( root >< ) swap >< boot >
268 # ( root >< )( swap >< ) boot >
269 # ( root >< )( swap >< )( boot )
270 p rm $bootn
271 pmk $swapn $op_size_rev $op_size_rev "linux-swap"
272 pmk $bootn $op_size_rev ""
273
274 if $grow; then
275 e dd bs=1M if=$boot_dev_path of=$boot_dev_path skip=$size
276 e mount /boot
277 e btrfs fi resize $boot_devid:${op_size}M /boot
278 else
279 e dd bs=1M if=$temp_boot of=$boot_dev_path
280 e mount /boot
281 fi
282 else
283 # if $grow; then ( root )( >< swap )( boot )
284 # else ( root >< )( swap )( boot )
285 pmk $swapn $op_size_rev "" "linux-swap"
286 e systemctl start systemd-cryptsetup@crypt_swap_$dev$swapn
287 fi
288 done < <(btrfs fi show / | sed -nr 's#^\s*devid\s*(\S+).* path (.*)$#\1 \2#p')
289
290
291 if $boot; then
292 e rm -rf "/root/temp_boot_*"
293 e rm -f /root/finish-resize
294 fi
295
296 if $needs_reboot; then
297 if ! $grow; then
298 echo "$0: Reboot, run /root/finish-resize. It's contents:"
299 cat /root/finish-resize
300 else
301 echo "$0: If you want to resize again later, a reboot is required first."
302 fi
303 if $reboot_now; then
304 echo "$0: rebooting now"
305 reboot now
306 exit
307 fi
308 fi