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 (efi) canTouchEfiVariables;
|
||||||
inherit (cfg)
|
inherit (cfg)
|
||||||
version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
|
version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
|
||||||
|
extraGrubInstallArgs
|
||||||
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
|
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
|
||||||
default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
|
default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
|
||||||
path = with pkgs; makeBinPath (
|
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 {
|
extraPerEntryConfig = mkOption {
|
||||||
default = "";
|
default = "";
|
||||||
example = "root (hd0)";
|
example = "root (hd0)";
|
||||||
@ -669,7 +697,7 @@ in
|
|||||||
in pkgs.writeScript "install-grub.sh" (''
|
in pkgs.writeScript "install-grub.sh" (''
|
||||||
#!${pkgs.runtimeShell}
|
#!${pkgs.runtimeShell}
|
||||||
set -e
|
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"}
|
${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
|
||||||
'' + flip concatMapStrings cfg.mirroredBoots (args: ''
|
'' + flip concatMapStrings cfg.mirroredBoots (args: ''
|
||||||
${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
|
${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
|
||||||
|
@ -8,6 +8,7 @@ use File::stat;
|
|||||||
use File::Copy;
|
use File::Copy;
|
||||||
use File::Slurp;
|
use File::Slurp;
|
||||||
use File::Temp;
|
use File::Temp;
|
||||||
|
use JSON;
|
||||||
require List::Compare;
|
require List::Compare;
|
||||||
use POSIX;
|
use POSIX;
|
||||||
use Cwd;
|
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 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 {
|
sub readFile {
|
||||||
my ($fn) = @_; local $/ = undef;
|
my ($fn) = @_; local $/ = undef;
|
||||||
open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
|
open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
|
||||||
@ -241,7 +252,7 @@ if ($grubVersion == 1) {
|
|||||||
timeout $timeout
|
timeout $timeout
|
||||||
";
|
";
|
||||||
if ($splashImage) {
|
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";
|
$conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,7 +330,7 @@ else {
|
|||||||
";
|
";
|
||||||
|
|
||||||
if ($font) {
|
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 .= "
|
$conf .= "
|
||||||
insmod font
|
insmod font
|
||||||
if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
|
if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
|
||||||
@ -347,7 +358,7 @@ else {
|
|||||||
background_color '$backgroundColor'
|
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 .= "
|
$conf .= "
|
||||||
insmod " . substr($suffix, 1) . "
|
insmod " . substr($suffix, 1) . "
|
||||||
if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
|
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.
|
# kernels or initrd if this script is ever interrupted.
|
||||||
if (! -e $dst) {
|
if (! -e $dst) {
|
||||||
my $tmp = "$dst.tmp";
|
my $tmp = "$dst.tmp";
|
||||||
copy $path, $tmp or die "cannot copy $path to $tmp\n";
|
copy $path, $tmp or die "cannot copy $path to $tmp: $!\n";
|
||||||
rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
|
rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n";
|
||||||
}
|
}
|
||||||
$copied{$dst} = 1;
|
$copied{$dst} = 1;
|
||||||
return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
|
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)
|
# Make sure initrd is not world readable (won't work if /boot is FAT)
|
||||||
umask 0137;
|
umask 0137;
|
||||||
my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
|
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
|
# Check whether any secrets were actually added
|
||||||
if (-e $initrdSecretsPathTemp && ! -z _) {
|
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;
|
$copied{$initrdSecretsPath} = 1;
|
||||||
$initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
|
$initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets";
|
||||||
} else {
|
} else {
|
||||||
@ -575,7 +586,7 @@ if (get("useOSProber") eq "true") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Atomically switch to the new config
|
# 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.
|
# Remove obsolete files from $bootPath/kernels.
|
||||||
@ -596,9 +607,12 @@ struct(GrubState => {
|
|||||||
efi => '$',
|
efi => '$',
|
||||||
devices => '$',
|
devices => '$',
|
||||||
efiMountPoint => '$',
|
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 {
|
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;
|
open FILE, "<$bootPath/grub/state" or return $defaultGrubState;
|
||||||
local $/ = "\n";
|
local $/ = "\n";
|
||||||
my $name = <FILE>;
|
my $name = <FILE>;
|
||||||
@ -611,24 +625,34 @@ sub readGrubState {
|
|||||||
chomp($devices);
|
chomp($devices);
|
||||||
my $efiMountPoint = <FILE>;
|
my $efiMountPoint = <FILE>;
|
||||||
chomp($efiMountPoint);
|
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;
|
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
|
return $grubState
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getDeviceTargets {
|
my @deviceTargets = getList('devices');
|
||||||
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 $prevGrubState = readGrubState();
|
my $prevGrubState = readGrubState();
|
||||||
my @prevDeviceTargets = split/,/, $prevGrubState->devices;
|
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 $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 $nameDiffer = get("fullName") ne $prevGrubState->name;
|
||||||
my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
|
my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
|
||||||
my $efiDiffer = $efiTarget ne $prevGrubState->efi;
|
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";
|
warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
|
||||||
$ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
|
$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
|
# 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";
|
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";
|
symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
|
||||||
|
|
||||||
# install non-EFI GRUB
|
# install non-EFI GRUB
|
||||||
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
||||||
foreach my $dev (@deviceTargets) {
|
foreach my $dev (@deviceTargets) {
|
||||||
next if $dev eq "nodev";
|
next if $dev eq "nodev";
|
||||||
print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
|
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") {
|
if ($forceInstall eq "true") {
|
||||||
push @command, "--force";
|
push @command, "--force";
|
||||||
}
|
}
|
||||||
if ($grubTarget ne "") {
|
if ($grubTarget ne "") {
|
||||||
push @command, "--target=$grubTarget";
|
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
|
# install EFI GRUB
|
||||||
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
|
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
|
||||||
print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
|
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") {
|
if ($forceInstall eq "true") {
|
||||||
push @command, "--force";
|
push @command, "--force";
|
||||||
}
|
}
|
||||||
@ -674,17 +698,29 @@ if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both"))
|
|||||||
push @command, "--removable" if $efiInstallAsRemovable eq "true";
|
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
|
# update GRUB state file
|
||||||
if ($requireNewInstall != 0) {
|
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("fullName"), "\n" or die;
|
||||||
print FILE get("fullVersion"), "\n" or die;
|
print FILE get("fullVersion"), "\n" or die;
|
||||||
print FILE $efiTarget, "\n" or die;
|
print FILE $efiTarget, "\n" or die;
|
||||||
print FILE join( ",", @deviceTargets ), "\n" or die;
|
print FILE join( ",", @deviceTargets ), "\n" or die;
|
||||||
print FILE $efiSysMountPoint, "\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;
|
close FILE or die;
|
||||||
|
|
||||||
|
# Atomically switch to the new state file
|
||||||
|
rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user