Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4f832b5
Revert "grub: Allow setting the boot root explicitly"
wkennington May 2, 2014
f2bef62
Update grub 2.00 to 2.02-beta2
wkennington Oct 21, 2013
3c6e2fb
Add optional zfsSupport to the nixos grub configuration
wkennington Apr 9, 2014
02ab48d
Enable grub zfsSupport if zfs is built into the initrd
wkennington Apr 9, 2014
c5bdb46
Update the grub configuration script to handle more complex filesyste…
wkennington Oct 21, 2013
fba9f64
grub: Add support for forcing devices to be identified with labels or…
wkennington Apr 30, 2014
a6e6c85
grub: Add support for detecting btrfs subvolumes
wkennington Apr 30, 2014
70c1177
nixos/grub: Fix some silly perl struct accesses
wkennington Apr 30, 2014
525acb4
nixos/grub: Fix typo
wkennington Apr 30, 2014
1f460e0
grub: Build grub2 from git instead of using the unpredictable generat…
wkennington Apr 30, 2014
99b4792
nixos/grub: Refactor perl script to remove the Switch module
wkennington Apr 30, 2014
d4e2040
nixos/grub: Refactor install-grub.pl and correct perl syntax
wkennington May 1, 2014
769d2dc
nixos/grub: Catch errors from command execution
wkennington May 2, 2014
5870ae6
nixos/release: Dynamically generate installer tests
wkennington May 2, 2014
8329d12
grub: Change fsIdentifier to str from string
wkennington May 2, 2014
809caa8
tests/installer: Add btrfs tests for grub and nixos-generate-config
wkennington May 1, 2014
2b703f8
tests/installer: Test for more grub configurations
wkennington May 2, 2014
62fedf6
installer/btrfs: Typo in subvol
wkennington May 2, 2014
d4a9645
nixos/grub: Needs mount so add utillinux to bin
wkennington May 2, 2014
8b36bf5
tests/installer: fix mount
wkennington May 2, 2014
8ff4b3b
tests/installer: Add swap to the new tests
wkennington May 2, 2014
429f785
tests/installer: Fix simple tests
wkennington May 2, 2014
87d5e45
nixos/grub: Grub detection is much simpler using subvol show
wkennington May 2, 2014
3bf2267
nixos/grub: Kernels don't need to be copied if we can read the nix store
wkennington May 2, 2014
7264941
tests/installer: Remove unneeded tests
wkennington May 2, 2014
b651097
tests/installer: Swapspace should be larger
wkennington May 2, 2014
6549bcf
tests/installer: Fix provided test uuid and label mounts
wkennington May 2, 2014
c02bc3a
nixos/grub: Fix regex for getting subvolume name in btrfs
wkennington May 2, 2014
4f096c0
nixos/grub: Simplify detection of labels / uuids for provided device …
wkennington May 2, 2014
cc62623
tests/installer: Provided test should add symlinks to /dev/disk if ud…
wkennington May 2, 2014
0f6079d
nixos/grub: Fix spacing and correct subvolume detection
wkennington May 2, 2014
940c57e
nixos/ova: Grub uuid detection is broken when generating the ova
wkennington May 2, 2014
2ea1433
grub: Fetch from alpha.gnu.org instead of git
wkennington May 2, 2014
dd18e67
grub: Cleanup efi support
wkennington May 4, 2014
babcd70
nixos/release-combined: Add required installer tests
wkennington Jun 18, 2014
36a4773
nixos-generate-config: Detect btrfs subvolumes
wkennington Jun 18, 2014
cf7f7a5
nixos/install-grub: Check /boot against /nix/store instead of /nix
wkennington Jul 13, 2014
0fdbc44
grub: Fix typo
wkennington Aug 28, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions nixos/modules/installer/tools/nixos-generate-config.pl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ sub uniq {
return @res;
}

sub runCommand {
my ($cmd) = @_;
open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
my @ret = <FILE>;
close FILE;
return ($?, @ret);
}

# Process the command line.
my $outDir = "/etc/nixos";
Expand Down Expand Up @@ -337,6 +344,20 @@ sub in {
}
}

