grub: Add boot.loader.grub.extraGrubInstallArgs
option.
Useful for when you need to build grub modules into your grub kernel to get a working boot, as shown in the added example. To store this new value, we switch to more structural JSON approach. Using one line per value to store in `/boot/grub/state` gets really messy when the values are arrays, or even worse, can contain newlines (escaping would be needed). Further, removing a value from the file would get extra messy (empty lines we'd have to keep for backwards compatibility). Thus, from now on we use JSON to store all values we'll need in the future.
This commit is contained in:
parent
81c15742ce
commit
8665b5ab91
@ -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;
|
||||||
@ -606,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>;
|
||||||
@ -621,16 +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
|
||||||
}
|
}
|
||||||
|
|
||||||
my @deviceTargets = getList('devices');
|
my @deviceTargets = getList('devices');
|
||||||
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;
|
||||||
@ -639,7 +661,7 @@ 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";
|
||||||
@ -650,7 +672,7 @@ 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";
|
||||||
}
|
}
|
||||||
@ -665,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";
|
||||||
}
|
}
|
||||||
@ -692,6 +714,11 @@ if ($requireNewInstall != 0) {
|
|||||||
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
|
# Atomically switch to the new state file
|
||||||
|
Loading…
x
Reference in New Issue
Block a user