add rescue, fix default pxe, ubuntu locale
[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 and we are currently into one of
56 it's encrypted oses (we it's btrfs filesystem to be mounted), resize
57 swap or boot, expanding or shrinking the root fs and partition to
58 compensate. If it changes the partition tables incorrectly, the
59 originals are stored in /root/backup_partition_table_<device_names>.
60
61 TODO: only tested on stretch. deactivation of swap on reboot
62 probably needs to be fixed on other oses. Even on stretch,
63 we get 1.5 minutes of waiting for the crypt_dev and normal
64 boot .device units.
65
66 Warning!!! Backup your data. This script could have bugs in it.
67
68 -n Dry run. Note, this likely won't be the exact commands,
69 for example, if you are running outside a vm, there will
70 probably be a reboot required in the middle so the kernel
71 can know about partition changes.
72 -r Reboot right away if it's needed.
73 -f Force running on a distro that has not been tested.
74 -h|--help Print help and exit.
75
76 SIZE is MiB, or if g is specified, GiB.
77
78 If using multiple devices, SIZE is applied to each device, so total change is
79 SIZE * devices.
80
81 Note: Uses GNU getopt options parsing style
82 EOF
83 exit $1
84 }
85
86 reboot_not=false
87 dry_run=false
88 force=false
89
90 temp=$(getopt -l help rnfh "$@") || usage 1
91 eval set -- "$temp"
92 while true; do
93 case $1 in
94 -r) reboot_now=true; shift ;;
95 -n) dry_run=true; shift ;;
96 -f) force=true; shift ;;
97 -h|--help) usage ;;
98 --) shift; break ;;
99 *) echo "$0: Internal error!" ; exit 1 ;;
100 esac
101 done
102
103 #### begin arg error checking ####
104
105 if [[ $# != 2 ]]; then
106 echo "$0: error: expected 2 arguments"
107 usage 1
108 fi
109
110 if [[ $1 != [+-][0-9]* ]]; then
111 echo "$0: error: bad 1st arg: $1"
112 usage 1
113 fi
114
115 if ! which parted &>/dev/null; then
116 echo "$0: error: install parted"
117 exit 1
118 fi
119
120 case $2 in swap|boot) : ;; *) echo "$0: error: bad 2nd arg"; usage 1 ;; esac
121
122 if ! $force && ! grep -q 'VERSION=.*stretch' /etc/os-release; then
123 echo "$0: error: This distro is untested. Only tested version atm is Stretch."
124 exit 1
125 fi
126
127 #### end arg error checking ####
128
129
130 boot=true
131 [[ $2 == boot ]] || boot=false
132
133 op_size=$1 # operator plus size
134 if [[ $op_size == *g ]]; then
135 op_size=${op_size%g}
136 size=${op_size#?}
137 op=${op_size%$size}
138 size=$(( $size * 1024 ))
139 op_size=$op$size
140 else
141 size=${op_size#?}
142 fi
143
144 if [[ $op_size == +* ]]; then
145 op_size_rev=-$size # rev = reverse
146 grow=true
147 else
148 op_size_rev=+$size
149 grow=false
150 fi
151
152 ##### end command line parsing ########
153
154 rootn=1
155 swapn=2
156 bootn=3
157 needs_reboot=false
158 reboot_script_initialized=false
159
160 pmk() { # partition make
161 part=$1
162 start_op=$2
163 end_op=$3
164 fs_type="$4"
165
166 # This fails outside a vm, but actually succeeds. also prints this
167 # message:
168 # Error: Partition(s) 2 on /dev/sda have been written, but
169 # we have been unable to inform the kernel of the change, probably
170 # because it/they are in use. As a result, the old partition(s)
171 # will remain in use. You should reboot now before making further
172 # changes.
173
174 if ! p mkpart primary "$fs_type" \
175 $((${ptable[start$part]} $start_op)) $((${ptable[end$part]} $end_op)); then
176 echo "$0: warning: ignoring failure return of mkpart"
177 fi
178 }
179
180 def-e() {
181 if $dry_run; then
182 e() { echo "+ $@"; }
183 else
184 e() { echo "+ $@"; "$@"; }
185 fi
186 }
187
188 def-e
189
190
191 while read devid dev; do
192 case $dev in
193 /dev/dm-[0-9])
194 # older oses, it points to /dev/dm-x
195 dev=$(dmsetup info $dev | sed -rn 's/^\s*Name:\s*(\S*)/\1/p')
196 ;;
197 /dev/mapper/*)
198 dev=${dev#/dev/mapper/}
199 ;;
200 *)
201 echo "$0: error: could not find devicemapper root dev,
202 make sure you are running from a encrypted root this script is resizing"
203 exit 1
204 ;;
205 esac
206 if [[ $dev != crypt_dev_*-part$rootn ]]; then
207 echo "$0: error: unexpected root device name,
208 make sure you are running from a encrypted root this script is resizing"
209 exit 1
210 fi
211 dev=${dev#crypt_dev_}
212 dev=${dev%-part$rootn}
213 devpath=/dev/disk/by-id/$dev
214 echo skip=$size
215 def-e
216 declare -A ptable
217 while IFS=: read id start end psize _; do
218 [[ $id == [0-9] ]] || continue
219 ptable[start$id]=start=${start%%[^0-9]*}
220 ptable[end$id]=${end%%[^0-9]*}
221 ptable[size$id]=${psize%%[^0-9]*}
222 done < <(parted -m $devpath unit MiB print)
223 parted $devpath unit MiB print | tee /root/backup_partition_table_$dev
224 p() { e parted -a optimal -s -- $devpath unit MiB "$@"; }
225 unit=systemd-cryptsetup@crypt_dev_$dev-part$swapn
226 # note systemctl show can test if a unit exists.
227 if ! e systemctl stop $unit; then
228 e swapoff -a
229 fi
230 # there is a bug in jessie. this and the .swap unit are
231 # generated from /etc/fstab, and it escapes - to x2d, then doesn't escape it
232 # when looking for the file to use as swap. so, no swap is working on jessie
233 # right now.
234 sleep 1 # dunno if this is needed,
235 # but systemd likes to do these kind of things in the background.
236
237 # These partition comments seems a little verbose now, but I bet they
238 # will be helpfull if I read this in more than a week from now.
239 # <> = deleted partition, () = partition
240 p rm $swapn # ( root )< swap >( boot )
241
242 root_resize_cmd="e btrfs fi resize $devid:${op_size_rev}M /"
243 if $grow; then $root_resize_cmd; fi
244 # if $grow; then
245 # < root >< swap >( boot )
246 # ( root ) >< swap >( boot )
247 # else
248 # < root >< swap >( boot )
249 # ( root >< ) swap >( boot )
250
251 out=$(p rm $rootn 2>&1)
252 echo "$out"
253
254 pmk $rootn "" $op_size_rev
255
256 if echo "$out" | \
257 grep "but we have been unable to inform the kernel" &>/dev/null; then
258 needs_reboot=true
259 fi
260 if $needs_reboot; then
261 # note: even if these units don't exist, this will succeed.
262 e systemctl mask dev-mapper-crypt_swap_$dev$swapn.swap
263 e systemctl mask systemd-cryptsetup@crypt_swap_$dev$swapn.service
264 e() { echo "$@" >> /root/finish-resize; }
265 if ! $reboot_script_initialized; then
266 reboot_script_initialized=true
267 rm -rf /root/finish-resize
268 cat >/root/finish-resize <<'EOF'
269 #!/bin/bash -x
270 set -eE -o pipefail
271 trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
272 EOF
273 chmod +x /root/finish-resize
274 fi
275 e swapoff -a
276 e systemctl unmask systemd-cryptsetup@crypt_swap_$dev$swapn.service
277 e systemctl unmask dev-mapper-crypt_swap_$dev$swapn.swap
278 fi
279 if ! $grow; then
280 $root_resize_cmd
281 fi
282 if $boot; then
283 # non by-id path, to match what btrfs fi show will tell us
284 boot_dev_path=$(readlink -f $devpath-part$bootn)
285 boot_devid=$(btrfs fi show /boot | \
286 sed -rn "s#^\s*devid\s+(\S+)\s.*$boot_dev_path#\1#p")
287
288 if ! $grow; then
289 # shrink boot, move it to a temp file
290 e btrfs fi resize $boot_devid:${op_size}M /boot
291 e umount /boot
292 temp_boot=/root/temp_boot_$dev
293 e dd bs=1M if=$boot_dev_path of=$temp_boot \
294 count=$((${ptable[size$bootn]} $op_size))
295 else
296 e umount /boot
297 fi
298 # if $grow; then
299 # ( root ) >< swap >< boot >
300 # ( root )( >< swap ) >< boot >
301 # ( root )( >< swap )( >< boot )
302 # else
303 # ( root >< ) swap >< boot >
304 # ( root >< )( swap >< ) boot >
305 # ( root >< )( swap >< )( boot )
306 p rm $bootn
307 pmk $swapn $op_size_rev $op_size_rev "linux-swap"
308 pmk $bootn $op_size_rev ""
309
310 if $grow; then
311 e dd bs=1M if=$boot_dev_path of=$boot_dev_path skip=$size
312 e mount /boot
313 e btrfs fi resize $boot_devid:${op_size}M /boot
314 else
315 e dd bs=1M if=$temp_boot of=$boot_dev_path
316 e mount /boot
317 fi
318 else
319 # if $grow; then ( root )( >< swap )( boot )
320 # else ( root >< )( swap )( boot )
321 pmk $swapn $op_size_rev "" "linux-swap"
322 e systemctl start systemd-cryptsetup@crypt_swap_$dev$swapn
323 fi
324 done < <(btrfs fi show / | sed -nr 's#^\s*devid\s*(\S+).* path (.*)$#\1 \2#p')
325
326
327 if $boot; then
328 e rm -rf "/root/temp_boot_*"
329 e rm -f /root/finish-resize
330 fi
331
332 if $needs_reboot; then
333 echo "$0: Reboot, run /root/finish-resize. It's contents:"
334 cat /root/finish-resize
335 if $reboot_now; then
336 echo "$0: rebooting now"
337 reboot now
338 exit
339 fi
340 fi