Merge pull request #85895 from nh2/extra-grub-install-flags
grub: Add `boot.loader.grub.extraGrubInstallArgs` option
This commit is contained in:
commit
d676d5d119
@ -61,6 +61,7 @@ let
|
||||
inherit (efi) canTouchEfiVariables;
|
||||
inherit (cfg)
|
||||
version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
|
||||
extraGrubInstallArgs
|
||||
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
|
||||
default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
|
||||
path = with pkgs; makeBinPath (
|
||||
@ -298,6 +299,33 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
extraGrubInstallArgs = mkOption {
|
||||
default = [ ];
|
||||
example = [ "--modules=nativedisk ahci pata part_gpt part_msdos diskfilter mdraid1x lvm ext2" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Additional arguments passed to <literal>grub-install</literal>.
|
||||
|
||||
A use case for this is to build specific GRUB2 modules
|
||||
directly into the GRUB2 kernel image, so that they are available
|
||||
and activated even in the <literal>grub rescue</literal> shell.
|
||||
|
||||
They are also necessary when the BIOS/UEFI is bugged and cannot
|
||||
correctly read large disks (e.g. above 2 TB), so GRUB2's own
|
||||
<literal>nativedisk</literal> and related modules can be used
|
||||
to use its own disk drivers. The example shows one such case.
|
||||
This is also useful for booting from USB.
|
||||
See the
|
||||
<link xlink:href="http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326">
|
||||
GRUB source code
|
||||
</link>
|
||||
for which disk modules are available.
|
||||
|
||||
The list elements are passed directly as <literal>argv</literal>
|
||||
arguments to the <literal>grub-install</literal> program, in order.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPerEntryConfig = mkOption {
|
||||
default = "";
|
||||
example = "root (hd0)";
|
||||
@ -669,7 +697,7 @@ in
|
||||
in pkgs.writeScript "install-grub.sh" (''
|
||||
#!${pkgs.runtimeShell}
|
||||
set -e
|
||||
export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ]}
|
||||
export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare JSON ]}
|
||||
${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
|
||||
'' + flip concatMapStrings cfg.mirroredBoots (args: ''
|
||||
${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
|
||||
|
@ -8,6 +8,7 @@ use File::stat;
|
||||
use File::Copy;
|
||||
use File::Slurp;
|
||||
use File::Temp;
|
||||
use JSON;
|
||||
require List::Compare;
|
||||
use POSIX;
|
||||
use Cwd;
|
||||
@ -20,6 +21,16 @@ my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
|
||||
|
||||
sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
|
||||
|
||||
sub getList {
|
||||
my ($name) = @_;
|
||||
my @list = ();
|
||||
foreach my $entry ($dom->findnodes("/expr/attrs/attr[\@name = '$name']/list/string/\@value")) {
|
||||
$entry = $entry->findvalue(".") or die;
|
||||
push(@list, $entry);
|
||||
}
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub readFile {
|
||||
my ($fn) = @_; local $/ = undef;
|
||||
open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
|
||||
@ -241,7 +252,7 @@ if ($grubVersion == 1) {
|
||||
timeout $timeout
|
||||
";
|
||||
if ($splashImage) {
|
||||
copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n";
|
||||
copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
|
||||
$conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
|
||||
}
|
||||
}
|
||||
@ -319,7 +330,7 @@ else {
|
||||
";
|
||||
|
||||
if ($font) {
|
||||
copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n";
|
||||
copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
|
||||
$conf .= "
|
||||
insmod font
|
||||
if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
|
||||
@ -347,7 +358,7 @@ else {
|
||||
background_color '$backgroundColor'
|
||||
";
|
||||
}
|
||||
copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n";
|
||||
copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
|
||||
$conf .= "
|
||||
insmod " . substr($suffix, 1) . "
|
||||
if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
|
||||
@ -381,8 +392,8 @@ sub copyToKernelsDir {
|
||||
# kernels or initrd if this script is ever interrupted.
|
||||
if (! -e $dst) {
|
||||
my $tmp = "$dst.tmp";
|
||||
copy $path, $tmp or die "cannot copy $path to $tmp\n";
|
||||
rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
|
||||
copy $path, $tmp or die "cannot copy $path to $tmp: $!\n";
|
||||
rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n";
|
||||
}
|
||||
$copied{$dst} = 1;
|
||||
return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
|
||||
@ -405,10 +416,10 @@ sub addEntry {
|
||||
# Make sure initrd is not world readable (won't work if /boot is FAT)
|
||||
umask 0137;
|
||||
my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
|
||||
system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets\n";
|
||||
system("$path/append-initrd-secrets", $initrdSecretsPathTemp) == 0 or die "failed to create initrd secrets: $!\n";
|
||||
# Check whether any secrets were actually added
|
||||
if (-e $initrdSecretsPathTemp && ! -z _) {
|
||||
rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place\n";
|
||||
rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
|
||||
$copied{$initrdSecretsPath} = 1;
|
||||
$initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
|
||||
} else {
|
||||
@ -575,7 +586,7 @@ if (get("useOSProber") eq "true") {
|
||||
}
|
||||
|
||||
# Atomically switch to the new config
|
||||
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
|
||||
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n";
|
||||
|
||||
|
||||
# Remove obsolete files from $bootPath/kernels.
|
||||
@ -596,9 +607,12 @@ struct(GrubState => {
|
||||
efi => '$',
|
||||
devices => '$',
|
||||
efiMountPoint => '$',
|
||||
extraGrubInstallArgs => '@',
|
||||
});
|
||||
# If you add something to the state file, only add it to the end
|
||||
# because it is read line-by-line.
|
||||
sub readGrubState {
|
||||
my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" );
|
||||
my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () );
|
||||
open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
|
||||
local $/ = "\n";
|
||||
my $name = <FILE>;
|
||||
@ -611,24 +625,34 @@ sub readGrubState {
|
||||
chomp($devices);
|
||||
my $efiMountPoint = <FILE>;
|
||||
chomp($efiMountPoint);
|
||||
# Historically, arguments in the state file were one per each line, but that
|
||||
# gets really messy when newlines are involved, structured arguments
|
||||
# like lists are needed (they have to have a separator encoding), or even worse,
|
||||
# when we need to remove a setting in the future. Thus, the 6th line is a JSON
|
||||
# object that can store structured data, with named keys, and all new state
|
||||
# should go in there.
|
||||
my $jsonStateLine = <FILE>;
|
||||
# For historical reasons we do not check the values above for un-definedness
|
||||
# (that is, when the state file has too few lines and EOF is reached),
|
||||
# because the above come from the first version of this logic and are thus
|
||||
# guaranteed to be present.
|
||||
$jsonStateLine = defined $jsonStateLine ? $jsonStateLine : '{}'; # empty JSON object
|
||||
chomp($jsonStateLine);
|
||||
my %jsonState = %{decode_json($jsonStateLine)};
|
||||
my @extraGrubInstallArgs = @{$jsonState{'extraGrubInstallArgs'}};
|
||||
close FILE;
|
||||
my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint );
|
||||
my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs );
|
||||
return $grubState
|
||||
}
|
||||
|
||||
sub getDeviceTargets {
|
||||
my @devices = ();
|
||||
foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
|
||||
$dev = $dev->findvalue(".") or die;
|
||||
push(@devices, $dev);
|
||||
}
|
||||
return @devices;
|
||||
}
|
||||
my @deviceTargets = getDeviceTargets();
|
||||
my @deviceTargets = getList('devices');
|
||||
my $prevGrubState = readGrubState();
|
||||
my @prevDeviceTargets = split/,/, $prevGrubState->devices;
|
||||
my @extraGrubInstallArgs = getList('extraGrubInstallArgs');
|
||||
my @prevExtraGrubInstallArgs = $prevGrubState->extraGrubInstallArgs;
|
||||
|
||||
my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
|
||||
my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference());
|
||||
my $nameDiffer = get("fullName") ne $prevGrubState->name;
|
||||
my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
|
||||
my $efiDiffer = $efiTarget ne $prevGrubState->efi;
|
||||
@ -637,25 +661,25 @@ if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
|
||||
warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
|
||||
$ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
|
||||
}
|
||||
my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
|
||||
my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
|
||||
|
||||
# install a symlink so that grub can detect the boot drive
|
||||
my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space";
|
||||
symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot";
|
||||
my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space: $!";
|
||||
symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
|
||||
|
||||
# install non-EFI GRUB
|
||||
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
||||
foreach my $dev (@deviceTargets) {
|
||||
next if $dev eq "nodev";
|
||||
print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
|
||||
my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev));
|
||||
my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
|
||||
if ($forceInstall eq "true") {
|
||||
push @command, "--force";
|
||||
}
|
||||
if ($grubTarget ne "") {
|
||||
push @command, "--target=$grubTarget";
|
||||
}
|
||||
(system @command) == 0 or die "$0: installation of GRUB on $dev failed\n";
|
||||
(system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -663,7 +687,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
||||
# install EFI GRUB
|
||||
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
|
||||
print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
|
||||
my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint");
|
||||
my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
|
||||
if ($forceInstall eq "true") {
|
||||
push @command, "--force";
|
||||
}
|
||||
@ -674,17 +698,29 @@ if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both"))
|
||||
push @command, "--removable" if $efiInstallAsRemovable eq "true";
|
||||
}
|
||||
|
||||
(system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n";
|
||||
(system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n";
|
||||
}
|
||||
|
||||
|
||||
# update GRUB state file
|
||||
if ($requireNewInstall != 0) {
|
||||
open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n";
|
||||
# Temp file for atomic rename.
|
||||
my $stateFile = "$bootPath/grub/state";
|
||||
my $stateFileTmp = $stateFile . ".tmp";
|
||||
|
||||
open FILE, ">$stateFileTmp" or die "cannot create $stateFileTmp: $!\n";
|
||||
print FILE get("fullName"), "\n" or die;
|
||||
print FILE get("fullVersion"), "\n" or die;
|
||||
print FILE $efiTarget, "\n" or die;
|
||||
print FILE join( ",", @deviceTargets ), "\n" or die;
|
||||
print FILE $efiSysMountPoint, "\n" or die;
|
||||
my %jsonState = (
|
||||
extraGrubInstallArgs => \@extraGrubInstallArgs
|
||||
);
|
||||
my $jsonStateLine = encode_json(\%jsonState);
|
||||
print FILE $jsonStateLine, "\n" or die;
|
||||
close FILE or die;
|
||||
|
||||
# Atomically switch to the new state file
|
||||
rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user