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