# Is this a btrfs filesystem?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use tabs...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ($fsType eq "btrfs") {
my ($status, @info) = runCommand("btrfs subvol show $rootDir$mountPoint");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use the backtick operator to run btrfs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because btrfs doesn't actually return an error string in the cases I need to know it failed, therefore relying on the exit code of the application. In hindsight, I could have used ${^CHILD_ERROR_NATIVE} to grab the return code, I just don't really like the fact that I am retrieving a global variable for the return status.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was most likely because I didn't want to deal with parsing errors from text vs the real subvolume strings.

if ($status != 0) {
die "Failed to retreive subvolume info for $mountPoint";
}
my @subvols = join("", @info) =~ m/Name:[ \t\n]*([^ \t\n]*)/;
if ($#subvols > 0) {
die "Btrfs subvol name for $mountPoint listed multiple times in mount\n"
} elsif ($#subvols == 0) {
push @extraOptions, "subvol=$subvols[0]";
}
}

# Emit the filesystem.
$fileSystems .= <<EOF;
fileSystems.\"$mountPoint\" =
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/installer/tools/tools.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let
nixos-generate-config = makeProg {
name = "nixos-generate-config";
src = ./nixos-generate-config.pl;
path = [ pkgs.btrfsProgs ];
perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl";
};

Expand Down
3 changes: 3 additions & 0 deletions nixos/modules/installer/virtualbox-demo.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ with lib;
../profiles/clone-config.nix
];

# FIXME: UUID detection is currently broken
boot.loader.grub.fsIdentifier = "provided";

# Allow mounting of shared folders.
users.extraUsers.demo.extraGroups = [ "vboxsf" ];

Expand Down
37 changes: 28 additions & 9 deletions nixos/modules/system/boot/loader/grub/grub.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ let

cfg = config.boot.loader.grub;

realGrub = if cfg.version == 1 then pkgs.grub else pkgs.grub2;
realGrub = if cfg.version == 1 then pkgs.grub
else pkgs.grub2.override { zfsSupport = cfg.zfsSupport; };

grub =
# Don't include GRUB if we're only generating a GRUB menu (e.g.,
Expand All @@ -25,11 +26,12 @@ let
inherit (cfg)
version extraConfig extraPerEntryConfig extraEntries
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels timeout
default devices explicitBootRoot;
default devices fsIdentifier;
path = (makeSearchPath "bin" [
pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils
pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils pkgs.btrfsProgs
pkgs.utillinux
]) + ":" + (makeSearchPath "sbin" [
pkgs.mdadm
pkgs.mdadm pkgs.utillinux
]);
});

Expand Down Expand Up @@ -209,12 +211,26 @@ in
'';
};

explicitBootRoot = mkOption {
default = "";
type = types.str;
fsIdentifier = mkOption {
default = "uuid";
type = types.addCheck types.str
(type: type == "uuid" || type == "label" || type == "provided");
description = ''
The relative path of /boot within the parent volume. Leave empty
if /boot is not a btrfs subvolume.
Determines how grub will identify devices when generating the
configuration file. A value of uuid / label signifies that grub
will always resolve the uuid or label of the device before using
it in the configuration. A value of provided means that grub will
use the device name as show in <command>df</command> or
<command>mount</command>. Note, zfs zpools / datasets are ignored
and will always be mounted using their labels.
'';
};

zfsSupport = mkOption {
default = false;
type = types.bool;
description = ''
Whether grub should be build against libzfs.
'';
};

Expand Down Expand Up @@ -260,6 +276,9 @@ in
${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}"
'') config.boot.loader.grub.extraFiles);

