diff --git a/.perltidyrc b/.perltidyrc new file mode 100644 index 0000000..b7620fb --- /dev/null +++ b/.perltidyrc @@ -0,0 +1 @@ +-i=2 --maximum-line-length=120 -ce -cb diff --git a/90zfsbootmenu/zfsbootmenu-parse-commandline.sh b/90zfsbootmenu/zfsbootmenu-parse-commandline.sh index d888b2b..b4a1e17 100755 --- a/90zfsbootmenu/zfsbootmenu-parse-commandline.sh +++ b/90zfsbootmenu/zfsbootmenu-parse-commandline.sh @@ -26,6 +26,14 @@ else import_args="-o readonly=on -N" fi +# Set a menu timeout, to allow immediate booting +menu_timeout=$( getarg timeout=) +if [ -n "${menu_timeout}" ]; then + info "ZFSBootMenu: Setting menu timeout from command line: ${menu_timeout}" +else + menu_timeout=10 +fi + wait_for_zfs=0 case "${root}" in ""|zfsbootmenu|zfsbootmenu:) diff --git a/90zfsbootmenu/zfsbootmenu.sh b/90zfsbootmenu/zfsbootmenu.sh index c8983d3..b13bebe 100755 --- a/90zfsbootmenu/zfsbootmenu.sh +++ b/90zfsbootmenu/zfsbootmenu.sh @@ -70,50 +70,57 @@ done <<<"${datasets}" # If BOOTFS is not empty display the fast boot menu fast_boot=0 if [[ ! -z "${BOOTFS}" ]]; then - - # Clear the screen - tput civis - HEIGHT=$(tput lines) - WIDTH=$(tput cols) - tput clear - - # Draw the line centered on the screen - mes="[ENTER] to boot" - x=$(( ($HEIGHT - 0) / 2 )) - y=$(( ($WIDTH - ${#mes}) / 2 )) - tput cup $x $y - echo -n ${mes} - - # Draw the line centered on the screen - mes="[ESC] boot menu" - x=$(( $x + 1 )) - y=$(( ($WIDTH - ${#mes}) / 2 )) - tput cup $x $y - echo -n ${mes} - - x=$(( $x + 1 )) - tput cup $x $y - - IFS='' # Draw a countdown menu - for (( i=10; i>0; i--)); do - mes="$( printf 'Booting %s in %0.2d seconds' ${BOOTFS} ${i} )" + if [[ ${menu_timeout} -gt 0 ]]; then + # Clear the screen + tput civis + HEIGHT=$(tput lines) + WIDTH=$(tput cols) + tput clear + + # Draw the line centered on the screen + mes="[ENTER] to boot" + x=$(( ($HEIGHT - 0) / 2 )) y=$(( ($WIDTH - ${#mes}) / 2 )) tput cup $x $y - echo -ne "${mes}" + echo -n ${mes} - # Wait 1 second for input - read -s -N 1 -t 1 key - # Escape key - if [ "$key" = $'\e' ]; then - break - # Enter key - elif [ "$key" = $'\x0a' ]; then - fast_boot=1 - break - fi - done - IFS="${OLDIFS}" + # Draw the line centered on the screen + mes="[ESC] boot menu" + x=$(( $x + 1 )) + y=$(( ($WIDTH - ${#mes}) / 2 )) + tput cup $x $y + echo -n ${mes} + + x=$(( $x + 1 )) + tput cup $x $y + + IFS='' + for (( i=${menu_timeout}; i>0; i--)); do + mes="$( printf 'Booting %s in %0.2d seconds' ${BOOTFS} ${i} )" + y=$(( ($WIDTH - ${#mes}) / 2 )) + tput cup $x $y + echo -ne "${mes}" + + # Wait 1 second for input + read -s -N 1 -t 1 key + # Escape key + if [ "$key" = $'\e' ]; then + break + # Enter key + elif [ "$key" = $'\x0a' ]; then + fast_boot=1 + break + fi + done + IFS="${OLDIFS}" + elif [[ ${menu_timeout} -eq 0 ]]; then + # Bypass the menu, immediately boot $BOOTFS + fast_boot=1 + else + # Make sure we bypass the other fastboot check + i=1 + fi # Boot up if we timed out, or if the enter key was pressed if [[ ${fast_boot} -eq 1 || $i -eq 0 ]]; then diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..f38fc53 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.7.3 diff --git a/bin/generate-zbm b/bin/generate-zbm new file mode 100755 index 0000000..9b70fb3 --- /dev/null +++ b/bin/generate-zbm @@ -0,0 +1,240 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +our $VERSION = '0.7.3'; + +use Getopt::Long qw(:config no_ignore_case auto_version); +use Pod::Usage qw(pod2usage); +use File::Basename; +use Sys::Hostname; +use Config::IniFiles; +use File::Temp qw(tempfile tempdir); +use File::Copy; +use File::Path qw(make_path remove_tree); + +use Data::Dumper; +$Data::Dumper::Indent = 1; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Purity = 1; + +sub latestKernel; +sub createInitramfs; +sub unifiedEFI; +sub execute; + +my ( %runConf, %config, %components ); + +my $configfile = "/etc/zfsbootmenu/config.ini"; +my $version_file = "/usr/share/zfsbootmenu/VERSION"; + +$runConf{bootdir} = "/boot"; +$runConf{confd} = "/usr/share/zfsbootmenu/dracut.conf.d"; + +GetOptions( + "version|v=s" => \$runConf{version}, + "pkgname|p=s" => \$runConf{pkgname}, + "action|a=s" => \$runConf{action}, + "update|u=s" => \$runConf{update}, + "kernel|k=s" => \$runConf{kernel}, + "bootdir|b=s" => \$runConf{bootdir}, + "confd|C=s" => \$runConf{confd}, + "config|c=s" => \$configfile, +); + +# Sanity check, ensure we have a configuration file +unless ( -f $configfile ) { + print "$configfile missing, exiting\n"; + exit; +} + +# Read our config into a hash +tie %config, 'Config::IniFiles', ( -file => $configfile ); + +unless ( ( defined $config{Global}{ManageImages} ) and ( $config{Global}{ManageImages} ) ) { + print "ManageImages not enabled, no action taken\n"; + exit; +} + +# Override the location of our specific dracut.conf.d directory +if ( defined $config{Global}{DracutConfDir} ) { + $runConf{confd} = $config{Global}{DracutConfDir}; +} + +# Sanity check that we can determine the version of zfsbootmenu +unless ( defined $runConf{version} ) { + if ( -f $version_file ) { + print "Found $version_file\n"; + open(my $fh, '<', $version_file); + read $fh, $runConf{version}, -s $fh; + close($fh); + chomp($runConf{version}); + } else { + print "Unable to determine version\n"; + exit; + } +} + +# Create a temp directory +# It is automatically purged on program exit +my $dir = File::Temp->newdir(); +my $tempdir = $dir->dirname; + +# Set our kernel from the CLI, or pick the latest available in /boot +if ( ( defined $runConf{kernel} ) and ( length $runConf{kernel} ) ) { + unless ( -f $runConf{kernel} ) { + printf "The provided kernel %s was not found, unable to continue", $runConf{kernel}; + exit; + } +} else { + $runConf{kernel} = latestKernel; +} + +$runConf{bootdir} = dirname( $runConf{kernel} ); +( $runConf{kernel_prefix}, $runConf{kernel_version} ) = split( '-', basename( $runConf{kernel} ) ); + +# We always need to create an initramfs +printf "Creating ZFS Boot Menu %s, with %s %s\n", $runConf{version}, $runConf{kernel_prefix}, $runConf{kernel_version}; + +$runConf{initramfs} = createInitramfs( $tempdir, $runConf{kernel_version} ); + +if ( defined( $config{EFI}{Copies} ) and ( $config{EFI}{Copies} gt 0 ) ) { + $runConf{unified_efi} = unifiedEFI( $tempdir, $runConf{kernel}, $runConf{initramfs} ); + + if ( defined( $config{EFI}{Versioned} ) and ( $config{EFI}{Versioned} ) ) { + $runConf{efi_target} = sprintf( "%s/ZFSBootMenu-%s.EFI", $config{EFI}{ImageDir}, $runConf{version} ); + } else { + $runConf{efi_target} = sprintf( "%s/ZFSBootMenu.EFI", $config{EFI}{ImageDir} ); + } + + my $glob = join( '/', ( $config{EFI}{ImageDir}, "ZFSBootMenu*.EFI" ) ); + my @efi = sort glob($glob); + + my $index = 0; + foreach my $entry (@efi) { + if ( $entry eq $runConf{efi_target} ) { + splice @efi, $index, 1; + } + $index++; + } + + printf "Found %s existing images, allowed to have a total of %s\n", scalar @efi, $config{EFI}{Copies}; + while ( scalar @efi > $config{EFI}{Copies} ) { + my $image = shift(@efi); + printf "Removing %s\n", $image; + unlink $image; + } + + copy $runConf{unified_efi}, $runConf{efi_target}; + printf "Created a unified EFI at %s\n", $runConf{efi_target}; +} + +if ( defined( $config{Components}{Copies} ) and ( $config{Components}{Copies} gt 0 ) ) { + if ( defined( $config{Components}{Versioned} ) and ( $config{Components}{Versioned} ) ) { + $runConf{kernel_target} = + sprintf( "%s/%s-%s", $config{Components}{ImageDir}, $runConf{kernel_prefix}, $runConf{version} ); + $runConf{initramfs_target} = sprintf( "%s/initramfs-%s.img", $config{Components}{ImageDir}, $runConf{version} ); + + my $glob = sprintf( "%s/%s-*", $config{Components}{ImageDir}, $runConf{kernel_prefix} ); + my @components = sort glob($glob); + + # Drop the component count if the entry we're making already exists + my $index = 0; + foreach my $entry (@components) { + if ( $entry eq $runConf{kernel_target} ) { + splice @components, $index, 1; + } + $index++; + } + + printf "Found %s existing images, allowed to have a total of %s\n", scalar @components, $config{Components}{Copies}; + while ( scalar @components > $config{Components}{Copies} ) { + my $kernel = shift(@components); + my $initramfs = sprintf( "%s.img", $kernel ); + $initramfs =~ s/\Q$runConf{kernel_prefix}/initramfs/; + printf "Removing %s, %s\n", $kernel, $initramfs; + unlink $kernel; + unlink $initramfs; + } + } else { + $runConf{kernel_target} = sprintf( "%s/%s-bootmenu", $config{Components}{ImageDir}, $runConf{kernel_prefix} ); + $runConf{kernel_backup} = + sprintf( "%s/%s-bootmenu-backup", $config{Components}{ImageDir}, $runConf{kernel_prefix} ); + $runConf{initramfs_target} = sprintf( "%s/initramfs-bootmenu.img", $config{Components}{ImageDir} ); + $runConf{initramfs_backup} = sprintf( "%s/initramfs-bootmenu-backup.img", $config{Components}{ImageDir} ); + + if ( -f $runConf{kernel_target} ) { + copy $runConf{kernel_target}, $runConf{kernel_backup}; + copy $runConf{initramfs_target}, $runConf{initramfs_backup}; + printf "Created %s, %s\n", $runConf{kernel_backup}, $runConf{initramfs_backup}; + } + } + + copy $runConf{kernel}, $runConf{kernel_target}; + copy $runConf{initramfs}, $runConf{initramfs_target}; + printf "Created %s, %s\n", $runConf{kernel_target}, $runConf{initramfs_target}; +} + +# Finds the latest kernel in /boot +sub latestKernel { + my @prefixes = ( "vmlinux*", "vmlinuz*", "linux*", "kernel*" ); + for my $prefix (@prefixes) { + my $glob = join( '/', ( $runConf{bootdir}, $prefix ) ); + my @kernels = sort glob($glob); + next if !@kernels; + return pop @kernels; + } +} + +# Returns the path to an initramfs, or dies with an error +sub createInitramfs { + my ( $temp, $kver ) = @_; + + my $output_file = join( '/', $temp, "zfsbootmenu" ); + my @cmd = ( qw(dracut -q -f --confdir), $runConf{confd}, $output_file, qw(--kver), $kver, ); + my ( $output, $status ) = execute(@cmd); + if ( $status eq 0 ) { + return $output_file; + } else { + print $output; + die "Failed to create $output_file"; + } +} + +sub unifiedEFI { + my ( $temp, $kernel, $initramfs ) = @_; + + my $output_file = join( '/', $temp, "zfsbootmenu.efi" ); + my $cmdline_file = join( '/', $temp, "cmdline.txt" ); + my $cmdline = $config{Kernel}{CommandLine}; + + open( my $fh, '>', $cmdline_file ); + print $fh $cmdline; + close($fh); + + my @cmd = ( + qw(objcopy), + qw(--add-section .osrel=/etc/os-release --change-section-vma .osrel=0x20000), + qq(--add-section .cmdline=$cmdline_file), + qw(--change-section-vma .cmdline=0x30000), + qq(--add-section .linux=$kernel), + qw(--change-section-vma .linux=0x40000), + qq(--add-section .initrd=$initramfs), + qw(--change-section-vma .initrd=0x3000000), + qw(/usr/lib/gummiboot/linuxx64.efi.stub), + $output_file + ); + + my ( $output, $status ) = execute(@cmd); + if ( $status eq 0 ) { + return $output_file; + } else { + print $output; + die "Failed to create $output_file"; + } +} + +sub execute { + ( $_ = qx{@_ 2>&1}, $? >> 8 ); +} diff --git a/etc/default/zfsbootmenu b/etc/default/zfsbootmenu deleted file mode 100644 index 51429a2..0000000 --- a/etc/default/zfsbootmenu +++ /dev/null @@ -1,2 +0,0 @@ -MANAGE_IMAGES=0 -IMAGE_DIR=boot/efi diff --git a/etc/zfsbootmenu/config b/etc/zfsbootmenu/config deleted file mode 100644 index bc6bf26..0000000 --- a/etc/zfsbootmenu/config +++ /dev/null @@ -1,2 +0,0 @@ -CREATE_BOOT_IMAGE=0 -IMAGE_DIR=/efi diff --git a/etc/zfsbootmenu/config.ini b/etc/zfsbootmenu/config.ini new file mode 100644 index 0000000..b1c1e36 --- /dev/null +++ b/etc/zfsbootmenu/config.ini @@ -0,0 +1,16 @@ +[Global] +ManageImages=0 +DracutConfDir=/etc/zfsbootmenu/dracut.conf.d + +[Kernel] +CommandLine="ro quiet loglevel=0" + +[Components] +ImageDir=/boot/efi/EFI/void +Versioned=1 +Copies=3 + +[EFI] +ImageDir=/boot/efi/EFI/void +Versioned=1 +Copies=0 diff --git a/etc/zfsbootmenu/dracut.conf.d/zfsbootmenu.conf b/etc/zfsbootmenu/dracut.conf.d/zfsbootmenu.conf index c7588b3..6396919 100644 --- a/etc/zfsbootmenu/dracut.conf.d/zfsbootmenu.conf +++ b/etc/zfsbootmenu/dracut.conf.d/zfsbootmenu.conf @@ -1,4 +1,3 @@ nofsck="yes" -hostonly="yes" add_dracutmodules+=" zfsbootmenu " omit_dracutmodules+=" btrfs zfs resume " diff --git a/mount-zfs.sh.diff b/examples/mount-zfs.sh.diff similarity index 100% rename from mount-zfs.sh.diff rename to examples/mount-zfs.sh.diff diff --git a/examples/refind_linux.conf b/examples/refind_linux.conf new file mode 100755 index 0000000..dfc6744 --- /dev/null +++ b/examples/refind_linux.conf @@ -0,0 +1,2 @@ +"Boot Default BE" "ro quiet loglevel=0 timeout=0" +"Select BE" "ro quiet loglevel=0 timeout=-1" diff --git a/void/zfsbootmenu-0.7.2_1.noarch.xbps b/void/zfsbootmenu-0.7.2_1.noarch.xbps deleted file mode 100644 index 3801ced..0000000 Binary files a/void/zfsbootmenu-0.7.2_1.noarch.xbps and /dev/null differ diff --git a/void/zfsbootmenu/INSTALL b/void/zfsbootmenu/INSTALL deleted file mode 100644 index 13027b2..0000000 --- a/void/zfsbootmenu/INSTALL +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/sh -CONF_FILE=etc/default/zfsbootmenu -CONF_DIR=etc/zfsbootmenu/dracut.conf.d - -if [ -f ${CONF_FILE} ]; then - . ${CONF_FILE} -else - echo "${CONF_FILE} missing" - exit 0 -fi - -if [ "x${MANAGE_IMAGES}" != x1 ]; then - echo "Set MANAGE_IMAGES=1 in ${CONF_FILE}" - exit 0 -elif [ ! -d ${IMAGE_DIR} ]; then - echo "${IMAGE_DIR} does not exist" - exit 0 -fi - -case "${ACTION}" in - pre) - for KERNEL in vmlinux vmlinuz kernel linux; do - if [ -f ${IMAGE_DIR}/${KERNEL}-zfsbootmenu ]; then - cp ${IMAGE_DIR}/${KERNEL}-zfsbootmenu ${IMAGE_DIR}/${KERNEL}-zfsbootmenu-backup - break - fi - done - - if [ -f ${IMAGE_DIR}/initramfs-zfsbootmenu.img ]; then - cp ${IMAGE_DIR}/initramfs-zfsbootmenu.img ${IMAGE_DIR}/initramfs-zfsbootmenu-backup.img - fi - ;; - post) - dracut -q -f --confdir ${CONF_DIR} ${IMAGE_DIR}/initramfs-zfsbootmenu.img - - KVER=$( uname -r ) - - for KERNEL in vmlinux vmlinuz kernel linux; do - if [ -f boot/${KERNEL}-${KVER} ]; then - cp boot/${KERNEL}-${KVER} ${IMAGE_DIR}/${KERNEL}-zfsbootmenu - break - fi - done - - # We always want both a primary and a backup image, even on the first run - for KERNEL in vmlinux vmlinuz kernel linux; do - if [ -f ${IMAGE_DIR}/${KERNEL}-zfsbootmenu ]; then - if [ ! -f ${IMAGE_DIR}/${KERNEL}-zfsbootmenu-backup ]; then - cp ${IMAGE_DIR}/${KERNEL}-zfsbootmenu ${IMAGE_DIR}/${KERNEL}-zfsbootmenu-backup - fi - break - fi - done - - if [ ! -f ${IMAGE_DIR}/initramfs-zfsbootmenu-backup.img ]; then - cp ${IMAGE_DIR}/initramfs-zfsbootmenu.img ${IMAGE_DIR}/initramfs-zfsbootmenu-backup.img - fi - ;; -esac diff --git a/void/zfsbootmenu/INSTALL.msg b/void/zfsbootmenu/INSTALL.msg deleted file mode 100644 index cd65b75..0000000 --- a/void/zfsbootmenu/INSTALL.msg +++ /dev/null @@ -1,2 +0,0 @@ -Refer to https://github.com/zdykstra/zfsbootmenu#installation for -integration directions. diff --git a/void/zfsbootmenu/template b/void/zfsbootmenu/template deleted file mode 100644 index 99889a3..0000000 --- a/void/zfsbootmenu/template +++ /dev/null @@ -1,25 +0,0 @@ -# Template file for 'zfsbootmenu' -pkgname=zfsbootmenu -version=0.7.2 -revision=1 -conf_files="/etc/default/zfsbootmenu - /etc/zfsbootmenu/dracut.conf.d/zfsbootmenu.conf" -archs=noarch -short_desc="ZFSBootMenu implemented in Dracut" -maintainer="Zach Dykstra " -license="MIT" -homepage="https://github.com/zdykstra/zfsbootmenu" -distfiles="https://github.com/zdykstra/zfsbootmenu/archive/v${version}.tar.gz" -checksum=@554e31860459c51c10d2fb908b55dd024db2b10b5d360b5ea6426cdbed5161e6 - -do_install() { - vmkdir usr/lib/dracut/modules.d - vmkdir etc/zfsbootmenu - vmkdir etc/default - - vcopy 90zfsbootmenu usr/lib/dracut/modules.d - vcopy etc/zfsbootmenu etc - vcopy etc/default etc - - vlicense LICENSE -}