]> git.proxmox.com Git - proxmox-kernel-helper.git/blob - bin/proxmox-boot-tool
p-b-t: pinning: prompt for auto-refresh on interactive seesion
[proxmox-kernel-helper.git] / bin / proxmox-boot-tool
1 #!/bin/sh
2
3 set -e
4
5 . /usr/share/pve-kernel-helper/scripts/functions
6
7 _add_entry_to_list_file() {
8 file="$1"
9 entry="$2"
10
11 if [ -e "$file" ]; then
12 cp "$file" "$file.new"
13 fi
14 echo "$entry" >> "$file.new"
15 sort -uo "$file.new" "$file.new"
16 mv "$file.new" "$file"
17 }
18
19 _remove_entry_from_list_file() {
20 file="$1"
21 entry="$2"
22
23 # guard against removing whole file by accident!
24 if [ -z "$entry" ]; then
25 echo "cannot remove empty entry from '$file'."
26 return
27 fi
28
29 if [ -e "$file" ]; then
30 grep -vFx "$entry" "$file" > "$file.new" || true
31 mv "$file.new" "$file"
32 else
33 echo "'$file' does not exist.."
34 fi
35 }
36
37 _get_partition_info() {
38 if [ ! -e "$1" ]; then
39 warn "E: '$1' does not exist!"
40 exit 1
41 fi
42 bdev=$(realpath "$1")
43 if [ ! -b "$bdev" ]; then
44 warn "E: '$bdev' is not a block device!"
45 exit 1
46 fi
47
48 bdev_info=$( \
49 lsblk \
50 --bytes \
51 --pairs \
52 -o 'UUID,SIZE,FSTYPE,PARTTYPE,PKNAME,MOUNTPOINT' \
53 "$bdev" \
54 )
55 if [ -z "$bdev_info" ]; then
56 warn "E: unable to get information about block device '$1'!"
57 exit 1
58 fi
59
60 count=$(echo "$bdev_info" | grep -c '^')
61 if [ "$count" -ne '1' ]; then
62 echo "$bdev_info"
63 warn "E: block device '$1' has children!"
64 exit 1
65 fi
66
67 echo "$bdev_info"
68 eval "$bdev_info"
69
70 if [ -z "$PKNAME" ]; then
71 warn "E: cannot determine parent device of '$1' - please provide a partition, not a full disk."
72 exit 1
73 fi
74
75 if [ -n "$SIZE" ] && [ "$SIZE" -lt 268435456 ]; then
76 warn "E: '$1' is too small (<256M)."
77 exit 1
78 fi
79
80 if [ -n "$MOUNTPOINT" ]; then
81 warn "E: '$1' is mounted on '$MOUNTPOINT' - exiting."
82 exit 1
83 fi
84 }
85
86 format() {
87 part="$1"
88 force="$2"
89
90 _get_partition_info "$part"
91
92 if [ -n "$FSTYPE" ]; then
93 if [ -z "$force" ] || [ "$force" != '--force' ]; then
94 warn "E: '$part' contains a filesystem ('$FSTYPE') - exiting (use --force to override)"
95 exit 1
96 fi
97 fi
98
99 part_basename=$(basename "$bdev")
100 if [ -z "$part_basename" ]; then
101 if [ "$part" != "$bdev" ]; then
102 symlinkmsg=" -> '$bdev'"
103 fi
104 warn "E: unable to determine basename of '$part'$symlinkmsg"
105 exit 1
106 fi
107
108 part_num=$(cat /sys/block/"$PKNAME"/"$part_basename"/partition)
109 if [ -z "$part_num" ]; then
110 warn "E: unable to determine partition number of '$part'"
111 exit 1
112 fi
113
114 if [ -z "$PARTTYPE" ] || [ "$PARTTYPE" != "$ESPTYPE" ]; then
115 echo "Setting partition type of '$part' to '$ESPTYPE'.."
116 sgdisk "-t$part_num:$ESPTYPE" "/dev/$PKNAME"
117 echo "Calling 'udevadm settle'.."
118 udevadm settle --timeout=5
119 fi
120
121 echo "Formatting '$part' as vfat.."
122 mkfs.vfat -F 32 "$part"
123 echo "Done."
124 exit 0
125 }
126
127 init() {
128 part="$1"
129
130 _get_partition_info "$part"
131
132 if [ -z "$PARTTYPE" ] || [ "$PARTTYPE" != "$ESPTYPE" ]; then
133 warn "E: '$part' has wrong partition type (!= $ESPTYPE)."
134 exit 1
135 fi
136
137 if [ -z "$FSTYPE" ] || [ "$FSTYPE" != 'vfat' ]; then
138 warn "E: '$part' has wrong filesystem (!= vfat)."
139 exit 1
140 fi
141
142 if [ -z "$UUID" ]; then
143 warn "E: '$part' has no UUID set, required for mounting."
144 exit 1
145 fi
146
147 esp_mp="/var/tmp/espmounts/$UUID"
148
149 mkdir -p "$esp_mp"
150 echo "Mounting '$part' on '$esp_mp'."
151 mount -t vfat "$part" "$esp_mp"
152
153 if [ -d /sys/firmware/efi ]; then
154 echo "Installing systemd-boot.."
155 mkdir -p "$esp_mp/$PMX_ESP_DIR"
156 bootctl --graceful --path "$esp_mp" install
157
158 echo "Configuring systemd-boot.."
159 echo "timeout 3" > "$esp_mp/$PMX_LOADER_CONF.tmp"
160 echo "default proxmox-*" >> "$esp_mp/$PMX_LOADER_CONF.tmp"
161 mv "$esp_mp/$PMX_LOADER_CONF.tmp" "$esp_mp/$PMX_LOADER_CONF"
162 else
163 echo "Installing grub i386-pc target.."
164 grub-install.real \
165 --boot-directory "$esp_mp" \
166 --target i386-pc \
167 --no-floppy \
168 --bootloader-id='proxmox' \
169 "/dev/$PKNAME"
170 fi
171 echo "Unmounting '$part'."
172 umount "$part"
173
174 echo "Adding '$part' to list of synced ESPs.."
175 _add_entry_to_list_file "$ESP_LIST" "$UUID"
176
177 echo "Refreshing kernels and initrds.."
178 refresh
179 }
180
181 _clean_impl() {
182 if [ ! -e "/dev/disk/by-uuid/" ]; then
183 warn 'E: /dev/disk/by-uuid does not exist, aborting!'
184 exit 1
185 fi
186 printf "Checking whether ESP '%s' exists.. " "$curr_uuid" # avoid newline
187 if [ -e "/dev/disk/by-uuid/$curr_uuid" ]; then
188 echo "Found!"
189 else
190 echo "Not found!"
191 if [ -z "$dry_run" ] || [ "$dry_run" != '--dry-run' ]; then
192 _remove_entry_from_list_file "$ESP_LIST" "$curr_uuid"
193 fi
194 fi
195 }
196
197 clean() {
198 dry_run="$1"
199 rm -f "$ESP_LIST".tmp
200 loop_esp_list _clean_impl
201 if [ "$?" -eq 2 ]; then
202 warn "E: $ESP_LIST does not exist."
203 exit 1
204 fi
205 if [ -e "$ESP_LIST".tmp ]; then
206 mv "$ESP_LIST".tmp "$ESP_LIST"
207 fi
208
209 echo "Sorting and removing duplicate ESPs.."
210 sort -uo "$ESP_LIST".tmp "$ESP_LIST"
211 mv "$ESP_LIST".tmp "$ESP_LIST"
212 }
213
214 refresh() {
215 hook=$1
216 hookscripts='proxmox-auto-removal zz-proxmox-boot'
217
218 if [ -n "$hook" ]; then
219 if echo "$hookscripts" | grep -sqE "(^|[[:space:]]+)$hook([[:space:]]+|$)"; then
220 hookscripts="$hook"
221 else
222 warn "E: '$hook' is not a valid hook script name.";
223 exit 1;
224 fi
225 fi
226
227 for script in $hookscripts; do
228 scriptpath="/etc/kernel/postinst.d/$script"
229 if [ -f "$scriptpath" ] && [ -x "$scriptpath" ]; then
230 echo "Running hook script '$script'.."
231 $scriptpath
232 else
233 warn "Hook script '$script' not found or not executable, skipping."
234 fi
235 done
236 }
237
238 add_kernel() {
239 ver="$1"
240
241 if [ -z "$ver" ]; then
242 warn "E: <kernel-version> is mandatory"
243 warn ""
244 exit 1
245 fi
246
247 if [ ! -e "/boot/vmlinuz-$ver" ]; then
248 warn "E: no kernel image found in /boot for '$ver', not adding."
249 exit 1
250 fi
251 _add_entry_to_list_file "$MANUAL_KERNEL_LIST" "$ver"
252 echo "Added kernel '$ver' to manual kernel list. Use the 'refresh' command to update the ESPs."
253 }
254
255 remove_kernel() {
256 ver="$1"
257
258 if [ -z "$ver" ]; then
259 warn "E: <kernel-version> is mandatory"
260 warn ""
261 exit 1
262 fi
263
264 if grep -sqFx "$ver" "$MANUAL_KERNEL_LIST"; then
265 _remove_entry_from_list_file "$MANUAL_KERNEL_LIST" "$ver"
266 echo "Removed kernel '$ver' from manual kernel list. Use the 'refresh' command to update the ESPs."
267 else
268 echo "Kernel '$ver' not found in manual kernel list."
269 fi
270 }
271
272 list_kernels() {
273 boot_kernels="$(boot_kernel_list)"
274
275 if [ -e "$MANUAL_KERNEL_LIST" ]; then
276 manual_kernels="$(cat "$MANUAL_KERNEL_LIST" || true)"
277 boot_kernels="$(echo "$boot_kernels" | grep -Fxv -f "$MANUAL_KERNEL_LIST" || true)"
278 fi
279
280 if [ -z "$manual_kernels" ]; then
281 manual_kernels="None."
282 fi
283
284 echo "Manually selected kernels:"
285 echo "$manual_kernels"
286 echo ""
287 echo "Automatically selected kernels:"
288 echo "$boot_kernels"
289
290 pinned_kernel="$(get_first_line "$PINNED_KERNEL_CONF")"
291 nextboot_kernel="$(get_first_line "$NEXT_BOOT_PIN")"
292 if [ -n "$pinned_kernel" ]; then
293 echo ""
294 echo "Pinned kernel:"
295 echo "${pinned_kernel}"
296 fi
297 if [ -n "$nextboot_kernel" ]; then
298 echo ""
299 echo "Kernel pinned on next-boot:"
300 echo "${nextboot_kernel}"
301 fi
302 }
303
304 usage() {
305 subcmd="$1"
306 if [ -z "$subcmd" ]; then
307 warn "USAGE: $0 <commands> [ARGS]"
308 warn ""
309 fi
310 if [ -z "$subcmd" ] || [ "$subcmd" = "format" ]; then
311 warn " $0 format <partition> [--force]"
312 fi
313 if [ -z "$subcmd" ] || [ "$subcmd" = "init" ]; then
314 warn " $0 init <partition>"
315 fi
316 if [ -z "$subcmd" ] || [ "$subcmd" = "clean" ]; then
317 warn " $0 clean [--dry-run]"
318 fi
319 if [ -z "$subcmd" ] || [ "$subcmd" = "refresh" ]; then
320 warn " $0 refresh [--hook <name>]"
321 fi
322 if [ -z "$subcmd" ] || [ "$subcmd" = "kernel" ]; then
323 warn " $0 kernel <add|remove> <kernel-version>"
324 warn " $0 kernel pin <kernel-version> [--next-boot]"
325 warn " $0 kernel unpin [--next-boot]"
326 warn " $0 kernel list"
327 fi
328 if [ -z "$subcmd" ] || [ "$subcmd" = "status" ]; then
329 warn " $0 status [--quiet]"
330 fi
331 if [ -z "$subcmd" ] || [ "$subcmd" = "help" ]; then
332 warn " $0 help"
333 fi
334 }
335
336 help() {
337 echo "USAGE: $0 format <partition> [--force]"
338 echo ""
339 echo " format <partition> as EFI system partition. Use --force to format even if <partition> is currently in use."
340 echo ""
341 echo "USAGE: $0 init <partition>"
342 echo ""
343 echo " initialize EFI system partition at <partition> for automatic synchronization of pve-kernels and their associated initrds."
344 echo ""
345 echo "USAGE: $0 clean [--dry-run]"
346 echo ""
347 echo " remove no longer existing EFI system partition UUIDs from $ESP_LIST. Use --dry-run to only print outdated entries instead of removing them."
348 echo ""
349 echo "USAGE: $0 refresh [--hook <name>]"
350 echo ""
351 echo " refresh all configured EFI system partitions. Use --hook to only run the specified hook, omit to run all."
352 echo ""
353 echo "USAGE: $0 kernel <add|remove> <kernel-version>"
354 echo ""
355 echo " add/remove pve-kernel with ABI <kernel-version> to list of synced kernels, in addition to automatically selected ones."
356 echo " NOTE: you need to manually run 'refresh' once you're finished with adding/removing kernels from the list"
357 echo ""
358 echo "USAGE: $0 kernel pin <kernel-version> [--next-boot]"
359 echo ""
360 echo " pin pve-kernel with ABI <kernel-version> as the default entry to be booted."
361 echo " with --next-boot sets <kernel-version> only for the next boot."
362 echo " NOTE: you need to manually run 'refresh' once you're finished with pinning kernels"
363 echo ""
364 echo "USAGE: $0 kernel unpin [--next-boot]"
365 echo ""
366 echo " unpin removes pinned and next-boot kernel settings."
367 echo " with --next-boot only removes the pin for the next boot."
368 echo ""
369 echo "USAGE: $0 kernel list"
370 echo ""
371 echo " list kernel versions currently selected for inclusion on ESPs."
372 echo ""
373 echo "USAGE: $0 status [--quiet]"
374 echo ""
375 echo " Print details about the ESPs configuration. Exits with 0 if any ESP is configured, else with 2."
376 echo ""
377 }
378
379 _status_detail() {
380 if ! (echo "${curr_uuid}" | grep -qE '[0-9a-fA-F]{4}-[0-9a-fA-F]{4}'); then
381 warn "WARN: ${curr_uuid} read from ${ESP_LIST} does not look like a VFAT-UUID - skipping"
382 return
383 fi
384
385 path="/dev/disk/by-uuid/$curr_uuid"
386 if [ ! -e "${path}" ]; then
387 warn "WARN: ${path} does not exist - clean '${ESP_LIST}'! - skipping"
388 return
389 fi
390
391 mountpoint="${MOUNTROOT}/${curr_uuid}"
392 mkdir -p "${mountpoint}" || \
393 { warn "creation of mountpoint ${mountpoint} failed - skipping"; return; }
394 mount "${path}" "${mountpoint}" || \
395 { warn "mount of ${path} failed - skipping"; return; }
396
397 result=""
398 if [ -f "${mountpoint}/$PMX_LOADER_CONF" ]; then
399 if [ ! -d "${mountpoint}/$PMX_ESP_DIR" ]; then
400 warn "${path}/$PMX_ESP_DIR does not exist"
401 fi
402 versions_uefi=$(ls -1 ${mountpoint}/$PMX_ESP_DIR | awk '{printf (NR>1?", ":"") $0}')
403 result="uefi (versions: ${versions_uefi})"
404 fi
405 if [ -d "${mountpoint}/grub" ]; then
406 versions_grub=$(ls -1 ${mountpoint}/vmlinuz-* | awk '{ gsub(/.*\/vmlinuz-/, ""); printf (NR>1?", ":"") $0 }')
407 if [ -n "$result" ]; then
408 result="${result}, grub (versions: ${versions_grub})"
409 else
410 result="grub (versions: ${versions_grub})"
411 fi
412 fi
413 echo "$curr_uuid is configured with: $result"
414 umount "${mountpoint}" || \
415 { warn "umount of ${path} failed - failure"; exit 0; }
416
417 rmdir "${mountpoint}" || true
418 }
419
420 status() {
421 quiet="$1"
422 if [ ! -e "${ESP_LIST}" ]; then
423 if [ -z "$quiet" ]; then
424 warn "E: $ESP_LIST does not exist."
425 fi
426 exit 2
427 fi
428 if [ -z "$quiet" ]; then
429 if [ -d /sys/firmware/efi ]; then
430 echo "System currently booted with uefi"
431 else
432 echo "System currently booted with legacy bios"
433 fi
434 loop_esp_list _status_detail
435 fi
436 }
437
438 _ask_interactive_refresh() {
439 msg="$1"
440
441 if [ -t 0 ] && [ -t 1 ]; then # check if interactive
442 echo "$msg."
443 printf "Refresh the actual boot ESPs now? [yN] "
444 read -r do_refresh
445 if [ "$do_refresh" != "${do_refresh#[Yy]}" ] ;then
446 refresh
447 else
448 echo "Skip auto-refresh, you can call it any time to enact boot changes."
449 fi
450 else
451 echo "$msg. Use the 'refresh' command to update the ESPs."
452 fi
453 }
454
455 pin_kernel() {
456 ver="$1"
457 pin_file="$2"
458
459 if [ -z "$ver" ]; then
460 boot_kernels="$(boot_kernel_list)"
461 warn "E: <kernel-version> is mandatory"
462 warn ""
463 warn "Possible Proxmox kernel versions are:"
464 warn "$boot_kernels"
465 exit 1
466 fi
467
468 if [ -z "$pin_file" ]; then
469 pin_file="$PINNED_KERNEL_CONF"
470 fi
471
472 if [ ! -e "/boot/vmlinuz-$ver" ]; then
473 boot_kernels="$(boot_kernel_list)"
474 warn "E: no kernel image found in /boot for '$ver', not setting default."
475 warn ""
476 warn "Possible Proxmox kernel versions are:"
477 warn "$boot_kernels"
478 exit 1
479 fi
480
481 if [ -e "$pin_file" ]; then
482 old_pin=$(get_first_line "${pin_file}")
483 if [ "$ver" != "$old_pin" ]; then
484 echo "Overriding previously pinned version '$old_pin' with '$ver'"
485 fi
486 fi
487 echo "$ver" > "$pin_file"
488
489 if [ -f "${ESP_LIST}" ]; then
490 _ask_interactive_refresh "Set kernel '$ver' in $pin_file"
491 else
492 next_boot_ver=$(get_first_line "${NEXT_BOOT_PIN}")
493 pin_ver="${next_boot_ver:-$ver}"
494 echo "Setting '$pin_ver' as grub default entry and running update-grub."
495 set_grub_default "$pin_ver"
496 update-grub
497 fi
498 }
499
500 unpin_kernel() {
501 last_pin=$(get_first_line "${NEXT_BOOT_PIN}")
502 rm -f "$NEXT_BOOT_PIN"
503 echo "Removed $NEXT_BOOT_PIN."
504 if [ -z "$1" ]; then
505 old_pin=$(get_first_line "${PINNED_KERNEL_CONF}")
506 last_pin=${old_pin:-$last_pin}
507 rm -f "$PINNED_KERNEL_CONF"
508 echo "Removed $PINNED_KERNEL_CONF."
509 fi
510
511 if [ -f "${ESP_LIST}" ]; then
512 if [ -n "$last_pin" ]; then
513 _ask_interactive_refresh "Unpinned kernel '$last_pin'"
514 fi
515 else
516 echo "Reset default grub entry and running update-grub."
517 pinned_kernel=$(get_first_line "${PINNED_KERNEL_CONF}")
518 set_grub_default "$pinned_kernel"
519 update-grub
520 fi
521 }
522
523 if [ -z "$1" ]; then
524 usage
525 exit 0
526 fi
527
528 case "$1" in
529 'format')
530 shift
531 if [ -z "$1" ]; then
532 warn "E: <partition> is mandatory."
533 warn ""
534 usage "format"
535 exit 1
536 fi
537 format "$@"
538 exit 0
539 ;;
540 'init')
541 reexec_in_mountns "$@"
542 shift
543 if [ -z "$1" ]; then
544 warn "E: <partition> is mandatory."
545 warn ""
546 usage "init"
547 exit 1
548 fi
549 init "$@"
550 exit 0
551 ;;
552 'clean')
553 shift
554 clean "$@"
555 exit 0
556 ;;
557 'refresh')
558 shift
559 if [ "$#" -eq 0 ]; then
560 refresh
561 elif [ "$#" -eq 2 ] && [ "$1" = "--hook" ]; then
562 refresh "$2"
563 else
564 usage "refresh"
565 exit 1
566 fi
567 exit 0
568 ;;
569 'kernel'|'kernels')
570 shift
571 if [ -z "$1" ]; then
572 warn "E: subcommand is mandatory for 'kernel'."
573 warn ""
574 usage "kernel"
575 exit 1
576 fi
577 cmd="$1"
578 case "$cmd" in
579 'add')
580 add_kernel "$2"
581 exit 0
582 ;;
583 'remove')
584 remove_kernel "$2"
585 exit 0
586 ;;
587 'list')
588 list_kernels
589 exit 0
590 ;;
591 'pin')
592 if [ "$#" -eq 3 ] && [ "$3" = '--next-boot' ]; then
593 pin_kernel "$2" "${NEXT_BOOT_PIN}"
594 elif [ "$#" -eq 2 ]; then
595 pin_kernel "$2"
596 else
597 usage "kernel"
598 exit 1
599 fi
600 exit 0
601 ;;
602 'unpin')
603 if [ "$#" -eq 2 ] && [ "$2" = '--next-boot' ]; then
604 unpin_kernel "$2"
605 elif [ "$#" -eq 1 ]; then
606 unpin_kernel
607 else
608 usage "kernel"
609 exit 1
610 fi
611 exit 0
612 ;;
613 *)
614 warn "E: invalid 'kernel' subcommand '$cmd'."
615 warn ""
616 usage "kernel"
617 exit 1
618 ;;
619 esac
620 ;;
621 'status')
622 if [ "$#" -eq 2 ] && [ "$2" = '--quiet' ]; then
623 shift
624 status "$1"
625 elif [ "$#" -eq 1 ]; then
626 reexec_in_mountns "$@"
627 shift
628 status
629 else
630 usage "status"
631 exit 1
632 fi
633 exit 0
634 ;;
635 'help')
636 shift
637 help
638 exit 0
639 ;;
640 *)
641 warn "Invalid/unknown command '$1'."
642 warn ""
643 usage
644 exit 1
645 ;;
646 esac
647
648 exit 1