diff options
Diffstat (limited to 'nixos/modules/system/boot/loader/grub/install-grub.pl')
-rw-r--r-- | nixos/modules/system/boot/loader/grub/install-grub.pl | 780 |
1 files changed, 0 insertions, 780 deletions
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl deleted file mode 100644 index 0c93b288fc6..00000000000 --- a/nixos/modules/system/boot/loader/grub/install-grub.pl +++ /dev/null @@ -1,780 +0,0 @@ -use strict; -use warnings; -use Class::Struct; -use XML::LibXML; -use File::Basename; -use File::Path; -use File::stat; -use File::Copy; -use File::Copy::Recursive qw(rcopy pathrm); -use File::Slurp; -use File::Temp; -use JSON; -use File::Find; -require List::Compare; -use POSIX; -use Cwd; - -# system.build.toplevel path -my $defaultConfig = $ARGV[1] or die; - -# Grub config XML generated by grubConfig function in grub.nix -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; - local $/ = "\n"; chomp $s; return $s; -} - -sub writeFile { - my ($fn, $s) = @_; - open FILE, ">$fn" or die "cannot create $fn: $!\n"; - print FILE $s or die; - close FILE or die; -} - -sub runCommand { - my ($cmd) = @_; - open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n"; - my @ret = <FILE>; - close FILE; - return ($?, @ret); -} - -my $grub = get("grub"); -my $grubVersion = int(get("version")); -my $grubTarget = get("grubTarget"); -my $extraConfig = get("extraConfig"); -my $extraPrepareConfig = get("extraPrepareConfig"); -my $extraPerEntryConfig = get("extraPerEntryConfig"); -my $extraEntries = get("extraEntries"); -my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true"; -my $splashImage = get("splashImage"); -my $splashMode = get("splashMode"); -my $backgroundColor = get("backgroundColor"); -my $configurationLimit = int(get("configurationLimit")); -my $copyKernels = get("copyKernels") eq "true"; -my $timeout = int(get("timeout")); -my $defaultEntry = get("default"); -my $fsIdentifier = get("fsIdentifier"); -my $grubEfi = get("grubEfi"); -my $grubTargetEfi = get("grubTargetEfi"); -my $bootPath = get("bootPath"); -my $storePath = get("storePath"); -my $canTouchEfiVariables = get("canTouchEfiVariables"); -my $efiInstallAsRemovable = get("efiInstallAsRemovable"); -my $efiSysMountPoint = get("efiSysMountPoint"); -my $gfxmodeEfi = get("gfxmodeEfi"); -my $gfxmodeBios = get("gfxmodeBios"); -my $gfxpayloadEfi = get("gfxpayloadEfi"); -my $gfxpayloadBios = get("gfxpayloadBios"); -my $bootloaderId = get("bootloaderId"); -my $forceInstall = get("forceInstall"); -my $font = get("font"); -my $theme = get("theme"); -my $saveDefault = $defaultEntry eq "saved"; -$ENV{'PATH'} = get("path"); - -die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2; - -print STDERR "updating GRUB $grubVersion menu...\n"; - -mkpath("$bootPath/grub", 0, 0700); - -# Discover whether the bootPath is on the same filesystem as / and -# /nix/store. If not, then all kernels and initrds must be copied to -# the bootPath. -if (stat($bootPath)->dev != stat("/nix/store")->dev) { - $copyKernels = 1; -} - -# Discover information about the location of the bootPath -struct(Fs => { - device => '$', - type => '$', - mount => '$', -}); -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; -} - -# Figure out what filesystem is used for the directory with init/initrd/kernel files -sub GetFs { - my ($dir) = @_; - 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]; - next unless -d $mountPoint; - 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]; - - # Skip the bind-mount on /nix/store. - next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions); - # Skip mount point generated by systemd-efi-boot-generator? - next if $fsType eq "autofs"; - - # Ensure this matches the intended directory - next unless PathInMount($dir, $mountPoint); - - # Is it better than our current match? - if (length($mountPoint) > length($bestFs->mount)) { - $bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint); - } - } - return $bestFs; -} -struct (Grub => { - path => '$', - search => '$', -}); -my $driveid = 1; -sub GrubFs { - my ($dir) = @_; - my $fs = GetFs($dir); - my $path = substr($dir, length($fs->mount)); - if (substr($path, 0, 1) ne "/") { - $path = "/$path"; - } - my $search = ""; - - if ($grubVersion > 1) { - # 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]" - } - } else { - # Determine the identifying type - $search = $types{$fsIdentifier} . ' '; - - # 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" - } - $search .= $matches[0]; - } - - # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes - 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]}"); - if ($status != 0) { - die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n"; - } - 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"; - } - $path = "/$paths[0]$path"; - } - } - } - if (not $search eq "") { - $search = "search --set=drive$driveid " . $search; - $path = "(\$drive$driveid)$path"; - $driveid += 1; - } - } - return Grub->new(path => $path, search => $search); -} -my $grubBoot = GrubFs($bootPath); -my $grubStore; -if ($copyKernels == 0) { - $grubStore = GrubFs($storePath); -} - -# Generate the header. -my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n"; - -if ($grubVersion == 1) { - # $defaultEntry might be "saved", indicating that we want to use the last selected configuration as default. - # Incidentally this is already the correct value for the grub 1 config to achieve this behaviour. - $conf .= " - default $defaultEntry - timeout $timeout - "; - if ($splashImage) { - 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"; - } -} - -else { - 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; - } - - if ($hashedPassword) { - if (index($hashedPassword, "grub.pbkdf2.") == 0) { - $conf .= "\npassword_pbkdf2 $name $hashedPassword"; - } - else { - die "Password hash for GRUB user '$name' is not valid!"; - } - } - elsif ($password) { - $conf .= "\npassword $name $password"; - } - else { - die "GRUB user '$name' has no password!"; - } - push(@users, $name); - } - if (@users) { - $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n"; - } - - 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 - - function savedefault { - if [ -z \"\${boot_once}\"]; then - saved_entry=\"\${chosen}\" - save_env saved_entry - fi - } - - # 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 - fi - "; - } - 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"; - } - if ($backgroundColor) { - $conf .= " - background_color '$backgroundColor' - "; - } - 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 - "; - } - - rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme"; - - if ($theme) { - # Copy theme - rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n"; - $conf .= " - # Sets theme. - set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt - export theme - # Load theme fonts, if any - "; - - 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 ); - } -} - -$conf .= "$extraConfig\n"; - - -# Generate the menu entries. -$conf .= "\n"; - -my %copied; -mkpath("$bootPath/kernels", 0, 0755) if $copyKernels; - -sub copyToKernelsDir { - my ($path) = @_; - return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels; - $path =~ /\/nix\/store\/(.*)/ or die; - my $name = $1; $name =~ s/\//-/g; - my $dst = "$bootPath/kernels/$name"; - # 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"; - 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"; -} - -sub addEntry { - my ($name, $path, $options) = @_; - return unless -e "$path/kernel" && -e "$path/initrd"; - - my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel")); - my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd")); - - # Include second initrd with secrets - if (-e -x "$path/append-initrd-secrets") { - my $initrdName = basename($initrd); - my $initrdSecretsPath = "$bootPath/kernels/$initrdName-secrets"; - - mkpath(dirname($initrdSecretsPath), 0, 0755); - 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"); - 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"; - $copied{$initrdSecretsPath} = 1; - $initrd .= " " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$initrdName-secrets"; - } else { - unlink $initrdSecretsPathTemp; - rmdir dirname($initrdSecretsPathTemp); - } - umask $oldUmask; - } - - my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef; - - # FIXME: $confName - - my $kernelParams = - "init=" . Cwd::abs_path("$path/init") . " " . - readFile("$path/kernel-params"); - my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : ""; - - if ($grubVersion == 1) { - $conf .= "title $name\n"; - $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; - $conf .= " kernel $xen $xenParams\n" if $xen; - $conf .= " " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n"; - $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n"; - if ($saveDefault) { - $conf .= " savedefault\n"; - } - $conf .= "\n"; - } else { - $conf .= "menuentry \"$name\" " . ($options||"") . " {\n"; - if ($saveDefault) { - $conf .= " savedefault\n"; - } - $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"; - } -} - - -# Add default entries. -$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS; - -addEntry("NixOS - Default", $defaultConfig, "--unrestricted"); - -$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; - -# Find all the children of the current default configuration -# Do not search for grand children -my @links = sort (glob "$defaultConfig/specialisation/*"); -foreach my $link (@links) { - - my $entryName = ""; - - my $cfgName = readFile("$link/configuration-name"); - - 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]); - - if ($cfgName) { - $entryName = $cfgName; - } else { - my $linkname = basename($link); - $entryName = "($linkname - $date - $version)"; - } - addEntry("NixOS - $entryName", $link); -} - -my $grubBootPath = $grubBoot->path; -# extraEntries could refer to @bootRoot@, which we have to substitute -$conf =~ s/\@bootRoot\@/$grubBootPath/g; - -# Emit submenus for all system profiles. -sub addProfile { - my ($profile, $description) = @_; - - # Add entries for all generations of this profile. - $conf .= "submenu \"$description\" {\n" if $grubVersion == 2; - - sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; } - - my @links = sort - { nrFromGen($b) <=> nrFromGen($a) } - (glob "$profile-*-link"); - - my $curEntry = 0; - foreach my $link (@links) { - last if $curEntry++ >= $configurationLimit; - if (! -e "$link/nixos-version") { - warn "skipping corrupt system profile entry ‘$link’\n"; - next; - } - 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]); - addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link); - } - - $conf .= "}\n" if $grubVersion == 2; -} - -addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations"; - -if ($grubVersion == 2) { - for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") { - my $name = basename($profile); - next unless $name =~ /^\w+$/; - addProfile $profile, "NixOS - Profile '$name'"; - } -} - -# extraPrepareConfig could refer to @bootPath@, which we have to substitute -$extraPrepareConfig =~ s/\@bootPath\@/$bootPath/g; - -# Run extraPrepareConfig in sh -if ($extraPrepareConfig ne "") { - system((get("shell"), "-c", $extraPrepareConfig)); -} - -# write the GRUB config. -my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg"; -my $tmpFile = $confFile . ".tmp"; -writeFile($tmpFile, $conf); - - -# check whether to install GRUB EFI or not -sub getEfiTarget { - if ($grubVersion == 1) { - return "no" - } elsif (($grub ne "") && ($grubEfi ne "")) { - # 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 "")) { - # TODO: It would be safer to disallow non-EFI grub installation if no taget is given. - # 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(); - -# Append entries detected by os-prober -if (get("useOSProber") eq "true") { - if ($saveDefault) { - # os-prober will read this to determine if "savedefault" should be added to generated entries - $ENV{'GRUB_SAVEDEFAULT'} = "true"; - } - - my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi; - system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile"); -} - -# Atomically switch to the new config -rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile: $!\n"; - - -# Remove obsolete files from $bootPath/kernels. -foreach my $fn (glob "$bootPath/kernels/*") { - next if defined $copied{$fn}; - print STDERR "removing obsolete file $fn\n"; - unlink $fn; -} - - -# -# Install GRUB if the parameters changed from the last time we installed it. -# - -struct(GrubState => { - name => '$', - version => '$', - 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 => "", extraGrubInstallArgs => () ); - open FILE, "<$bootPath/grub/state" or return $defaultGrubState; - local $/ = "\n"; - my $name = <FILE>; - chomp($name); - my $version = <FILE>; - chomp($version); - my $efi = <FILE>; - chomp($efi); - my $devices = <FILE>; - 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); - if ($jsonStateLine eq "") { - $jsonStateLine = '{}'; # empty JSON object - } - my %jsonState = %{decode_json($jsonStateLine)}; - my @extraGrubInstallArgs = exists($jsonState{'extraGrubInstallArgs'}) ? @{$jsonState{'extraGrubInstallArgs'}} : (); - close FILE; - my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint, extraGrubInstallArgs => \@extraGrubInstallArgs ); - return $grubState -} - -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; -my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint; -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 || $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: $!"; - -# 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), @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"; - } -} - - -# 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", @extraGrubInstallArgs); - if ($forceInstall eq "true") { - push @command, "--force"; - } - if ($canTouchEfiVariables eq "true") { - push @command, "--bootloader-id=$bootloaderId"; - } else { - push @command, "--no-nvram"; - push @command, "--removable" if $efiInstallAsRemovable eq "true"; - } - - (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed: $!\n"; -} - - -# update GRUB state file -if ($requireNewInstall != 0) { - # 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"; -} |