2012-07-24 19:16:27 -04:00
|
|
|
|
use strict;
|
|
|
|
|
use warnings;
|
2014-08-31 09:18:13 -07:00
|
|
|
|
use Class::Struct;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
use XML::LibXML;
|
|
|
|
|
use File::Basename;
|
2023-03-27 20:08:22 +02:00
|
|
|
|
use File::Path qw(make_path);
|
2012-07-24 19:16:27 -04:00
|
|
|
|
use File::stat;
|
|
|
|
|
use File::Copy;
|
2020-07-05 05:16:25 +02:00
|
|
|
|
use File::Copy::Recursive qw(rcopy pathrm);
|
2014-09-03 14:40:27 -07:00
|
|
|
|
use File::Slurp;
|
2015-07-05 18:34:45 -07:00
|
|
|
|
use File::Temp;
|
2020-04-23 22:44:21 +02:00
|
|
|
|
use JSON;
|
2020-07-05 05:16:25 +02:00
|
|
|
|
use File::Find;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
require List::Compare;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
use POSIX;
|
|
|
|
|
use Cwd;
|
|
|
|
|
|
2016-09-01 10:13:47 +02:00
|
|
|
|
# system.build.toplevel path
|
2015-05-29 12:01:50 -07:00
|
|
|
|
my $defaultConfig = $ARGV[1] or die;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2016-09-01 10:13:47 +02:00
|
|
|
|
# Grub config XML generated by grubConfig function in grub.nix
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
|
|
|
|
|
|
|
|
|
|
sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
|
|
|
|
|
|
2020-04-23 22:42:11 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-25 10:47:32 -04:00
|
|
|
|
sub readFile {
|
2023-03-27 19:19:41 +02:00
|
|
|
|
my ($fn) = @_;
|
|
|
|
|
# enable slurp mode: read entire file in one go
|
|
|
|
|
local $/ = undef;
|
2023-03-27 20:09:26 +02:00
|
|
|
|
open my $fh, "<", $fn
|
|
|
|
|
or return;
|
2023-03-27 19:19:41 +02:00
|
|
|
|
my $s = <$fh>;
|
|
|
|
|
close $fh;
|
|
|
|
|
# disable slurp mode
|
|
|
|
|
local $/ = "\n";
|
|
|
|
|
chomp $s;
|
|
|
|
|
return $s;
|
2012-07-25 10:47:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub writeFile {
|
|
|
|
|
my ($fn, $s) = @_;
|
2023-03-27 20:09:26 +02:00
|
|
|
|
open my $fh, ">", $fn or die "cannot create $fn: $!\n";
|
2023-03-27 19:19:41 +02:00
|
|
|
|
print $fh $s or die "cannot write to $fn: $!\n";
|
|
|
|
|
close $fh or die "cannot close $fn: $!\n";
|
2012-07-25 10:47:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2014-08-31 09:18:13 -07:00
|
|
|
|
sub runCommand {
|
2023-03-27 19:18:00 +02:00
|
|
|
|
open(my $fh, "-|", @_) or die "Failed to execute: $@_\n";
|
|
|
|
|
my @ret = $fh->getlines();
|
|
|
|
|
close $fh;
|
2014-08-31 09:18:13 -07:00
|
|
|
|
return ($?, @ret);
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $grub = get("grub");
|
2015-01-14 10:30:57 +01:00
|
|
|
|
my $grubTarget = get("grubTarget");
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $extraConfig = get("extraConfig");
|
2012-12-16 21:41:47 +01:00
|
|
|
|
my $extraPrepareConfig = get("extraPrepareConfig");
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $extraPerEntryConfig = get("extraPerEntryConfig");
|
|
|
|
|
my $extraEntries = get("extraEntries");
|
|
|
|
|
my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true";
|
|
|
|
|
my $splashImage = get("splashImage");
|
2018-08-28 23:53:10 -04:00
|
|
|
|
my $splashMode = get("splashMode");
|
2022-03-31 06:39:16 -04:00
|
|
|
|
my $entryOptions = get("entryOptions");
|
|
|
|
|
my $subEntryOptions = get("subEntryOptions");
|
2018-08-28 23:53:10 -04:00
|
|
|
|
my $backgroundColor = get("backgroundColor");
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $configurationLimit = int(get("configurationLimit"));
|
|
|
|
|
my $copyKernels = get("copyKernels") eq "true";
|
|
|
|
|
my $timeout = int(get("timeout"));
|
2023-03-06 23:59:39 -05:00
|
|
|
|
my $timeoutStyle = get("timeoutStyle");
|
2018-06-18 23:54:45 -05:00
|
|
|
|
my $defaultEntry = get("default");
|
2014-08-31 09:18:13 -07:00
|
|
|
|
my $fsIdentifier = get("fsIdentifier");
|
2015-01-14 10:30:57 +01:00
|
|
|
|
my $grubEfi = get("grubEfi");
|
|
|
|
|
my $grubTargetEfi = get("grubTargetEfi");
|
2015-05-25 14:57:20 -07:00
|
|
|
|
my $bootPath = get("bootPath");
|
2015-06-13 15:00:43 +02:00
|
|
|
|
my $storePath = get("storePath");
|
2015-01-14 10:30:57 +01:00
|
|
|
|
my $canTouchEfiVariables = get("canTouchEfiVariables");
|
2016-09-13 18:46:53 +01:00
|
|
|
|
my $efiInstallAsRemovable = get("efiInstallAsRemovable");
|
2015-01-14 10:30:57 +01:00
|
|
|
|
my $efiSysMountPoint = get("efiSysMountPoint");
|
2015-06-10 11:50:21 -07:00
|
|
|
|
my $gfxmodeEfi = get("gfxmodeEfi");
|
|
|
|
|
my $gfxmodeBios = get("gfxmodeBios");
|
2019-03-21 10:00:39 +00:00
|
|
|
|
my $gfxpayloadEfi = get("gfxpayloadEfi");
|
|
|
|
|
my $gfxpayloadBios = get("gfxpayloadBios");
|
2015-06-10 15:47:08 -07:00
|
|
|
|
my $bootloaderId = get("bootloaderId");
|
2016-10-08 23:59:42 -04:00
|
|
|
|
my $forceInstall = get("forceInstall");
|
2017-06-10 09:53:24 -04:00
|
|
|
|
my $font = get("font");
|
2020-07-05 05:16:25 +02:00
|
|
|
|
my $theme = get("theme");
|
2021-01-13 18:55:30 +01:00
|
|
|
|
my $saveDefault = $defaultEntry eq "saved";
|
2012-11-15 22:54:43 +01:00
|
|
|
|
$ENV{'PATH'} = get("path");
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
print STDERR "updating GRUB 2 menu...\n";
|
2012-07-24 19:27:16 -04:00
|
|
|
|
|
2023-03-27 20:08:22 +02:00
|
|
|
|
make_path("$bootPath/grub", { mode => 0700 });
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2015-05-25 14:57:20 -07:00
|
|
|
|
# Discover whether the bootPath is on the same filesystem as / and
|
2012-07-24 19:16:27 -04:00
|
|
|
|
# /nix/store. If not, then all kernels and initrds must be copied to
|
2015-05-25 14:57:20 -07:00
|
|
|
|
# the bootPath.
|
|
|
|
|
if (stat($bootPath)->dev != stat("/nix/store")->dev) {
|
2012-07-24 19:16:27 -04:00
|
|
|
|
$copyKernels = 1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-25 14:57:20 -07:00
|
|
|
|
# Discover information about the location of the bootPath
|
2014-08-31 09:18:13 -07:00
|
|
|
|
struct(Fs => {
|
2021-02-21 09:09:28 +01:00
|
|
|
|
device => '$',
|
|
|
|
|
type => '$',
|
|
|
|
|
mount => '$',
|
|
|
|
|
});
|
2014-09-03 14:40:27 -07:00
|
|
|
|
sub PathInMount {
|
|
|
|
|
my ($path, $mount) = @_;
|
|
|
|
|
my @splitMount = split /\//, $mount;
|
|
|
|
|
my @splitPath = split /\//, $path;
|
|
|
|
|
if ($#splitPath < $#splitMount) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
for (my $i = 0; $i <= $#splitMount; $i++) {
|
|
|
|
|
if ($splitMount[$i] ne $splitPath[$i]) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2016-09-01 10:13:47 +02:00
|
|
|
|
|
|
|
|
|
# Figure out what filesystem is used for the directory with init/initrd/kernel files
|
2014-08-31 09:18:13 -07:00
|
|
|
|
sub GetFs {
|
|
|
|
|
my ($dir) = @_;
|
2014-09-03 14:40:27 -07:00
|
|
|
|
my $bestFs = Fs->new(device => "", type => "", mount => "");
|
|
|
|
|
foreach my $fs (read_file("/proc/self/mountinfo")) {
|
|
|
|
|
chomp $fs;
|
|
|
|
|
my @fields = split / /, $fs;
|
|
|
|
|
my $mountPoint = $fields[4];
|
|
|
|
|
my @mountOptions = split /,/, $fields[5];
|
|
|
|
|
|
|
|
|
|
# Skip the optional fields.
|
|
|
|
|
my $n = 6; $n++ while $fields[$n] ne "-"; $n++;
|
|
|
|
|
my $fsType = $fields[$n];
|
|
|
|
|
my $device = $fields[$n + 1];
|
|
|
|
|
my @superOptions = split /,/, $fields[$n + 2];
|
|
|
|
|
|
2017-07-13 23:43:48 +02:00
|
|
|
|
# Skip the bind-mount on /nix/store.
|
|
|
|
|
next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions);
|
2015-01-14 10:30:57 +01:00
|
|
|
|
# Skip mount point generated by systemd-efi-boot-generator?
|
|
|
|
|
next if $fsType eq "autofs";
|
2014-09-03 14:40:27 -07:00
|
|
|
|
|
|
|
|
|
# Ensure this matches the intended directory
|
|
|
|
|
next unless PathInMount($dir, $mountPoint);
|
|
|
|
|
|
|
|
|
|
# Is it better than our current match?
|
|
|
|
|
if (length($mountPoint) > length($bestFs->mount)) {
|
2023-04-23 04:29:58 +00:00
|
|
|
|
|
|
|
|
|
# -d performs a stat, which can hang forever on network file systems,
|
|
|
|
|
# so we only make this call last, when it's likely that this is the mount point we need.
|
|
|
|
|
next unless -d $mountPoint;
|
|
|
|
|
|
2014-09-03 14:40:27 -07:00
|
|
|
|
$bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint);
|
|
|
|
|
}
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
2014-09-03 14:40:27 -07:00
|
|
|
|
return $bestFs;
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
|
|
|
|
struct (Grub => {
|
2021-02-21 09:09:28 +01:00
|
|
|
|
path => '$',
|
|
|
|
|
search => '$',
|
|
|
|
|
});
|
2014-08-31 09:18:13 -07:00
|
|
|
|
my $driveid = 1;
|
|
|
|
|
sub GrubFs {
|
|
|
|
|
my ($dir) = @_;
|
|
|
|
|
my $fs = GetFs($dir);
|
2016-09-01 10:14:23 +02:00
|
|
|
|
my $path = substr($dir, length($fs->mount));
|
|
|
|
|
if (substr($path, 0, 1) ne "/") {
|
2021-02-21 09:24:23 +01:00
|
|
|
|
$path = "/$path";
|
2016-09-01 10:14:23 +02:00
|
|
|
|
}
|
2014-08-31 09:18:13 -07:00
|
|
|
|
my $search = "";
|
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
# ZFS is completely separate logic as zpools are always identified by a label
|
|
|
|
|
# or custom UUID
|
|
|
|
|
if ($fs->type eq 'zfs') {
|
|
|
|
|
my $sid = index($fs->device, '/');
|
|
|
|
|
|
|
|
|
|
if ($sid < 0) {
|
|
|
|
|
$search = '--label ' . $fs->device;
|
|
|
|
|
$path = '/@' . $path;
|
|
|
|
|
} else {
|
|
|
|
|
$search = '--label ' . substr($fs->device, 0, $sid);
|
|
|
|
|
$path = '/' . substr($fs->device, $sid) . '/@' . $path;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
|
|
|
|
|
|
|
|
|
|
if ($fsIdentifier eq 'provided') {
|
|
|
|
|
# If the provided dev is identifying the partition using a label or uuid,
|
|
|
|
|
# we should get the label / uuid and do a proper search
|
|
|
|
|
my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
|
|
|
|
|
if ($#matches > 1) {
|
|
|
|
|
die "Too many matched devices"
|
|
|
|
|
} elsif ($#matches == 1) {
|
|
|
|
|
$search = "$types{$matches[0]} $matches[1]"
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2022-04-04 17:54:14 +01:00
|
|
|
|
# Determine the identifying type
|
|
|
|
|
$search = $types{$fsIdentifier} . ' ';
|
2014-08-31 09:18:13 -07:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
# Based on the type pull in the identifier from the system
|
|
|
|
|
my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid", "-o", "export", @{[$fs->device]});
|
|
|
|
|
if ($status != 0) {
|
|
|
|
|
die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
|
|
|
|
|
}
|
|
|
|
|
my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
|
|
|
|
|
if ($#matches != 0) {
|
|
|
|
|
die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$search .= $matches[0];
|
|
|
|
|
}
|
2014-08-31 09:18:13 -07:00
|
|
|
|
|
2023-05-19 22:11:38 -04:00
|
|
|
|
# BTRFS is a special case in that we need to fix the referenced path based on subvolumes
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if ($fs->type eq 'btrfs') {
|
|
|
|
|
my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "show", @{[$fs->mount]});
|
|
|
|
|
if ($status != 0) {
|
|
|
|
|
die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
|
|
|
|
|
}
|
|
|
|
|
my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
|
|
|
|
|
if ($#ids > 0) {
|
|
|
|
|
die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
|
|
|
|
|
} elsif ($#ids == 0) {
|
|
|
|
|
my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "list", @{[$fs->mount]});
|
2014-08-31 09:18:13 -07:00
|
|
|
|
if ($status != 0) {
|
2022-04-04 17:54:14 +01:00
|
|
|
|
die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
|
|
|
|
|
if ($#paths > 0) {
|
|
|
|
|
die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
|
|
|
|
|
} elsif ($#paths != 0) {
|
|
|
|
|
die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$path = "/$paths[0]$path";
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
}
|
|
|
|
|
if (not $search eq "") {
|
|
|
|
|
$search = "search --set=drive$driveid " . $search;
|
|
|
|
|
$path = "(\$drive$driveid)$path";
|
|
|
|
|
$driveid += 1;
|
2014-08-31 09:18:13 -07:00
|
|
|
|
}
|
|
|
|
|
return Grub->new(path => $path, search => $search);
|
|
|
|
|
}
|
2015-05-25 14:57:20 -07:00
|
|
|
|
my $grubBoot = GrubFs($bootPath);
|
2014-09-23 14:34:44 +02:00
|
|
|
|
my $grubStore;
|
|
|
|
|
if ($copyKernels == 0) {
|
2015-06-13 15:00:43 +02:00
|
|
|
|
$grubStore = GrubFs($storePath);
|
2014-09-23 14:34:44 +02:00
|
|
|
|
}
|
2014-08-31 09:18:13 -07:00
|
|
|
|
|
2012-07-24 19:16:27 -04:00
|
|
|
|
# Generate the header.
|
|
|
|
|
my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n";
|
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
my @users = ();
|
|
|
|
|
foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
|
|
|
|
|
my $name = $user->findvalue('@name') or die;
|
|
|
|
|
my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
|
|
|
|
|
my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
|
|
|
|
|
my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
|
|
|
|
|
my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
|
|
|
|
|
|
|
|
|
|
if ($hashedPasswordFile) {
|
|
|
|
|
open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
|
|
|
|
|
$hashedPassword = <$f>;
|
|
|
|
|
chomp $hashedPassword;
|
|
|
|
|
}
|
|
|
|
|
if ($passwordFile) {
|
|
|
|
|
open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
|
|
|
|
|
$password = <$f>;
|
|
|
|
|
chomp $password;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
2019-07-21 16:39:07 +00:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if ($hashedPassword) {
|
|
|
|
|
if (index($hashedPassword, "grub.pbkdf2.") == 0) {
|
|
|
|
|
$conf .= "\npassword_pbkdf2 $name $hashedPassword";
|
2019-07-21 16:39:07 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
2022-04-04 17:54:14 +01:00
|
|
|
|
die "Password hash for GRUB user '$name' is not valid!";
|
2019-07-21 16:39:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
elsif ($password) {
|
|
|
|
|
$conf .= "\npassword $name $password";
|
2014-09-01 00:01:41 -07:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
else {
|
|
|
|
|
die "GRUB user '$name' has no password!";
|
2021-01-13 18:55:30 +01:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
push(@users, $name);
|
|
|
|
|
}
|
|
|
|
|
if (@users) {
|
|
|
|
|
$conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
|
|
|
|
|
}
|
2021-02-21 09:09:28 +01:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if ($copyKernels == 0) {
|
|
|
|
|
$conf .= "
|
|
|
|
|
" . $grubStore->search;
|
|
|
|
|
}
|
|
|
|
|
# FIXME: should use grub-mkconfig.
|
|
|
|
|
my $defaultEntryText = $defaultEntry;
|
|
|
|
|
if ($saveDefault) {
|
|
|
|
|
$defaultEntryText = "\"\${saved_entry}\"";
|
|
|
|
|
}
|
|
|
|
|
$conf .= "
|
|
|
|
|
" . $grubBoot->search . "
|
|
|
|
|
if [ -s \$prefix/grubenv ]; then
|
|
|
|
|
load_env
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ‘grub-reboot’ sets a one-time saved entry, which we process here and
|
|
|
|
|
# then delete.
|
|
|
|
|
if [ \"\${next_entry}\" ]; then
|
|
|
|
|
set default=\"\${next_entry}\"
|
|
|
|
|
set next_entry=
|
|
|
|
|
save_env next_entry
|
|
|
|
|
set timeout=1
|
|
|
|
|
set boot_once=true
|
|
|
|
|
else
|
|
|
|
|
set default=$defaultEntryText
|
|
|
|
|
set timeout=$timeout
|
|
|
|
|
fi
|
2023-03-06 23:59:39 -05:00
|
|
|
|
set timeout_style=$timeoutStyle
|
2022-04-04 17:54:14 +01:00
|
|
|
|
|
|
|
|
|
function savedefault {
|
|
|
|
|
if [ -z \"\${boot_once}\"]; then
|
|
|
|
|
saved_entry=\"\${chosen}\"
|
|
|
|
|
save_env saved_entry
|
2021-01-13 18:55:30 +01:00
|
|
|
|
fi
|
2022-04-04 17:54:14 +01:00
|
|
|
|
}
|
2021-01-13 18:55:30 +01:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
# Setup the graphics stack for bios and efi systems
|
|
|
|
|
if [ \"\${grub_platform}\" = \"efi\" ]; then
|
|
|
|
|
insmod efi_gop
|
|
|
|
|
insmod efi_uga
|
|
|
|
|
else
|
|
|
|
|
insmod vbe
|
|
|
|
|
fi
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
if ($font) {
|
|
|
|
|
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
|
|
|
|
|
insmod gfxterm
|
|
|
|
|
if [ \"\${grub_platform}\" = \"efi\" ]; then
|
|
|
|
|
set gfxmode=$gfxmodeEfi
|
|
|
|
|
set gfxpayload=$gfxpayloadEfi
|
|
|
|
|
else
|
|
|
|
|
set gfxmode=$gfxmodeBios
|
|
|
|
|
set gfxpayload=$gfxpayloadBios
|
|
|
|
|
fi
|
|
|
|
|
terminal_output gfxterm
|
2021-02-21 09:09:28 +01:00
|
|
|
|
fi
|
2012-07-24 19:16:27 -04:00
|
|
|
|
";
|
2022-04-04 17:54:14 +01:00
|
|
|
|
}
|
|
|
|
|
if ($splashImage) {
|
|
|
|
|
# Keeps the image's extension.
|
|
|
|
|
my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
|
|
|
|
|
# The module for jpg is jpeg.
|
|
|
|
|
if ($suffix eq ".jpg") {
|
|
|
|
|
$suffix = ".jpeg";
|
2017-06-10 09:53:24 -04:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if ($backgroundColor) {
|
2012-07-24 19:16:27 -04:00
|
|
|
|
$conf .= "
|
2022-04-04 17:54:14 +01:00
|
|
|
|
background_color '$backgroundColor'
|
2012-07-24 19:16:27 -04:00
|
|
|
|
";
|
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
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
|
|
|
|
|
set color_normal=white/black
|
|
|
|
|
set color_highlight=black/white
|
|
|
|
|
else
|
|
|
|
|
set menu_color_normal=cyan/blue
|
|
|
|
|
set menu_color_highlight=white/blue
|
|
|
|
|
fi
|
|
|
|
|
";
|
|
|
|
|
}
|
2020-07-05 05:16:25 +02:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
|
2020-07-05 05:16:25 +02:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if ($theme) {
|
|
|
|
|
# Copy theme
|
|
|
|
|
rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
|
2023-03-08 14:24:37 -05:00
|
|
|
|
|
|
|
|
|
# Detect which modules will need to be loaded
|
|
|
|
|
my $with_png = 0;
|
|
|
|
|
my $with_jpeg = 0;
|
|
|
|
|
|
|
|
|
|
find({ wanted => sub {
|
|
|
|
|
if ($_ =~ /\.png$/i) {
|
|
|
|
|
$with_png = 1;
|
|
|
|
|
}
|
|
|
|
|
elsif ($_ =~ /\.jpe?g$/i) {
|
|
|
|
|
$with_jpeg = 1;
|
|
|
|
|
}
|
|
|
|
|
}, no_chdir => 1 }, $theme);
|
|
|
|
|
|
|
|
|
|
if ($with_png) {
|
|
|
|
|
$conf .= "
|
|
|
|
|
insmod png
|
|
|
|
|
"
|
|
|
|
|
}
|
|
|
|
|
if ($with_jpeg) {
|
|
|
|
|
$conf .= "
|
|
|
|
|
insmod jpeg
|
|
|
|
|
"
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$conf .= "
|
|
|
|
|
# Sets theme.
|
|
|
|
|
set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
|
|
|
|
|
export theme
|
|
|
|
|
# Load theme fonts, if any
|
|
|
|
|
";
|
2021-02-21 09:24:23 +01:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
find( { wanted => sub {
|
|
|
|
|
if ($_ =~ /\.pf2$/i) {
|
|
|
|
|
$font = File::Spec->abs2rel($File::Find::name, $theme);
|
|
|
|
|
$conf .= "
|
|
|
|
|
loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
|
|
|
|
|
";
|
|
|
|
|
}
|
|
|
|
|
}, no_chdir => 1 }, $theme );
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$conf .= "$extraConfig\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Generate the menu entries.
|
|
|
|
|
$conf .= "\n";
|
|
|
|
|
|
|
|
|
|
my %copied;
|
2023-03-27 20:08:22 +02:00
|
|
|
|
make_path("$bootPath/kernels", { mode => 0755 }) if $copyKernels;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
|
|
|
|
sub copyToKernelsDir {
|
|
|
|
|
my ($path) = @_;
|
2014-09-03 09:29:22 -07:00
|
|
|
|
return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
$path =~ /\/nix\/store\/(.*)/ or die;
|
|
|
|
|
my $name = $1; $name =~ s/\//-/g;
|
2015-05-25 14:57:20 -07:00
|
|
|
|
my $dst = "$bootPath/kernels/$name";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
# Don't copy the file if $dst already exists. This means that we
|
|
|
|
|
# have to create $dst atomically to prevent partially copied
|
|
|
|
|
# kernels or initrd if this script is ever interrupted.
|
|
|
|
|
if (! -e $dst) {
|
|
|
|
|
my $tmp = "$dst.tmp";
|
2020-07-02 22:18:49 +02:00
|
|
|
|
copy $path, $tmp or die "cannot copy $path to $tmp: $!\n";
|
|
|
|
|
rename $tmp, $dst or die "cannot rename $tmp to $dst: $!\n";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
|
|
|
|
$copied{$dst} = 1;
|
2018-09-02 12:34:55 +00:00
|
|
|
|
return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub addEntry {
|
2023-01-15 02:31:15 +01:00
|
|
|
|
my ($name, $path, $options, $current) = @_;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
return unless -e "$path/kernel" && -e "$path/initrd";
|
|
|
|
|
|
2016-02-12 13:14:34 +01:00
|
|
|
|
my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
|
|
|
|
|
my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
|
2018-03-27 19:57:52 -04:00
|
|
|
|
|
|
|
|
|
# Include second initrd with secrets
|
|
|
|
|
if (-e -x "$path/append-initrd-secrets") {
|
nixos/grub: Name initrd-secrets by system, not by initrd
Previously, secrets were named according to the initrd they were
associated with. This created a problem: If secrets were changed whilst
the initrd remained the same, there were two versions of the secrets
with one initrd. The result was that only one version of the secrets would
by recorded into the /boot partition and get used. AFAICT this would
only be the oldest version of the secrets for the given initrd version.
This manifests as #114594, which I found frustrating while trying to use
initrd secrets for the first time. While developing the secrets I found
I could not get new versions of the secrets to take effect.
Additionally, it's a nasty issue to run into if you had cause to change
the initrd secrets for credential rotation, etc, if you change them and
discover you cannot, or alternatively that you can't roll back as you
would expect.
Additional changes in this patch.
* Add a regression test that switching to another grub configuration
with the alternate secrets works. This test relies on the fact that it
is not changing the initrd. I have checked that the test fails if I
undo my change.
* Persist the useBootLoader disk state, similarly to other boot state.
* I had to do this, otherwise I could not find a route to testing the
alternate boot configuration. I did attempt a few different ways of
testing this, including directly running install-grub.pl, but what
I've settled on is most like what a user would do and avoids
depending on lots of internal details.
* Making tests that test the boot are a bit tricky (see hibernate.nix
and installer.nix for inspiration), I found that in addition to
having to copy quite a bit of code I still couldn't get things to
work as desired since the bootloader state was being clobbered.
My change to persist the useBootLoader state could break things,
conceptually. I need some help here discovering if that is the case,
possibly by letting this run through a staging CI if there is one.
Fix #114594.
cc potential reviewers:
@lopsided98 (original implementer) @joachifm (original reviewer),
@wkennington (numerous fixes to grub-install.pl), @lheckemann (wrote
original secrets test).
2023-01-05 12:28:32 +00:00
|
|
|
|
# Name the initrd secrets after the system from which they're derived.
|
|
|
|
|
my $systemName = basename(Cwd::abs_path("$path"));
|
|
|
|
|
my $initrdSecretsPath = "$bootPath/kernels/$systemName-secrets";
|
2021-02-21 09:24:23 +01:00
|
|
|
|
|
2023-03-27 20:08:22 +02:00
|
|
|
|
make_path(dirname($initrdSecretsPath), { mode => 0755 });
|
2021-02-21 09:24:23 +01:00
|
|
|
|
my $oldUmask = umask;
|
|
|
|
|
# Make sure initrd is not world readable (won't work if /boot is FAT)
|
|
|
|
|
umask 0137;
|
|
|
|
|
my $initrdSecretsPathTemp = File::Temp::mktemp("$initrdSecretsPath.XXXXXXXX");
|
2023-01-15 02:31:15 +01:00
|
|
|
|
if (system("$path/append-initrd-secrets", $initrdSecretsPathTemp) != 0) {
|
|
|
|
|
if ($current) {
|
|
|
|
|
die "failed to create initrd secrets $!\n";
|
|
|
|
|
} else {
|
|
|
|
|
say STDERR "warning: failed to create initrd secrets for \"$name\", an older generation";
|
|
|
|
|
say STDERR "note: this is normal after having removed or renamed a file in `boot.initrd.secrets`";
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-21 09:24:23 +01:00
|
|
|
|
# Check whether any secrets were actually added
|
|
|
|
|
if (-e $initrdSecretsPathTemp && ! -z _) {
|
|
|
|
|
rename $initrdSecretsPathTemp, $initrdSecretsPath or die "failed to move initrd secrets into place: $!\n";
|
|
|
|
|
$copied{$initrdSecretsPath} = 1;
|
nixos/grub: Name initrd-secrets by system, not by initrd
Previously, secrets were named according to the initrd they were
associated with. This created a problem: If secrets were changed whilst
the initrd remained the same, there were two versions of the secrets
with one initrd. The result was that only one version of the secrets would
by recorded into the /boot partition and get used. AFAICT this would
only be the oldest version of the secrets for the given initrd version.
This manifests as #114594, which I found frustrating while trying to use
initrd secrets for the first time. While developing the secrets I found
I could not get new versions of the secrets to take effect.
Additionally, it's a nasty issue to run into if you had cause to change
the initrd secrets for credential rotation, etc, if you change them and
discover you cannot, or alternatively that you can't roll back as you
would expect.
Additional changes in this patch.
* Add a regression test that switching to another grub configuration
with the alternate secrets works. This test relies on the fact that it
is not changing the initrd. I have checked that the test fails if I
undo my change.
* Persist the useBootLoader disk state, similarly to other boot state.
* I had to do this, otherwise I could not find a route to testing the
alternate boot configuration. I did attempt a few different ways of
testing this, including directly running install-grub.pl, but what
I've settled on is most like what a user would do and avoids
depending on lots of internal details.
* Making tests that test the boot are a bit tricky (see hibernate.nix
and installer.nix for inspiration), I found that in addition to
having to copy quite a bit of code I still couldn't get things to
work as desired since the bootloader state was being clobbered.
My change to persist the useBootLoader state could break things,
conceptually. I need some help here discovering if that is the case,
possibly by letting this run through a staging CI if there is one.
Fix #114594.
cc potential reviewers:
@lopsided98 (original implementer) @joachifm (original reviewer),
@wkennington (numerous fixes to grub-install.pl), @lheckemann (wrote
original secrets test).
2023-01-05 12:28:32 +00:00
|
|
|
|
$initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$systemName-secrets";
|
2021-02-21 09:24:23 +01:00
|
|
|
|
} else {
|
|
|
|
|
unlink $initrdSecretsPathTemp;
|
|
|
|
|
rmdir dirname($initrdSecretsPathTemp);
|
|
|
|
|
}
|
|
|
|
|
umask $oldUmask;
|
2017-02-18 23:30:24 +01:00
|
|
|
|
}
|
2018-03-27 19:57:52 -04:00
|
|
|
|
|
2016-02-12 13:14:34 +01:00
|
|
|
|
my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
|
|
|
|
# FIXME: $confName
|
|
|
|
|
|
|
|
|
|
my $kernelParams =
|
2021-02-21 09:09:28 +01:00
|
|
|
|
"init=" . Cwd::abs_path("$path/init") . " " .
|
|
|
|
|
readFile("$path/kernel-params");
|
2012-07-25 10:47:32 -04:00
|
|
|
|
my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$conf .= "menuentry \"$name\" " . $options . " {\n";
|
|
|
|
|
if ($saveDefault) {
|
|
|
|
|
$conf .= " savedefault\n";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$conf .= $grubBoot->search . "\n";
|
|
|
|
|
if ($copyKernels == 0) {
|
|
|
|
|
$conf .= $grubStore->search . "\n";
|
|
|
|
|
}
|
|
|
|
|
$conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig;
|
|
|
|
|
$conf .= " multiboot $xen $xenParams\n" if $xen;
|
|
|
|
|
$conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
|
|
|
|
|
$conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n";
|
|
|
|
|
$conf .= "}\n\n";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
sub addGeneration {
|
|
|
|
|
my ($name, $nameSuffix, $path, $options, $current) = @_;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
# Do not search for grand children
|
|
|
|
|
my @links = sort (glob "$path/specialisation/*");
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
if ($current != 1 && scalar(@links) != 0) {
|
2024-01-29 19:09:57 +08:00
|
|
|
|
$conf .= "submenu \"$name$nameSuffix\" --class submenu {\n";
|
2023-07-27 15:29:57 +02:00
|
|
|
|
}
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
addEntry("$name" . (scalar(@links) == 0 ? "" : " - Default") . $nameSuffix, $path, $options, $current);
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
# Find all the children of the current default configuration
|
|
|
|
|
# Do not search for grand children
|
|
|
|
|
foreach my $link (@links) {
|
2018-08-19 23:17:21 +05:30
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
my $entryName = "";
|
2018-08-19 23:17:21 +05:30
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
my $cfgName = readFile("$link/configuration-name");
|
2018-08-19 23:17:21 +05:30
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
my $date = strftime("%F", localtime(lstat($link)->mtime));
|
|
|
|
|
my $version =
|
|
|
|
|
-e "$link/nixos-version"
|
|
|
|
|
? readFile("$link/nixos-version")
|
|
|
|
|
: basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
|
2018-08-19 23:17:21 +05:30
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
if ($cfgName) {
|
|
|
|
|
$entryName = $cfgName;
|
|
|
|
|
} else {
|
|
|
|
|
my $linkname = basename($link);
|
|
|
|
|
$entryName = "($linkname - $date - $version)";
|
|
|
|
|
}
|
|
|
|
|
addEntry("$name - $entryName", $link, "", 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($current != 1 && scalar(@links) != 0) {
|
|
|
|
|
$conf .= "}\n";
|
2018-08-19 23:17:21 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 15:29:57 +02:00
|
|
|
|
# Add default entries.
|
|
|
|
|
$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS;
|
|
|
|
|
|
|
|
|
|
addGeneration("@distroName@", "", $defaultConfig, $entryOptions, 1);
|
|
|
|
|
|
|
|
|
|
$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
|
|
|
|
|
|
2014-12-02 11:46:45 -05:00
|
|
|
|
my $grubBootPath = $grubBoot->path;
|
2012-12-16 21:41:47 +01:00
|
|
|
|
# extraEntries could refer to @bootRoot@, which we have to substitute
|
2014-12-02 11:46:45 -05:00
|
|
|
|
$conf =~ s/\@bootRoot\@/$grubBootPath/g;
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2013-10-09 19:13:26 +02:00
|
|
|
|
# Emit submenus for all system profiles.
|
|
|
|
|
sub addProfile {
|
|
|
|
|
my ($profile, $description) = @_;
|
|
|
|
|
|
|
|
|
|
# Add entries for all generations of this profile.
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$conf .= "submenu \"$description\" --class submenu {\n";
|
2013-10-09 19:13:26 +02:00
|
|
|
|
|
|
|
|
|
sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
|
|
|
|
|
|
|
|
|
|
my @links = sort
|
2021-02-21 09:09:28 +01:00
|
|
|
|
{ nrFromGen($b) <=> nrFromGen($a) }
|
|
|
|
|
(glob "$profile-*-link");
|
2013-10-09 19:13:26 +02:00
|
|
|
|
|
|
|
|
|
my $curEntry = 0;
|
|
|
|
|
foreach my $link (@links) {
|
|
|
|
|
last if $curEntry++ >= $configurationLimit;
|
2015-12-30 12:53:36 +01:00
|
|
|
|
if (! -e "$link/nixos-version") {
|
|
|
|
|
warn "skipping corrupt system profile entry ‘$link’\n";
|
|
|
|
|
next;
|
|
|
|
|
}
|
2013-10-09 19:13:26 +02:00
|
|
|
|
my $date = strftime("%F", localtime(lstat($link)->mtime));
|
|
|
|
|
my $version =
|
2021-02-21 09:09:28 +01:00
|
|
|
|
-e "$link/nixos-version"
|
|
|
|
|
? readFile("$link/nixos-version")
|
|
|
|
|
: basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
|
2023-07-27 15:29:57 +02:00
|
|
|
|
addGeneration("@distroName@ - Configuration " . nrFromGen($link), " ($date - $version)", $link, $subEntryOptions, 0);
|
2013-10-09 19:13:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
$conf .= "}\n";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-17 18:00:58 -05:00
|
|
|
|
addProfile "/nix/var/nix/profiles/system", "@distroName@ - All configurations";
|
2013-10-09 19:13:26 +02:00
|
|
|
|
|
2022-04-04 17:54:14 +01:00
|
|
|
|
for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
|
|
|
|
|
my $name = basename($profile);
|
|
|
|
|
next unless $name =~ /^\w+$/;
|
|
|
|
|
addProfile $profile, "@distroName@ - Profile '$name'";
|
2013-10-09 19:13:26 +02:00
|
|
|
|
}
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2020-01-12 19:39:03 -08:00
|
|
|
|
# extraPrepareConfig could refer to @bootPath@, which we have to substitute
|
|
|
|
|
$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g;
|
|
|
|
|
|
2012-12-16 21:41:47 +01:00
|
|
|
|
# Run extraPrepareConfig in sh
|
|
|
|
|
if ($extraPrepareConfig ne "") {
|
2021-02-21 09:24:23 +01:00
|
|
|
|
system((get("shell"), "-c", $extraPrepareConfig));
|
2012-12-16 21:41:47 +01:00
|
|
|
|
}
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
2017-02-13 14:53:15 +01:00
|
|
|
|
# write the GRUB config.
|
2022-04-04 17:54:14 +01:00
|
|
|
|
my $confFile = "$bootPath/grub/grub.cfg";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
my $tmpFile = $confFile . ".tmp";
|
2012-07-25 10:47:32 -04:00
|
|
|
|
writeFile($tmpFile, $conf);
|
2017-02-13 14:53:15 +01:00
|
|
|
|
|
2017-03-22 15:40:22 +01:00
|
|
|
|
|
|
|
|
|
# check whether to install GRUB EFI or not
|
|
|
|
|
sub getEfiTarget {
|
2022-04-04 17:54:14 +01:00
|
|
|
|
if (($grub ne "") && ($grubEfi ne "")) {
|
2017-03-22 15:40:22 +01:00
|
|
|
|
# EFI can only be installed when target is set;
|
|
|
|
|
# A target is also required then for non-EFI grub
|
|
|
|
|
if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
|
|
|
|
|
else { return "both" }
|
|
|
|
|
} elsif (($grub ne "") && ($grubEfi eq "")) {
|
2023-05-19 22:11:38 -04:00
|
|
|
|
# TODO: It would be safer to disallow non-EFI grub installation if no target is given.
|
2017-03-22 15:40:22 +01:00
|
|
|
|
# If no target is given, then grub auto-detects the target which can lead to errors.
|
|
|
|
|
# E.g. it seems as if grub would auto-detect a EFI target based on the availability
|
|
|
|
|
# of a EFI partition.
|
|
|
|
|
# However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386
|
|
|
|
|
# architectures in NixOS. That would have to be fixed in the nixos modules first.
|
|
|
|
|
return "no"
|
|
|
|
|
} elsif (($grub eq "") && ($grubEfi ne "")) {
|
|
|
|
|
# EFI can only be installed when target is set;
|
|
|
|
|
if ($grubTargetEfi eq "") { die }
|
|
|
|
|
else {return "only" }
|
|
|
|
|
} else {
|
|
|
|
|
# prevent an installation if neither grub nor grubEfi is given
|
|
|
|
|
return "neither"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $efiTarget = getEfiTarget();
|
|
|
|
|
|
2017-02-13 14:53:15 +01:00
|
|
|
|
# Append entries detected by os-prober
|
2017-02-13 14:54:30 +01:00
|
|
|
|
if (get("useOSProber") eq "true") {
|
2021-01-13 18:55:30 +01:00
|
|
|
|
if ($saveDefault) {
|
|
|
|
|
# os-prober will read this to determine if "savedefault" should be added to generated entries
|
|
|
|
|
$ENV{'GRUB_SAVEDEFAULT'} = "true";
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-22 15:40:22 +01:00
|
|
|
|
my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi;
|
|
|
|
|
system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile");
|
2017-02-13 14:53:15 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Atomically switch to the new config
|
2020-07-02 22:18:49 +02:00
|
|
|
|
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n";
|
2012-07-24 19:16:27 -04:00
|
|
|
|
|
|
|
|
|
|
2015-05-25 14:57:20 -07:00
|
|
|
|
# Remove obsolete files from $bootPath/kernels.
|
|
|
|
|
foreach my $fn (glob "$bootPath/kernels/*") {
|
2012-07-24 19:16:27 -04:00
|
|
|
|
next if defined $copied{$fn};
|
|
|
|
|
print STDERR "removing obsolete file $fn\n";
|
|
|
|
|
unlink $fn;
|
|
|
|
|
}
|
2012-07-25 10:47:32 -04:00
|
|
|
|
|
|
|
|
|
|
2015-01-14 10:30:57 +01:00
|
|
|
|
#
|
|
|
|
|
# Install GRUB if the parameters changed from the last time we installed it.
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
struct(GrubState => {
|
2021-02-21 09:09:28 +01:00
|
|
|
|
name => '$',
|
|
|
|
|
version => '$',
|
|
|
|
|
efi => '$',
|
|
|
|
|
devices => '$',
|
|
|
|
|
efiMountPoint => '$',
|
|
|
|
|
extraGrubInstallArgs => '@',
|
|
|
|
|
});
|
2020-04-23 22:44:21 +02:00
|
|
|
|
# If you add something to the state file, only add it to the end
|
|
|
|
|
# because it is read line-by-line.
|
2015-01-14 10:30:57 +01:00
|
|
|
|
sub readGrubState {
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "", extraGrubInstallArgs => () );
|
2023-03-27 20:09:26 +02:00
|
|
|
|
open my $fh, "<", "$bootPath/grub/state" or return $defaultGrubState;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
local $/ = "\n";
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $name = <$fh>;
|
2015-07-05 18:54:35 +02:00
|
|
|
|
chomp($name);
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $version = <$fh>;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
chomp($version);
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $efi = <$fh>;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
chomp($efi);
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $devices = <$fh>;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
chomp($devices);
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $efiMountPoint = <$fh>;
|
2015-01-14 10:30:57 +01:00
|
|
|
|
chomp($efiMountPoint);
|
2020-04-23 22:44:21 +02:00
|
|
|
|
# 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.
|
2023-03-27 20:09:26 +02:00
|
|
|
|
my $jsonStateLine = <$fh>;
|
2020-04-23 22:44:21 +02:00
|
|
|
|
# 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);
|
2020-07-07 00:53:55 +02:00
|
|
|
|
if ($jsonStateLine eq "") {
|
|
|
|
|
$jsonStateLine = '{}'; # empty JSON object
|
|
|
|
|
}
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my %jsonState = %{decode_json($jsonStateLine)};
|
2020-07-07 00:53:55 +02:00
|
|
|
|
my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : ();
|
2023-03-27 20:09:26 +02:00
|
|
|
|
close $fh;
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs );
|
2015-01-14 10:30:57 +01:00
|
|
|
|
return $grubState
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-23 22:42:11 +02:00
|
|
|
|
my @deviceTargets = getList('devices');
|
2015-01-14 10:30:57 +01:00
|
|
|
|
my $prevGrubState = readGrubState();
|
2016-07-05 23:40:35 +02:00
|
|
|
|
my @prevDeviceTargets = split/,/, $prevGrubState->devices;
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my @extraGrubInstallArgs = getList('extraGrubInstallArgs');
|
2020-07-13 02:16:43 +02:00
|
|
|
|
my @prevExtraGrubInstallArgs = @{$prevGrubState->extraGrubInstallArgs};
|
2015-01-14 10:30:57 +01:00
|
|
|
|
|
2015-07-05 20:54:36 +02:00
|
|
|
|
my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference());
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my $extraGrubInstallArgsDiffer = scalar (List::Compare->new( '-u', '-a', \@extraGrubInstallArgs, \@prevExtraGrubInstallArgs)->get_symmetric_difference());
|
2015-07-05 20:54:36 +02:00
|
|
|
|
my $nameDiffer = get("fullName") ne $prevGrubState->name;
|
|
|
|
|
my $versionDiffer = get("fullVersion") ne $prevGrubState->version;
|
|
|
|
|
my $efiDiffer = $efiTarget ne $prevGrubState->efi;
|
|
|
|
|
my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint;
|
2016-08-16 07:51:58 -04:00
|
|
|
|
if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") {
|
|
|
|
|
warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER";
|
|
|
|
|
$ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1";
|
|
|
|
|
}
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my $requireNewInstall = $devicesDiffer || $extraGrubInstallArgsDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1");
|
2015-01-14 10:30:57 +01:00
|
|
|
|
|
2015-07-05 18:34:45 -07:00
|
|
|
|
# install a symlink so that grub can detect the boot drive
|
2020-07-02 22:18:49 +02:00
|
|
|
|
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: $!";
|
2015-01-14 10:30:57 +01:00
|
|
|
|
|
|
|
|
|
# install non-EFI GRUB
|
|
|
|
|
if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
|
|
|
|
|
foreach my $dev (@deviceTargets) {
|
2012-07-25 10:47:32 -04:00
|
|
|
|
next if $dev eq "nodev";
|
2022-04-04 17:54:14 +01:00
|
|
|
|
print STDERR "installing the GRUB 2 boot loader on $dev...\n";
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
|
2016-10-08 23:59:42 -04:00
|
|
|
|
if ($forceInstall eq "true") {
|
|
|
|
|
push @command, "--force";
|
|
|
|
|
}
|
|
|
|
|
if ($grubTarget ne "") {
|
|
|
|
|
push @command, "--target=$grubTarget";
|
2015-01-14 10:30:57 +01:00
|
|
|
|
}
|
2020-07-02 22:18:49 +02:00
|
|
|
|
(system @command) == 0 or die "$0: installation of GRUB on $dev failed: $!\n";
|
2012-07-25 10:47:32 -04:00
|
|
|
|
}
|
2015-01-14 10:30:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# install EFI GRUB
|
|
|
|
|
if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
|
2022-04-04 17:54:14 +01:00
|
|
|
|
print STDERR "installing the GRUB 2 boot loader into $efiSysMountPoint...\n";
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
|
2016-10-08 23:59:42 -04:00
|
|
|
|
if ($forceInstall eq "true") {
|
|
|
|
|
push @command, "--force";
|
|
|
|
|
}
|
2023-03-08 02:18:37 -05:00
|
|
|
|
push @command, "--bootloader-id=$bootloaderId";
|
|
|
|
|
if ($canTouchEfiVariables ne "true") {
|
2016-09-13 18:46:53 +01:00
|
|
|
|
push @command, "--no-nvram";
|
|
|
|
|
push @command, "--removable" if $efiInstallAsRemovable eq "true";
|
2015-01-14 10:30:57 +01:00
|
|
|
|
}
|
2016-09-13 18:46:53 +01:00
|
|
|
|
|
2020-07-02 22:18:49 +02:00
|
|
|
|
(system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n";
|
2015-01-14 10:30:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# update GRUB state file
|
|
|
|
|
if ($requireNewInstall != 0) {
|
2020-04-24 00:20:56 +02:00
|
|
|
|
# Temp file for atomic rename.
|
|
|
|
|
my $stateFile = "$bootPath/grub/state";
|
|
|
|
|
my $stateFileTmp = $stateFile . ".tmp";
|
|
|
|
|
|
2023-03-27 20:09:26 +02:00
|
|
|
|
open my $fh, ">", "$stateFileTmp" or die "cannot create $stateFileTmp: $!\n";
|
|
|
|
|
print $fh get("fullName"), "\n" or die;
|
|
|
|
|
print $fh get("fullVersion"), "\n" or die;
|
|
|
|
|
print $fh $efiTarget, "\n" or die;
|
|
|
|
|
print $fh join( ",", @deviceTargets ), "\n" or die;
|
|
|
|
|
print $fh $efiSysMountPoint, "\n" or die;
|
2020-04-23 22:44:21 +02:00
|
|
|
|
my %jsonState = (
|
|
|
|
|
extraGrubInstallArgs => \@extraGrubInstallArgs
|
|
|
|
|
);
|
|
|
|
|
my $jsonStateLine = encode_json(\%jsonState);
|
2023-03-27 20:09:26 +02:00
|
|
|
|
print $fh $jsonStateLine, "\n" or die;
|
|
|
|
|
close $fh or die;
|
2020-04-24 00:20:56 +02:00
|
|
|
|
|
|
|
|
|
# Atomically switch to the new state file
|
2020-07-02 22:18:49 +02:00
|
|
|
|
rename $stateFileTmp, $stateFile or die "cannot rename $stateFileTmp to $stateFile: $!\n";
|
2012-07-25 10:47:32 -04:00
|
|
|
|
}
|