assertions = [{ assertion = !cfg.zfsSupport || cfg.version == 2;
message = "Only grub version 2 provides zfs support";}];

})

];
Expand Down
134 changes: 116 additions & 18 deletions nixos/modules/system/boot/loader/grub/install-grub.pl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use strict;
use warnings;
use Class::Struct;
use XML::LibXML;
use File::Basename;
use File::Path;
Expand Down Expand Up @@ -27,6 +28,14 @@ sub writeFile {
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 $extraConfig = get("extraConfig");
Expand All @@ -39,7 +48,7 @@ sub writeFile {
my $copyKernels = get("copyKernels") eq "true";
my $timeout = int(get("timeout"));
my $defaultEntry = int(get("default"));
my $explicitBootRoot = get("explicitBootRoot");
my $fsIdentifier = get("fsIdentifier");
$ENV{'PATH'} = get("path");

die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
Expand All @@ -48,24 +57,109 @@ sub writeFile {

mkpath("/boot/grub", 0, 0700);


# Discover whether /boot is on the same filesystem as / and
# /nix/store. If not, then all kernels and initrds must be copied to
# /boot, and all paths in the GRUB config file must be relative to the
# root of the /boot filesystem. `$bootRoot' is the path to be
# prepended to paths under /boot.
my $bootRoot = "/boot";
if (stat("/")->dev != stat("/boot")->dev) {
$bootRoot = "";
$copyKernels = 1;
} elsif (stat("/boot")->dev != stat("/nix/store")->dev) {
# /boot.
if (stat("/boot")->dev != stat("/nix/store")->dev) {
$copyKernels = 1;
}

if ($explicitBootRoot ne "") {
$bootRoot = $explicitBootRoot;
# Discover information about the location of /boot
struct(Fs => {
device => '$',
type => '$',
mount => '$',
});
sub GetFs {
my ($dir) = @_;
my ($status, @dfOut) = runCommand("df -T $dir");
if ($status != 0 || $#dfOut != 1) {
die "Failed to retrieve output about $dir from `df`";
}
my @boot = split(/[ \n\t]+/, $dfOut[1]);
return Fs->new(device => $boot[0], type => $boot[1], mount => $boot[6]);
}
struct (Grub => {
path => '$',
search => '$',
});
my $driveid = 1;
sub GrubFs {
my ($dir) = @_;
my $fs = GetFs($dir);
my $path = "/" . substr($dir, length($fs->mount));
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("blkid -o export @{[$fs->device]}");
if ($status != 0) {
die "Failed to get blkid info for @{[$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, @info) = runCommand("btrfs subvol show @{[$fs->mount]}");
if ($status != 0) {
die "Failed to retreive subvolume info for @{[$fs->mount]}";
}
my @subvols = join("", @info) =~ m/Name:[ \t\n]*([^ \t\n]*)/;
if ($#subvols > 0) {
die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
} elsif ($#subvols == 0) {
$path = "/$subvols[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("/boot");
my $grubStore = GrubFs("/nix");

# We don't need to copy if we can read the kernels directly
if ($grubStore->search ne "") {
$copyKernels = 0;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that is not necessarily true. Searching may fail if it's encrypted, for example!


# Generate the header.
my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n";
Expand All @@ -77,12 +171,14 @@ sub writeFile {
";
if ($splashImage) {
copy $splashImage, "/boot/background.xpm.gz" or die "cannot copy $splashImage to /boot\n";
$conf .= "splashimage $bootRoot/background.xpm.gz\n";
$conf .= "splashimage " . $grubBoot->path . "/background.xpm.gz\n";
}
}

else {
$conf .= "
" . $grubBoot->search . "
" . $grubStore->search . "
if [ -s \$prefix/grubenv ]; then
load_env
fi
Expand All @@ -103,7 +199,7 @@ sub writeFile {
set timeout=$timeout
fi

if loadfont $bootRoot/grub/fonts/unicode.pf2; then
if loadfont " . $grubBoot->path . "/grub/fonts/unicode.pf2; then
set gfxmode=640x480
insmod gfxterm
insmod vbe
Expand All @@ -117,7 +213,7 @@ sub writeFile {
copy $splashImage, "/boot/background.png" or die "cannot copy $splashImage to /boot\n";
$conf .= "
insmod png
if background_image $bootRoot/background.png; then
if background_image " . $grubBoot->path . "/background.png; then
set color_normal=white/black
set color_highlight=black/white
else
Expand All @@ -139,7 +235,7 @@ sub writeFile {

sub copyToKernelsDir {
my ($path) = @_;
return $path unless $copyKernels;
return $grubStore->path . substr($path, length("/nix")) unless $copyKernels;
$path =~ /\/nix\/store\/(.*)/ or die;
my $name = $1; $name =~ s/\//-/g;
my $dst = "/boot/kernels/$name";
Expand All @@ -152,7 +248,7 @@ sub copyToKernelsDir {
rename $tmp, $dst or die "cannot rename $tmp to $dst\n";
}
$copied{$dst} = 1;
return "$bootRoot/kernels/$name";
return $grubBoot->path . "/kernels/$name";
}

sub addEntry {
Expand All @@ -179,6 +275,8 @@ sub addEntry {
$conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n\n";
} else {
$conf .= "menuentry \"$name\" {\n";
$conf .= $grubBoot->search . "\n";
$conf .= $grubStore->search . "\n";
$conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig;
$conf .= " multiboot $xen $xenParams\n" if $xen;
$conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
Expand All @@ -196,7 +294,7 @@ sub addEntry {
$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;

# extraEntries could refer to @bootRoot@, which we have to substitute
$conf =~ s/\@bootRoot\@/$bootRoot/g;
$conf =~ s/\@bootRoot\@/$grubBoot->path/g;

# Emit submenus for all system profiles.
sub addProfile {
Expand Down
6 changes: 5 additions & 1 deletion nixos/modules/tasks/filesystems/zfs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ in
};

boot.initrd = mkIf inInitrd {
kernelModules = [ "spl" "zfs" ] ;
kernelModules = [ "spl" "zfs" ];
extraUtilsCommands =
''
cp -v ${zfsPkg}/sbin/zfs $out/bin
Expand All @@ -148,6 +148,10 @@ in
'';
};

boot.loader.grub = mkIf inInitrd {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this only be needed if /boot itself is zfs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but when using zfs we cannot tell from the nix expression if /boot is a part of the / dataset or it's own dataset. It was just easier to assume that if they were intending to boot from a root zpool, grub would likely need zfs support. Using grub2 with compiled in zfs support adds no extra dependencies if you already have zfs installed on your machine.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the overhead of zfs support in grub? Can we enable it unconditionally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty small as it just needs libzfs. I wouldn't mind enabling this by default but I'll leave it as a configurable option. The biggest issue I suppose is that any breakage to the zfs package (incompatible kernel api) and then you break the ability to build grub. This probably isn't a big deal but something to keep in mind.

zfsSupport = true;
};

systemd.services."zpool-import" = {
description = "Import zpools";
after = [ "systemd-udev-settle.service" ];
Expand Down
4 changes: 4 additions & 0 deletions nixos/release-combined.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ in rec {
(all nixos.tests.installer.lvm)
(all nixos.tests.installer.separateBoot)
(all nixos.tests.installer.simple)
(all nixos.tests.installer.simpleLabels)
(all nixos.tests.installer.simpleProvided)
(all nixos.tests.installer.btrfsSimple)
(all nixos.tests.installer.btrfsSubvols)
(all nixos.tests.ipv6)
(all nixos.tests.kde4)
(all nixos.tests.login)
Expand Down
11 changes: 5 additions & 6 deletions nixos/release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,11 @@ in rec {
tests.firefox = callTest tests/firefox.nix {};
tests.firewall = callTest tests/firewall.nix {};
tests.gnome3 = callTest tests/gnome3.nix {};
tests.installer.efi = forAllSystems (system: (import tests/installer.nix { inherit system; }).efi.test);
tests.installer.grub1 = forAllSystems (system: (import tests/installer.nix { inherit system; }).grub1.test);
tests.installer.lvm = forAllSystems (system: (import tests/installer.nix { inherit system; }).lvm.test);
tests.installer.rebuildCD = forAllSystems (system: (import tests/installer.nix { inherit system; }).rebuildCD.test);
tests.installer.separateBoot = forAllSystems (system: (import tests/installer.nix { inherit system; }).separateBoot.test);
tests.installer.simple = forAllSystems (system: (import tests/installer.nix { inherit system; }).simple.test);
tests.installer = with pkgs.lib;
let installer = import tests/installer.nix; in
flip mapAttrs (installer { }) (name: _:
forAllSystems (system: (installer { system = system; }).${name}.test)
);
tests.influxdb = callTest tests/influxdb.nix {};
tests.ipv6 = callTest tests/ipv6.nix {};
tests.jenkins = callTest tests/jenkins.nix {};
Expand Down
Loading