Move all of NixOS to nixos/ in preparation of the repository merge
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# generate the script used to activate the configuration.
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
addAttributeName = mapAttrs (a: v: v // {
|
||||
text = ''
|
||||
#### Activation script snippet ${a}:
|
||||
${v.text}
|
||||
'';
|
||||
});
|
||||
|
||||
path =
|
||||
[ pkgs.coreutils pkgs.gnugrep pkgs.findutils
|
||||
pkgs.glibc # needed for getent
|
||||
pkgs.shadow
|
||||
pkgs.nettools # needed for hostname
|
||||
];
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
system.activationScripts = mkOption {
|
||||
default = {};
|
||||
|
||||
example = {
|
||||
stdio = {
|
||||
text = ''
|
||||
# Needed by some programs.
|
||||
ln -sfn /proc/self/fd /dev/fd
|
||||
ln -sfn /proc/self/fd/0 /dev/stdin
|
||||
ln -sfn /proc/self/fd/1 /dev/stdout
|
||||
ln -sfn /proc/self/fd/2 /dev/stderr
|
||||
'';
|
||||
deps = [];
|
||||
};
|
||||
};
|
||||
|
||||
description = ''
|
||||
Activate the new configuration (i.e., update /etc, make accounts,
|
||||
and so on).
|
||||
'';
|
||||
|
||||
merge = mergeTypedOption "script" builtins.isAttrs (fold mergeAttrs {});
|
||||
|
||||
apply = set: {
|
||||
script =
|
||||
''
|
||||
#! ${pkgs.stdenv.shell}
|
||||
|
||||
systemConfig=@out@
|
||||
|
||||
export PATH=/empty
|
||||
for i in ${toString path}; do
|
||||
PATH=$PATH:$i/bin:$i/sbin
|
||||
done
|
||||
|
||||
# Ensure a consistent umask.
|
||||
umask 0022
|
||||
|
||||
${
|
||||
let
|
||||
set' = mapAttrs (n: v: if builtins.isString v then noDepEntry v else v) set;
|
||||
withHeadlines = addAttributeName set';
|
||||
in textClosureMap id (withHeadlines) (attrNames withHeadlines)
|
||||
}
|
||||
|
||||
# Make this configuration the current configuration.
|
||||
# The readlink is there to ensure that when $systemConfig = /system
|
||||
# (which is a symlink to the store), /run/current-system is still
|
||||
# used as a garbage collection root.
|
||||
ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
|
||||
|
||||
# Prevent the current configuration from being garbage-collected.
|
||||
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
system.activationScripts.stdio =
|
||||
''
|
||||
# Needed by some programs.
|
||||
ln -sfn /proc/self/fd /dev/fd
|
||||
ln -sfn /proc/self/fd/0 /dev/stdin
|
||||
ln -sfn /proc/self/fd/1 /dev/stdout
|
||||
ln -sfn /proc/self/fd/2 /dev/stderr
|
||||
'';
|
||||
|
||||
system.activationScripts.var =
|
||||
''
|
||||
# Various log/runtime directories.
|
||||
|
||||
touch /var/run/utmp # must exist
|
||||
chgrp ${toString config.ids.gids.utmp} /var/run/utmp
|
||||
chmod 664 /var/run/utmp
|
||||
|
||||
mkdir -m 0755 -p /var/run/nix/current-load # for distributed builds
|
||||
mkdir -m 0700 -p /var/run/nix/remote-stores
|
||||
|
||||
# Directory holding symlinks to currently running Upstart
|
||||
# jobs. Used to determine which jobs need to be restarted
|
||||
# when switching to a new configuration.
|
||||
mkdir -m 0700 -p /var/run/upstart-jobs
|
||||
|
||||
mkdir -m 0755 -p /var/log
|
||||
|
||||
touch /var/log/wtmp # must exist
|
||||
chmod 644 /var/log/wtmp
|
||||
|
||||
touch /var/log/lastlog
|
||||
chmod 644 /var/log/lastlog
|
||||
|
||||
mkdir -m 1777 -p /var/tmp
|
||||
|
||||
# Empty, read-only home directory of many system accounts.
|
||||
mkdir -m 0555 -p /var/empty
|
||||
'';
|
||||
|
||||
system.activationScripts.media =
|
||||
''
|
||||
mkdir -m 0755 -p /media
|
||||
'';
|
||||
|
||||
system.activationScripts.usrbinenv =
|
||||
''
|
||||
mkdir -m 0755 -p /usr/bin
|
||||
ln -sfn ${pkgs.coreutils}/bin/env /usr/bin/.env.tmp
|
||||
mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env
|
||||
'';
|
||||
|
||||
system.activationScripts.tmpfs =
|
||||
''
|
||||
${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.devSize}" none /dev
|
||||
${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.devShmSize}" none /dev/shm
|
||||
${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.runSize}" none /run
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# This configuration is not made to figure inside the module-list.nix to
|
||||
# allow clone of the first level.
|
||||
{pkgs, ...}:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
{
|
||||
boot.loader.grub.device = mkOverrideTemplate 0 {} "nodev";
|
||||
nesting.children = mkOverrideTemplate 0 {} [];
|
||||
nesting.clone = mkOverrideTemplate 0 {} [];
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
#! @perl@
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Basename;
|
||||
use File::Slurp;
|
||||
use Sys::Syslog qw(:standard :macros);
|
||||
use Cwd 'abs_path';
|
||||
|
||||
my $out = "@out@";
|
||||
|
||||
my $startListFile = "/run/systemd/start-list";
|
||||
my $restartListFile = "/run/systemd/restart-list";
|
||||
my $reloadListFile = "/run/systemd/reload-list";
|
||||
|
||||
my $action = shift @ARGV;
|
||||
|
||||
if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test")) {
|
||||
print STDERR <<EOF;
|
||||
Usage: $0 [switch|boot|test]
|
||||
|
||||
switch: make the configuration the boot default and activate now
|
||||
boot: make the configuration the boot default
|
||||
test: activate the configuration, but don\'t make it the boot default
|
||||
EOF
|
||||
exit 1;
|
||||
}
|
||||
|
||||
die "This is not a NixOS installation (/etc/NIXOS is missing)!\n" unless -f "/etc/NIXOS";
|
||||
|
||||
openlog("nixos", "", LOG_USER);
|
||||
|
||||
# Install or update the bootloader.
|
||||
if ($action eq "switch" || $action eq "boot") {
|
||||
system("@installBootLoader@ $out") == 0 or exit 1;
|
||||
}
|
||||
|
||||
# Just in case the new configuration hangs the system, do a sync now.
|
||||
system("@coreutils@/bin/sync") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1";
|
||||
|
||||
exit 0 if $action eq "boot";
|
||||
|
||||
# Check if we can activate the new configuration.
|
||||
my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // "";
|
||||
my $newVersion = read_file("$out/init-interface-version");
|
||||
|
||||
if ($newVersion ne $oldVersion) {
|
||||
print STDERR <<EOF;
|
||||
Warning: the new NixOS configuration has an ‘init’ that is
|
||||
incompatible with the current configuration. The new configuration
|
||||
won\'t take effect until you reboot the system.
|
||||
EOF
|
||||
exit 100;
|
||||
}
|
||||
|
||||
syslog(LOG_NOTICE, "switching to system configuration $out");
|
||||
|
||||
# Ignore SIGHUP so that we're not killed if we're running on (say)
|
||||
# virtual console 1 and we restart the "tty1" unit.
|
||||
$SIG{PIPE} = "IGNORE";
|
||||
|
||||
sub getActiveUnits {
|
||||
# FIXME: use D-Bus or whatever to query this, since parsing the
|
||||
# output of list-units is likely to break.
|
||||
my $lines = `@systemd@/bin/systemctl list-units --full`;
|
||||
my $res = {};
|
||||
foreach my $line (split '\n', $lines) {
|
||||
chomp $line;
|
||||
last if $line eq "";
|
||||
$line =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s/ or next;
|
||||
next if $1 eq "UNIT";
|
||||
$res->{$1} = { load => $2, state => $3, substate => $4 };
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub parseFstab {
|
||||
my ($filename) = @_;
|
||||
my ($fss, $swaps);
|
||||
foreach my $line (read_file($filename, err_mode => 'quiet')) {
|
||||
chomp $line;
|
||||
$line =~ s/^\s*#.*//;
|
||||
next if $line =~ /^\s*$/;
|
||||
my @xs = split / /, $line;
|
||||
if ($xs[2] eq "swap") {
|
||||
$swaps->{$xs[0]} = { options => $xs[3] // "" };
|
||||
} else {
|
||||
$fss->{$xs[1]} = { device => $xs[0], fsType => $xs[2], options => $xs[3] // "" };
|
||||
}
|
||||
}
|
||||
return ($fss, $swaps);
|
||||
}
|
||||
|
||||
sub parseUnit {
|
||||
my ($filename) = @_;
|
||||
my $info = {};
|
||||
foreach my $line (read_file($filename)) {
|
||||
# FIXME: not quite correct.
|
||||
$line =~ /^([^=]+)=(.*)$/ or next;
|
||||
$info->{$1} = $2;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub boolIsTrue {
|
||||
my ($s) = @_;
|
||||
return $s eq "yes" || $s eq "true";
|
||||
}
|
||||
|
||||
# Stop all services that no longer exist or have changed in the new
|
||||
# configuration.
|
||||
my (@unitsToStop, @unitsToSkip);
|
||||
my $activePrev = getActiveUnits;
|
||||
while (my ($unit, $state) = each %{$activePrev}) {
|
||||
my $baseUnit = $unit;
|
||||
|
||||
# Recognise template instances.
|
||||
$baseUnit = "$1\@.$2" if $unit =~ /^(.*)@[^\.]*\.(.*)$/;
|
||||
my $prevUnitFile = "/etc/systemd/system/$baseUnit";
|
||||
my $newUnitFile = "$out/etc/systemd/system/$baseUnit";
|
||||
|
||||
my $baseName = $baseUnit;
|
||||
$baseName =~ s/\.[a-z]*$//;
|
||||
|
||||
if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
|
||||
if (! -e $newUnitFile) {
|
||||
push @unitsToStop, $unit;
|
||||
}
|
||||
|
||||
elsif ($unit =~ /\.target$/) {
|
||||
my $unitInfo = parseUnit($newUnitFile);
|
||||
|
||||
# Cause all active target units to be restarted below.
|
||||
# This should start most changed units we stop here as
|
||||
# well as any new dependencies (including new mounts and
|
||||
# swap devices). FIXME: the suspend target is sometimes
|
||||
# active after the system has resumed, which probably
|
||||
# should not be the case. Just ignore it.
|
||||
if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") {
|
||||
unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no")) {
|
||||
write_file($startListFile, { append => 1 }, "$unit\n");
|
||||
}
|
||||
}
|
||||
|
||||
# Stop targets that have X-StopOnReconfiguration set.
|
||||
# This is necessary to respect dependency orderings
|
||||
# involving targets: if unit X starts after target Y and
|
||||
# target Y starts after unit Z, then if X and Z have both
|
||||
# changed, then X should be restarted after Z. However,
|
||||
# if target Y is in the "active" state, X and Z will be
|
||||
# restarted at the same time because X's dependency on Y
|
||||
# is already satisfied. Thus, we need to stop Y first.
|
||||
# Stopping a target generally has no effect on other units
|
||||
# (unless there is a PartOf dependency), so this is just a
|
||||
# bookkeeping thing to get systemd to do the right thing.
|
||||
if (boolIsTrue($unitInfo->{'X-StopOnReconfiguration'} // "no")) {
|
||||
push @unitsToStop, $unit;
|
||||
}
|
||||
}
|
||||
|
||||
elsif (abs_path($prevUnitFile) ne abs_path($newUnitFile)) {
|
||||
if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") {
|
||||
# Do nothing. These cannot be restarted directly.
|
||||
} elsif ($unit =~ /\.mount$/) {
|
||||
# Reload the changed mount unit to force a remount.
|
||||
write_file($reloadListFile, { append => 1 }, "$unit\n");
|
||||
} elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/) {
|
||||
# FIXME: do something?
|
||||
} else {
|
||||
my $unitInfo = parseUnit($newUnitFile);
|
||||
if (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes")) {
|
||||
push @unitsToSkip, $unit;
|
||||
} else {
|
||||
# If this unit is socket-activated, then stop the
|
||||
# socket unit(s) as well, and restart the
|
||||
# socket(s) instead of the service.
|
||||
my $socketActivated = 0;
|
||||
if ($unit =~ /\.service$/) {
|
||||
my @sockets = split / /, ($unitInfo->{Sockets} // "");
|
||||
if (scalar @sockets == 0) {
|
||||
@sockets = ("$baseName.socket");
|
||||
}
|
||||
foreach my $socket (@sockets) {
|
||||
if (defined $activePrev->{$socket}) {
|
||||
push @unitsToStop, $socket;
|
||||
write_file($startListFile, { append => 1 }, "$socket\n");
|
||||
$socketActivated = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) {
|
||||
|
||||
# This unit should be restarted instead of
|
||||
# stopped and started.
|
||||
write_file($restartListFile, { append => 1 }, "$unit\n");
|
||||
|
||||
} else {
|
||||
|
||||
# If the unit is not socket-activated, record
|
||||
# that this unit needs to be started below.
|
||||
# We write this to a file to ensure that the
|
||||
# service gets restarted if we're interrupted.
|
||||
if (!$socketActivated) {
|
||||
write_file($startListFile, { append => 1 }, "$unit\n");
|
||||
}
|
||||
|
||||
push @unitsToStop, $unit;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub pathToUnitName {
|
||||
my ($path) = @_;
|
||||
die unless substr($path, 0, 1) eq "/";
|
||||
return "-" if $path eq "/";
|
||||
$path = substr($path, 1);
|
||||
$path =~ s/\//-/g;
|
||||
# FIXME: handle - and unprintable characters.
|
||||
return $path;
|
||||
}
|
||||
|
||||
sub unique {
|
||||
my %seen;
|
||||
my @res;
|
||||
foreach my $name (@_) {
|
||||
next if $seen{$name};
|
||||
$seen{$name} = 1;
|
||||
push @res, $name;
|
||||
}
|
||||
return @res;
|
||||
}
|
||||
|
||||
# Compare the previous and new fstab to figure out which filesystems
|
||||
# need a remount or need to be unmounted. New filesystems are mounted
|
||||
# automatically by starting local-fs.target. FIXME: might be nicer if
|
||||
# we generated units for all mounts; then we could unify this with the
|
||||
# unit checking code above.
|
||||
my ($prevFss, $prevSwaps) = parseFstab "/etc/fstab";
|
||||
my ($newFss, $newSwaps) = parseFstab "$out/etc/fstab";
|
||||
foreach my $mountPoint (keys %$prevFss) {
|
||||
my $prev = $prevFss->{$mountPoint};
|
||||
my $new = $newFss->{$mountPoint};
|
||||
my $unit = pathToUnitName($mountPoint) . ".mount";
|
||||
if (!defined $new) {
|
||||
# Filesystem entry disappeared, so unmount it.
|
||||
push @unitsToStop, $unit;
|
||||
} elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) {
|
||||
# Filesystem type or device changed, so unmount and mount it.
|
||||
write_file($startListFile, { append => 1 }, "$unit\n");
|
||||
push @unitsToStop, $unit;
|
||||
} elsif ($prev->{options} ne $new->{options}) {
|
||||
# Mount options changes, so remount it.
|
||||
write_file($reloadListFile, { append => 1 }, "$unit\n");
|
||||
}
|
||||
}
|
||||
|
||||
# Also handles swap devices.
|
||||
foreach my $device (keys %$prevSwaps) {
|
||||
my $prev = $prevSwaps->{$device};
|
||||
my $new = $newSwaps->{$device};
|
||||
if (!defined $new) {
|
||||
# Swap entry disappeared, so turn it off. Can't use
|
||||
# "systemctl stop" here because systemd has lots of alias
|
||||
# units that prevent a stop from actually calling
|
||||
# "swapoff".
|
||||
print STDERR "stopping swap device: $device\n";
|
||||
system("@utillinux@/sbin/swapoff", $device);
|
||||
}
|
||||
# FIXME: update swap options (i.e. its priority).
|
||||
}
|
||||
|
||||
if (scalar @unitsToStop > 0) {
|
||||
@unitsToStop = unique(@unitsToStop);
|
||||
print STDERR "stopping the following units: ", join(", ", sort(@unitsToStop)), "\n";
|
||||
system("@systemd@/bin/systemctl", "stop", "--", @unitsToStop); # FIXME: ignore errors?
|
||||
}
|
||||
|
||||
print STDERR "NOT restarting the following units: ", join(", ", sort(@unitsToSkip)), "\n"
|
||||
if scalar @unitsToSkip > 0;
|
||||
|
||||
# Activate the new configuration (i.e., update /etc, make accounts,
|
||||
# and so on).
|
||||
my $res = 0;
|
||||
print STDERR "activating the configuration...\n";
|
||||
system("$out/activate", "$out") == 0 or $res = 2;
|
||||
|
||||
# Restart systemd if necessary.
|
||||
if (abs_path("/proc/1/exe") ne abs_path("@systemd@/lib/systemd/systemd")) {
|
||||
print STDERR "restarting systemd...\n";
|
||||
system("@systemd@/bin/systemctl", "daemon-reexec") == 0 or $res = 2;
|
||||
}
|
||||
|
||||
# Forget about previously failed services.
|
||||
system("@systemd@/bin/systemctl", "reset-failed");
|
||||
|
||||
# Make systemd reload its units.
|
||||
system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3;
|
||||
|
||||
# Restart changed services (those that have to be restarted rather
|
||||
# than stopped and started).
|
||||
my @restart = unique(split('\n', read_file($restartListFile, err_mode => 'quiet') // ""));
|
||||
if (scalar @restart > 0) {
|
||||
print STDERR "restarting the following units: ", join(", ", sort(@restart)), "\n";
|
||||
system("@systemd@/bin/systemctl", "restart", "--", @restart) == 0 or $res = 4;
|
||||
unlink($restartListFile);
|
||||
}
|
||||
|
||||
# Start all active targets, as well as changed units we stopped above.
|
||||
# The latter is necessary because some may not be dependencies of the
|
||||
# targets (i.e., they were manually started). FIXME: detect units
|
||||
# that are symlinks to other units. We shouldn't start both at the
|
||||
# same time because we'll get a "Failed to add path to set" error from
|
||||
# systemd.
|
||||
my @start = unique("default.target", "timers.target", split('\n', read_file($startListFile, err_mode => 'quiet') // ""));
|
||||
print STDERR "starting the following units: ", join(", ", sort(@start)), "\n";
|
||||
system("@systemd@/bin/systemctl", "start", "--", @start) == 0 or $res = 4;
|
||||
unlink($startListFile);
|
||||
|
||||
# Reload units that need it. This includes remounting changed mount
|
||||
# units.
|
||||
my @reload = unique(split '\n', read_file($reloadListFile, err_mode => 'quiet') // "");
|
||||
if (scalar @reload > 0) {
|
||||
print STDERR "reloading the following units: ", join(", ", sort(@reload)), "\n";
|
||||
system("@systemd@/bin/systemctl", "reload", "--", @reload) == 0 or $res = 4;
|
||||
unlink($reloadListFile);
|
||||
}
|
||||
|
||||
# Signal dbus to reload its configuration.
|
||||
system("@systemd@/bin/systemctl", "reload", "dbus.service");
|
||||
|
||||
# Print failed and new units.
|
||||
my (@failed, @new, @restarting);
|
||||
my $activeNew = getActiveUnits;
|
||||
while (my ($unit, $state) = each %{$activeNew}) {
|
||||
push @failed, $unit if $state->{state} eq "failed" || $state->{substate} eq "auto-restart";
|
||||
push @new, $unit if $state->{state} ne "failed" && !defined $activePrev->{$unit};
|
||||
}
|
||||
|
||||
print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n"
|
||||
if scalar @new > 0;
|
||||
|
||||
if (scalar @failed > 0) {
|
||||
print STDERR "warning: the following units failed: ", join(", ", sort(@failed)), "\n";
|
||||
foreach my $unit (@failed) {
|
||||
print STDERR "\n";
|
||||
system("COLUMNS=1000 @systemd@/bin/systemctl status --no-pager '$unit' >&2");
|
||||
}
|
||||
$res = 4;
|
||||
}
|
||||
|
||||
if ($res == 0) {
|
||||
syslog(LOG_NOTICE, "finished switching to system configuration $out");
|
||||
} else {
|
||||
syslog(LOG_ERR, "switching to system configuration $out failed (status $res)");
|
||||
}
|
||||
|
||||
exit $res;
|
||||
@@ -0,0 +1,194 @@
|
||||
{ config, pkgs, modules, baseModules, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
|
||||
# This attribute is responsible for creating boot entries for
|
||||
# child configuration. They are only (directly) accessible
|
||||
# when the parent configuration is boot default. For example,
|
||||
# you can provide an easy way to boot the same configuration
|
||||
# as you use, but with another kernel
|
||||
# !!! fix this
|
||||
cloner = inheritParent: list: with pkgs.lib;
|
||||
map (childConfig:
|
||||
(import ../../../lib/eval-config.nix {
|
||||
inherit baseModules;
|
||||
modules =
|
||||
(optionals inheritParent modules)
|
||||
++ [ ./no-clone.nix ]
|
||||
++ [ childConfig ];
|
||||
}).config.system.build.toplevel
|
||||
) list;
|
||||
|
||||
children =
|
||||
cloner false config.nesting.children
|
||||
++ cloner true config.nesting.clone;
|
||||
|
||||
|
||||
systemBuilder =
|
||||
let
|
||||
kernelPath = "${config.boot.kernelPackages.kernel}/" +
|
||||
"${config.system.boot.loader.kernelFile}";
|
||||
in ''
|
||||
mkdir $out
|
||||
|
||||
if [ ! -f ${kernelPath} ]; then
|
||||
echo "The bootloader cannot find the proper kernel image."
|
||||
echo "(Expecting ${kernelPath})"
|
||||
false
|
||||
fi
|
||||
|
||||
ln -s ${kernelPath} $out/kernel
|
||||
ln -s ${config.system.modulesTree} $out/kernel-modules
|
||||
|
||||
ln -s ${config.system.build.initialRamdisk}/initrd $out/initrd
|
||||
|
||||
echo "$activationScript" > $out/activate
|
||||
substituteInPlace $out/activate --subst-var out
|
||||
chmod u+x $out/activate
|
||||
unset activationScript
|
||||
|
||||
cp ${config.system.build.bootStage2} $out/init
|
||||
substituteInPlace $out/init --subst-var-by systemConfig $out
|
||||
|
||||
ln -s ${config.system.build.etc}/etc $out/etc
|
||||
ln -s ${config.system.path} $out/sw
|
||||
ln -s "$systemd" $out/systemd
|
||||
ln -s ${config.hardware.firmware} $out/firmware
|
||||
|
||||
echo -n "$kernelParams" > $out/kernel-params
|
||||
echo -n "$configurationName" > $out/configuration-name
|
||||
echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
|
||||
echo -n "$nixosVersion" > $out/nixos-version
|
||||
|
||||
mkdir $out/fine-tune
|
||||
childCount=0
|
||||
for i in $children; do
|
||||
childCount=$(( childCount + 1 ))
|
||||
ln -s $i $out/fine-tune/child-$childCount
|
||||
done
|
||||
|
||||
mkdir $out/bin
|
||||
substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration
|
||||
chmod +x $out/bin/switch-to-configuration
|
||||
|
||||
${config.system.extraSystemBuilderCmds}
|
||||
'';
|
||||
|
||||
|
||||
# Putting it all together. This builds a store path containing
|
||||
# symlinks to the various parts of the built configuration (the
|
||||
# kernel, the Upstart services, the init scripts, etc.) as well as a
|
||||
# script `switch-to-configuration' that activates the configuration
|
||||
# and makes it bootable.
|
||||
system = pkgs.stdenv.mkDerivation {
|
||||
name = "nixos-${config.system.nixosVersion}";
|
||||
preferLocalBuild = true;
|
||||
buildCommand = systemBuilder;
|
||||
|
||||
inherit (pkgs) utillinux coreutils;
|
||||
systemd = config.systemd.package;
|
||||
|
||||
inherit children;
|
||||
kernelParams =
|
||||
config.boot.kernelParams ++ config.boot.extraKernelParams;
|
||||
installBootLoader =
|
||||
config.system.build.installBootLoader
|
||||
or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
|
||||
activationScript = config.system.activationScripts.script;
|
||||
nixosVersion = config.system.nixosVersion;
|
||||
|
||||
jobs = map (j: j.name) (attrValues config.jobs);
|
||||
|
||||
# Pass the names of all Upstart tasks to the activation script.
|
||||
tasks = attrValues (mapAttrs (n: v: if v.task then ["[${v.name}]=1"] else []) config.jobs);
|
||||
|
||||
# Pass the names of all Upstart jobs that shouldn't be restarted
|
||||
# to the activation script.
|
||||
noRestartIfChanged = attrValues (mapAttrs (n: v: if v.restartIfChanged then [] else ["[${v.name}]=1"]) config.jobs);
|
||||
|
||||
configurationName = config.boot.loader.grub.configurationName;
|
||||
|
||||
# Needed by switch-to-configuration.
|
||||
perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl";
|
||||
};
|
||||
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
system.build = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
Attribute set of derivations used to setup the system.
|
||||
'';
|
||||
};
|
||||
|
||||
nesting.children = mkOption {
|
||||
default = [];
|
||||
description = ''
|
||||
Additional configurations to build.
|
||||
'';
|
||||
};
|
||||
|
||||
nesting.clone = mkOption {
|
||||
default = [];
|
||||
description = ''
|
||||
Additional configurations to build based on the current
|
||||
configuration which is has a lower priority.
|
||||
'';
|
||||
};
|
||||
|
||||
system.boot.loader.id = mkOption {
|
||||
default = "";
|
||||
description = ''
|
||||
Id string of the used bootloader.
|
||||
'';
|
||||
};
|
||||
|
||||
system.boot.loader.kernelFile = mkOption {
|
||||
default = pkgs.stdenv.platform.kernelTarget;
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Name of the kernel file to be passed to the bootloader.
|
||||
'';
|
||||
};
|
||||
|
||||
system.copySystemConfiguration = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
If enabled, copies the NixOS configuration file
|
||||
<literal>$NIXOS_CONFIG</literal> (usually
|
||||
<filename>/etc/nixos/configuration.nix</filename>)
|
||||
to the system store path.
|
||||
'';
|
||||
};
|
||||
|
||||
system.extraSystemBuilderCmds = mkOption {
|
||||
default = "";
|
||||
internal = true;
|
||||
merge = concatStringsSep "\n";
|
||||
description = ''
|
||||
This code will be added to the builder creating the system store path.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config = {
|
||||
|
||||
system.extraSystemBuilderCmds =
|
||||
optionalString
|
||||
config.system.copySystemConfiguration
|
||||
"cp ${maybeEnv "NIXOS_CONFIG" "/etc/nixos/configuration.nix"} $out";
|
||||
|
||||
system.build.toplevel = system;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
kernel = config.boot.kernelPackages.kernel;
|
||||
|
||||
kernelModulesConf = pkgs.writeText "nixos.conf"
|
||||
''
|
||||
${concatStringsSep "\n" config.boot.kernelModules}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.kernelPackages = mkOption {
|
||||
default = pkgs.linuxPackages;
|
||||
# We don't want to evaluate all of linuxPackages for the manual
|
||||
# - some of it might not even evaluate correctly.
|
||||
defaultText = "pkgs.linuxPackages";
|
||||
example = "pkgs.linuxPackages_2_6_25";
|
||||
description = ''
|
||||
This option allows you to override the Linux kernel used by
|
||||
NixOS. Since things like external kernel module packages are
|
||||
tied to the kernel you're using, it also overrides those.
|
||||
This option is a function that takes Nixpkgs as an argument
|
||||
(as a convenience), and returns an attribute set containing at
|
||||
the very least an attribute <varname>kernel</varname>.
|
||||
Additional attributes may be needed depending on your
|
||||
configuration. For instance, if you use the NVIDIA X driver,
|
||||
then it also needs to contain an attribute
|
||||
<varname>nvidia_x11</varname>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.kernelParams = mkOption {
|
||||
default = [ ];
|
||||
description = ''
|
||||
The kernel parameters. If you want to add additional
|
||||
parameters, it's best to set
|
||||
<option>boot.extraKernelParams</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.extraKernelParams = mkOption {
|
||||
default = [ ];
|
||||
example = [ "boot.trace" ];
|
||||
description = "Additional user-defined kernel parameters.";
|
||||
};
|
||||
|
||||
boot.consoleLogLevel = mkOption {
|
||||
type = types.int;
|
||||
default = 4;
|
||||
description = ''
|
||||
The kernel console log level. Only log messages with a
|
||||
priority numerically less than this will appear on the
|
||||
console.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.vesa = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to activate VESA video mode on boot.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.extraModulePackages = mkOption {
|
||||
default = [];
|
||||
# !!! example = [pkgs.nvidia_x11];
|
||||
description = "A list of additional packages supplying kernel modules.";
|
||||
};
|
||||
|
||||
boot.kernelModules = mkOption {
|
||||
default = [];
|
||||
description = ''
|
||||
The set of kernel modules to be loaded in the second stage of
|
||||
the boot process. Note that modules that are needed to
|
||||
mount the root file system should be added to
|
||||
<option>boot.initrd.availableKernelModules</option> or
|
||||
<option>boot.initrd.kernelModules</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = mkOption {
|
||||
default = [];
|
||||
example = [ "sata_nv" "ext3" ];
|
||||
description = ''
|
||||
The set of kernel modules in the initial ramdisk used during the
|
||||
boot process. This set must include all modules necessary for
|
||||
mounting the root device. That is, it should include modules
|
||||
for the physical device (e.g., SCSI drivers) and for the file
|
||||
system (e.g., ext3). The set specified here is automatically
|
||||
closed under the module dependency relation, i.e., all
|
||||
dependencies of the modules list here are included
|
||||
automatically. The modules listed here are available in the
|
||||
initrd, but are only loaded on demand (e.g., the ext3 module is
|
||||
loaded automatically when an ext3 filesystem is mounted, and
|
||||
modules for PCI devices are loaded when they match the PCI ID
|
||||
of a device in your system). To force a module to be loaded,
|
||||
include it in <option>boot.initrd.kernelModules</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.kernelModules = mkOption {
|
||||
default = [];
|
||||
description = "List of modules that are always loaded by the initrd.";
|
||||
};
|
||||
|
||||
system.modulesTree = mkOption {
|
||||
internal = true;
|
||||
default = [];
|
||||
description = ''
|
||||
Tree of kernel modules. This includes the kernel, plus modules
|
||||
built outside of the kernel. Combine these into a single tree of
|
||||
symlinks because modprobe only supports one directory.
|
||||
'';
|
||||
merge = mergeListOption;
|
||||
# Convert the list of path to only one path.
|
||||
apply = pkgs.aggregateModules;
|
||||
};
|
||||
|
||||
system.requiredKernelConfig = mkOption {
|
||||
default = [];
|
||||
example = literalExample ''
|
||||
with config.lib.kernelConfig; [
|
||||
(isYes "MODULES")
|
||||
(isEnabled "FB_CON_DECOR")
|
||||
(isEnabled "BLK_DEV_INITRD")
|
||||
]
|
||||
'';
|
||||
internal = true;
|
||||
type = types.listOf types.attrs;
|
||||
description = ''
|
||||
This option allows modules to specify the kernel config options that
|
||||
must be set (or unset) for the module to work. Please use the
|
||||
lib.kernelConfig functions to build list elements.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
system.build = { inherit kernel; };
|
||||
|
||||
system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
|
||||
|
||||
# Implement consoleLogLevel both in early boot and using sysctl
|
||||
# (so you don't need to reboot to have changes take effect).
|
||||
boot.kernelParams =
|
||||
[ "loglevel=${toString config.boot.consoleLogLevel}" ] ++
|
||||
optionals config.boot.vesa [ "vga=0x317" ];
|
||||
|
||||
boot.kernel.sysctl."kernel.printk" = config.boot.consoleLogLevel;
|
||||
|
||||
boot.kernelModules = [ "loop" ];
|
||||
|
||||
boot.initrd.availableKernelModules =
|
||||
[ # Note: most of these (especially the SATA/PATA modules)
|
||||
# shouldn't be included by default since nixos-hardware-scan
|
||||
# detects them, but I'm keeping them for now for backwards
|
||||
# compatibility.
|
||||
|
||||
# Some SATA/PATA stuff.
|
||||
"ahci"
|
||||
"sata_nv"
|
||||
"sata_via"
|
||||
"sata_sis"
|
||||
"sata_uli"
|
||||
"ata_piix"
|
||||
"pata_marvell"
|
||||
|
||||
# Standard SCSI stuff.
|
||||
"sd_mod"
|
||||
"sr_mod"
|
||||
|
||||
# Standard IDE stuff.
|
||||
"ide_cd"
|
||||
"ide_disk"
|
||||
"ide_generic"
|
||||
|
||||
# Support USB keyboards, in case the boot fails and we only have
|
||||
# a USB keyboard.
|
||||
"uhci_hcd"
|
||||
"ehci_hcd"
|
||||
"ehci_pci"
|
||||
"ohci_hcd"
|
||||
"xhci_hcd"
|
||||
"usbhid"
|
||||
"hid_generic"
|
||||
|
||||
# Unix domain sockets (needed by udev).
|
||||
"unix"
|
||||
|
||||
# Misc. stuff.
|
||||
"pcips2" "xtkbd"
|
||||
|
||||
# To wait for SCSI devices to appear.
|
||||
"scsi_wait_scan"
|
||||
];
|
||||
|
||||
boot.initrd.kernelModules =
|
||||
[ # For LVM.
|
||||
"dm_mod"
|
||||
];
|
||||
|
||||
# The Linux kernel >= 2.6.27 provides firmware.
|
||||
hardware.firmware = [ "${kernel}/lib/firmware" ];
|
||||
|
||||
# Create /etc/modules-load.d/nixos.conf, which is read by
|
||||
# systemd-modules-load.service to load required kernel modules.
|
||||
# FIXME: ensure that systemd-modules-load.service is restarted if
|
||||
# this file changes.
|
||||
environment.etc = singleton
|
||||
{ target = "modules-load.d/nixos.conf";
|
||||
source = kernelModulesConf;
|
||||
};
|
||||
|
||||
# Sigh. This overrides systemd's systemd-modules-load.service
|
||||
# just so we can set a restart trigger. Also make
|
||||
# multi-user.target pull it in so that it gets started if it
|
||||
# failed earlier.
|
||||
systemd.services."systemd-modules-load" =
|
||||
{ description = "Load Kernel Modules";
|
||||
wantedBy = [ "sysinit.target" "multi-user.target" ];
|
||||
before = [ "sysinit.target" "shutdown.target" ];
|
||||
unitConfig =
|
||||
{ DefaultDependencies = "no";
|
||||
Conflicts = "shutdown.target";
|
||||
};
|
||||
serviceConfig =
|
||||
{ Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${config.systemd.package}/lib/systemd/systemd-modules-load";
|
||||
# Ignore failed module loads. Typically some of the
|
||||
# modules in ‘boot.kernelModules’ are "nice to have but
|
||||
# not required" (e.g. acpi-cpufreq), so we don't want to
|
||||
# barf on those.
|
||||
SuccessExitStatus = "0 1";
|
||||
};
|
||||
restartTriggers = [ kernelModulesConf ];
|
||||
};
|
||||
|
||||
lib.kernelConfig = {
|
||||
isYes = option: {
|
||||
assertion = config: config.isYes option;
|
||||
message = "CONFIG_${option} is not yes!";
|
||||
configLine = "CONFIG_${option}=y";
|
||||
};
|
||||
|
||||
isNo = option: {
|
||||
assertion = config: config.isNo option;
|
||||
message = "CONFIG_${option} is not no!";
|
||||
configLine = "CONFIG_${option}=n";
|
||||
};
|
||||
|
||||
isModule = option: {
|
||||
assertion = config: config.isModule option;
|
||||
message = "CONFIG_${option} is not built as a module!";
|
||||
configLine = "CONFIG_${option}=m";
|
||||
};
|
||||
|
||||
### Usually you will just want to use these two
|
||||
# True if yes or module
|
||||
isEnabled = option: {
|
||||
assertion = config: config.isEnabled option;
|
||||
message = "CONFIG_${option} is not enabled!";
|
||||
configLine = "CONFIG_${option}=y";
|
||||
};
|
||||
|
||||
# True if no or omitted
|
||||
isDisabled = option: {
|
||||
assertion = config: config.isDisabled option;
|
||||
message = "CONFIG_${option} is not disabled!";
|
||||
configLine = "CONFIG_${option}=n";
|
||||
};
|
||||
};
|
||||
|
||||
# The config options that all modules can depend upon
|
||||
system.requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
# !!! Should this really be needed?
|
||||
(isYes "MODULES")
|
||||
(isYes "BINFMT_ELF")
|
||||
];
|
||||
|
||||
# nixpkgs kernels are assumed to have all required features
|
||||
assertions = if config.boot.kernelPackages.kernel ? features then [] else
|
||||
let cfg = config.boot.kernelPackages.kernel.config; in map (attrs:
|
||||
{ assertion = attrs.assertion cfg; inherit (attrs) message; }
|
||||
) config.system.requiredKernelConfig;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = [ pkgs.kexectools ];
|
||||
|
||||
systemd.services."prepare-kexec" =
|
||||
{ description = "Preparation for kexec";
|
||||
wantedBy = [ "kexec.target" ];
|
||||
before = [ "systemd-kexec.service" ];
|
||||
unitConfig.DefaultDependencies = false;
|
||||
serviceConfig.Type = "oneshot";
|
||||
path = [ pkgs.kexectools ];
|
||||
script =
|
||||
''
|
||||
p=$(readlink -f /nix/var/nix/profiles/system)
|
||||
if ! [ -d $p ]; then exit 1; fi
|
||||
exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin:$i/sbin; done
|
||||
|
||||
default=$1
|
||||
if test -z "$1"; then
|
||||
echo "Syntax: efi-boot-stub-builder.sh <DEFAULT-CONFIG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "updating the efi system partition..."
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
# Also, efi executables need the .efi extension
|
||||
cleanName() {
|
||||
local path="$1"
|
||||
echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' | sed 's|@kernelFile@$|@kernelFile@.efi|'
|
||||
}
|
||||
|
||||
# Copy a file from the Nix store to the EFI system partition
|
||||
declare -A filesCopied
|
||||
|
||||
copyToKernelsDir() {
|
||||
local src="$1"
|
||||
local dst="@efiSysMountPoint@/efi/nixos/$(cleanName $src)"
|
||||
# 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 ! test -e $dst; then
|
||||
local dstTmp=$dst.tmp.$$
|
||||
cp $src $dstTmp
|
||||
mv $dstTmp $dst
|
||||
fi
|
||||
filesCopied[$dst]=1
|
||||
result=$dst
|
||||
}
|
||||
|
||||
# Copy its kernel, initrd, and startup script to the efi system partition
|
||||
# Add the efibootmgr entry if requested
|
||||
addEntry() {
|
||||
local path="$1"
|
||||
local generation="$2"
|
||||
|
||||
if ! test -e $path/kernel -a -e $path/initrd; then
|
||||
return
|
||||
fi
|
||||
|
||||
local kernel=$(readlink -f $path/kernel)
|
||||
local initrd=$(readlink -f $path/initrd)
|
||||
copyToKernelsDir $kernel; kernel=$result
|
||||
copyToKernelsDir $initrd; initrd=$result
|
||||
|
||||
local startup="@efiSysMountPoint@/efi/nixos/generation-$generation-startup.nsh"
|
||||
if ! test -e $startup; then
|
||||
local dstTmp=$startup.tmp.$$
|
||||
echo "$(echo $kernel | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') systemConfig=$(readlink -f $path) init=$(readlink -f $path/init) initrd=$(echo $initrd | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') $(cat $path/kernel-params)" > $dstTmp
|
||||
mv $dstTmp $startup
|
||||
fi
|
||||
filesCopied[$startup]=1
|
||||
|
||||
if test -n "@runEfibootmgr@"; then
|
||||
set +e
|
||||
efibootmgr -c -d "@efiDisk@" -g -l $(echo $kernel | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') -L "NixOS $generation Generation" -p "@efiPartition@" \
|
||||
-u systemConfig=$(readlink -f $path) init=$(readlink -f $path/init) initrd=$(echo $initrd | sed 's|@efiSysMountPoint@||' | sed 's|/|\\|g') $(cat $path/kernel-params) > /dev/null 2>&1
|
||||
set -e
|
||||
fi
|
||||
|
||||
if test $(readlink -f "$path") = "$default"; then
|
||||
if test -n "@runEfibootmgr@"; then
|
||||
set +e
|
||||
defaultbootnum=$(efibootmgr | grep "NixOS $generation Generation" | sed 's/Boot//' | sed 's/\*.*//')
|
||||
set -e
|
||||
fi
|
||||
|
||||
if test -n "@installStartupNsh@"; then
|
||||
sed 's|.*@kernelFile@.efi|@kernelFile@.efi|' < $startup > "@efiSysMountPoint@/startup.nsh"
|
||||
cp $kernel "@efiSysMountPoint@/@kernelFile@.efi"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
mkdir -p "@efiSysMountPoint@/efi/nixos/"
|
||||
|
||||
# Remove all old boot manager entries
|
||||
if test -n "@runEfibootmgr@"; then
|
||||
set +e
|
||||
modprobe efivars > /dev/null 2>&1
|
||||
for bootnum in $(efibootmgr | grep "NixOS" | grep "Generation" | sed 's/Boot//' | sed 's/\*.*//'); do
|
||||
efibootmgr -B -b "$bootnum" > /dev/null 2>&1
|
||||
done
|
||||
set -e
|
||||
fi
|
||||
|
||||
# Add all generations of the system profile to the system partition, in reverse
|
||||
# (most recent to least recent) order.
|
||||
for generation in $(
|
||||
(cd /nix/var/nix/profiles && ls -d system-*-link) \
|
||||
| sed 's/system-\([0-9]\+\)-link/\1/' \
|
||||
| sort -n -r); do
|
||||
link=/nix/var/nix/profiles/system-$generation-link
|
||||
addEntry $link $generation
|
||||
done
|
||||
|
||||
if test -n "@runEfibootmgr@"; then
|
||||
set +e
|
||||
efibootmgr -o $defaultbootnum > /dev/null 2>&1
|
||||
set -e
|
||||
fi
|
||||
|
||||
if test -n "@efiShell@"; then
|
||||
mkdir -pv "@efiSysMountPoint@"/efi/boot
|
||||
cp "@efiShell@" "@efiSysMountPoint@"/efi/boot/boot"@targetArch@".efi
|
||||
fi
|
||||
|
||||
# Remove obsolete files from the EFI system partition
|
||||
for fn in "@efiSysMountPoint@/efi/nixos/"*; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
rm -vf -- "$fn"
|
||||
fi
|
||||
done
|
||||
|
||||
# Run any extra commands users may need
|
||||
if test -n "@runEfibootmgr@"; then
|
||||
set +e
|
||||
@postEfiBootMgrCommands@
|
||||
set -e
|
||||
fi
|
||||
@@ -0,0 +1,98 @@
|
||||
{pkgs, config, ...}:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
efiBootStubBuilder = pkgs.substituteAll {
|
||||
src = ./efi-boot-stub-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.glibc] ++ (pkgs.stdenv.lib.optionals config.boot.loader.efi.canTouchEfiVariables [pkgs.efibootmgr pkgs.module_init_tools]);
|
||||
inherit (config.boot.loader.efiBootStub) installStartupNsh;
|
||||
|
||||
inherit (config.boot.loader.efi) efiSysMountPoint;
|
||||
|
||||
inherit (config.boot.loader.efi.efibootmgr) efiDisk efiPartition postEfiBootMgrCommands;
|
||||
|
||||
runEfibootmgr = config.boot.loader.efi.canTouchEfiVariables;
|
||||
|
||||
efiShell = if config.boot.loader.efiBootStub.installShell then
|
||||
if pkgs.stdenv.isi686 then
|
||||
pkgs.fetchurl {
|
||||
url = "https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2/EdkShellBinPkg/FullShell/Ia32/Shell_Full.efi";
|
||||
sha256 = "1gv6kyaspczdp7x8qnx5x76ilriaygkfs99ay7ihhdi6riclkhfl";
|
||||
}
|
||||
else
|
||||
pkgs.fetchurl {
|
||||
url = "https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2/EdkShellBinPkg/FullShell/X64/Shell_Full.efi";
|
||||
sha256 = "1g18z84rlavxr5gsrh2g942rfr6znv9fs3fqww5m7dhmnysgyv8p";
|
||||
}
|
||||
else
|
||||
null;
|
||||
|
||||
kernelFile = platform.kernelTarget;
|
||||
targetArch = if pkgs.stdenv.isi686 then
|
||||
"IA32"
|
||||
else if pkgs.stdenv.isx86_64 then
|
||||
"X64"
|
||||
else
|
||||
throw "Unsupported architecture";
|
||||
};
|
||||
|
||||
# Temporary check, for nixos to cope both with nixpkgs stdenv-updates and trunk
|
||||
platform = pkgs.stdenv.platform;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
boot = {
|
||||
loader = {
|
||||
efiBootStub = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to use the linux kernel as an EFI bootloader.
|
||||
When enabled, the kernel, initrd, and an EFI shell script
|
||||
to boot the system are copied to the EFI system partition.
|
||||
'';
|
||||
};
|
||||
|
||||
installStartupNsh = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to install a startup.nsh in the root of the EFI system partition.
|
||||
For now, it will just boot the latest version when run, the eventual goal
|
||||
is to have a basic menu-type interface.
|
||||
'';
|
||||
};
|
||||
|
||||
installShell = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to install an EFI shell in \EFI\BOOT.
|
||||
This _should_ only be needed for removable devices
|
||||
(CDs, usb sticks, etc.), but it may be an option for broken
|
||||
systems where efibootmgr doesn't work. Particularly useful in
|
||||
conjunction with installStartupNsh
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf config.boot.loader.efiBootStub.enable {
|
||||
assertions = [ { assertion = ! config.boot.kernelPackages.kernel ? features || config.boot.kernelPackages.kernel.features ? efiBootStub; message = "This kernel does not support the EFI boot stub"; } ];
|
||||
|
||||
system = {
|
||||
build.installBootLoader = efiBootStubBuilder;
|
||||
boot.loader.id = "efiBootStub";
|
||||
boot.loader.kernelFile = platform.kernelTarget;
|
||||
requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
(isYes "EFI_STUB")
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
{
|
||||
options.boot.loader.efi = {
|
||||
canTouchEfiVariables = mkOption {
|
||||
default = false;
|
||||
|
||||
type = types.bool;
|
||||
|
||||
description = "Whether or not the installation process should modify efi boot variables.";
|
||||
};
|
||||
|
||||
efibootmgr = {
|
||||
efiDisk = mkOption {
|
||||
default = "/dev/sda";
|
||||
|
||||
type = types.string;
|
||||
|
||||
description = "The disk that contains the EFI system partition.";
|
||||
};
|
||||
|
||||
efiPartition = mkOption {
|
||||
default = "1";
|
||||
description = "The partition number of the EFI system partition.";
|
||||
};
|
||||
|
||||
postEfiBootMgrCommands = mkOption {
|
||||
default = "";
|
||||
type = types.string;
|
||||
description = ''
|
||||
Shell commands to be executed immediately after efibootmgr has setup the system EFI.
|
||||
Some systems do not follow the EFI specifications properly and insert extra entries.
|
||||
Others will brick (fix by removing battery) on boot when it finds more than X entries.
|
||||
This hook allows for running a few extra efibootmgr commands to combat these issues.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
efiSysMountPoint = mkOption {
|
||||
default = "/boot";
|
||||
|
||||
type = types.string;
|
||||
|
||||
description = "Where the EFI System Partition is mounted.";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
default=$1
|
||||
if test -z "$1"; then
|
||||
echo "Syntax: generations-dir-builder.sh <DEFAULT-CONFIG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "updating the boot generations directory..."
|
||||
|
||||
mkdir -p /boot
|
||||
|
||||
rm -Rf /boot/system* || true
|
||||
|
||||
target=/boot/grub/menu.lst
|
||||
tmp=$target.tmp
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
cleanName() {
|
||||
local path="$1"
|
||||
echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
|
||||
}
|
||||
|
||||
# Copy a file from the Nix store to /boot/kernels.
|
||||
declare -A filesCopied
|
||||
|
||||
copyToKernelsDir() {
|
||||
local src="$1"
|
||||
local dst="/boot/kernels/$(cleanName $src)"
|
||||
# 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 ! test -e $dst; then
|
||||
local dstTmp=$dst.tmp.$$
|
||||
cp $src $dstTmp
|
||||
mv $dstTmp $dst
|
||||
fi
|
||||
filesCopied[$dst]=1
|
||||
result=$dst
|
||||
}
|
||||
|
||||
|
||||
# Copy its kernel and initrd to /boot/kernels.
|
||||
addEntry() {
|
||||
local path="$1"
|
||||
local generation="$2"
|
||||
local outdir=/boot/system-$generation
|
||||
|
||||
if ! test -e $path/kernel -a -e $path/initrd; then
|
||||
return
|
||||
fi
|
||||
|
||||
local kernel=$(readlink -f $path/kernel)
|
||||
local initrd=$(readlink -f $path/initrd)
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
copyToKernelsDir $kernel; kernel=$result
|
||||
copyToKernelsDir $initrd; initrd=$result
|
||||
fi
|
||||
|
||||
mkdir -p $outdir
|
||||
ln -sf $(readlink -f $path) $outdir/system
|
||||
ln -sf $(readlink -f $path/init) $outdir/init
|
||||
ln -sf $initrd $outdir/initrd
|
||||
ln -sf $kernel $outdir/kernel
|
||||
|
||||
if test $(readlink -f "$path") = "$default"; then
|
||||
cp "$kernel" /boot/nixos-kernel
|
||||
cp "$initrd" /boot/nixos-initrd
|
||||
cp "$(readlink -f "$path/init")" /boot/nixos-init
|
||||
|
||||
mkdir -p /boot/default
|
||||
# ln -sfT: overrides target even if it exists.
|
||||
ln -sfT $(readlink -f $path) /boot/default/system
|
||||
ln -sfT $(readlink -f $path/init) /boot/default/init
|
||||
ln -sfT $initrd /boot/default/initrd
|
||||
ln -sfT $kernel /boot/default/kernel
|
||||
fi
|
||||
}
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
mkdir -p /boot/kernels
|
||||
fi
|
||||
|
||||
# Add all generations of the system profile to the menu, in reverse
|
||||
# (most recent to least recent) order.
|
||||
for generation in $(
|
||||
(cd /nix/var/nix/profiles && ls -d system-*-link) \
|
||||
| sed 's/system-\([0-9]\+\)-link/\1/' \
|
||||
| sort -n -r); do
|
||||
link=/nix/var/nix/profiles/system-$generation-link
|
||||
addEntry $link $generation
|
||||
done
|
||||
|
||||
# Remove obsolete files from /boot/kernels.
|
||||
for fn in /boot/kernels/*; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
rm -vf -- "$fn"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,63 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
generationsDirBuilder = pkgs.substituteAll {
|
||||
src = ./generations-dir-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
inherit (config.boot.loader.generationsDir) copyKernels;
|
||||
};
|
||||
|
||||
# Temporary check, for nixos to cope both with nixpkgs stdenv-updates and trunk
|
||||
platform = pkgs.stdenv.platform;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.generationsDir = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to create symlinks to the system generations under
|
||||
<literal>/boot</literal>. When enabled,
|
||||
<literal>/boot/default/kernel</literal>,
|
||||
<literal>/boot/default/initrd</literal>, etc., are updated to
|
||||
point to the current generation's kernel image, initial RAM
|
||||
disk, and other bootstrap files.
|
||||
|
||||
This optional is not necessary with boot loaders such as GNU GRUB
|
||||
for which the menu is updated to point to the latest bootstrap
|
||||
files. However, it is needed for U-Boot on platforms where the
|
||||
boot command line is stored in flash memory rather than in a
|
||||
menu file.
|
||||
'';
|
||||
};
|
||||
|
||||
copyKernels = mkOption {
|
||||
default = false;
|
||||
description = "
|
||||
Whether copy the necessary boot files into /boot, so
|
||||
/nix/store is not needed by the boot loader.
|
||||
";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config = mkIf config.boot.loader.generationsDir.enable {
|
||||
|
||||
system.build.installBootLoader = generationsDirBuilder;
|
||||
system.boot.loader.id = "generationsDir";
|
||||
system.boot.loader.kernelFile = platform.kernelTarget;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.boot.loader.grub;
|
||||
|
||||
realGrub = if cfg.version == 1 then pkgs.grub else pkgs.grub2;
|
||||
|
||||
grub =
|
||||
# Don't include GRUB if we're only generating a GRUB menu (e.g.,
|
||||
# in EC2 instances).
|
||||
if cfg.devices == ["nodev"]
|
||||
then null
|
||||
else realGrub;
|
||||
|
||||
f = x: if x == null then "" else "" + x;
|
||||
|
||||
grubConfig = pkgs.writeText "grub-config.xml" (builtins.toXML
|
||||
{ splashImage = f config.boot.loader.grub.splashImage;
|
||||
grub = f grub;
|
||||
shell = "${pkgs.stdenv.shell}";
|
||||
fullVersion = (builtins.parseDrvName realGrub.name).version;
|
||||
inherit (cfg)
|
||||
version extraConfig extraPerEntryConfig extraEntries
|
||||
extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels timeout
|
||||
default devices;
|
||||
path = (makeSearchPath "bin" [
|
||||
pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.findutils pkgs.diffutils
|
||||
]) + ":" + (makeSearchPath "sbin" [
|
||||
pkgs.mdadm
|
||||
]);
|
||||
});
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.loader.grub = {
|
||||
|
||||
enable = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the GNU GRUB boot loader.
|
||||
'';
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
default = 2;
|
||||
example = 1;
|
||||
type = types.int;
|
||||
description = ''
|
||||
The version of GRUB to use: <literal>1</literal> for GRUB
|
||||
Legacy (versions 0.9x), or <literal>2</literal> (the
|
||||
default) for GRUB 2.
|
||||
'';
|
||||
};
|
||||
|
||||
device = mkOption {
|
||||
default = "";
|
||||
example = "/dev/hda";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
The device on which the GRUB boot loader will be installed.
|
||||
The special value <literal>nodev</literal> means that a GRUB
|
||||
boot menu will be generated, but GRUB itself will not
|
||||
actually be installed. To install GRUB on multiple devices,
|
||||
use <literal>boot.loader.grub.devices</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
devices = mkOption {
|
||||
default = [];
|
||||
example = [ "/dev/hda" ];
|
||||
type = types.listOf types.string;
|
||||
description = ''
|
||||
The devices on which the boot loader, GRUB, will be
|
||||
installed. Can be used instead of <literal>device</literal> to
|
||||
install grub into multiple devices (e.g., if as softraid arrays holding /boot).
|
||||
'';
|
||||
};
|
||||
|
||||
# !!! How can we mark options as obsolete?
|
||||
bootDevice = mkOption {
|
||||
default = "";
|
||||
description = "Obsolete.";
|
||||
};
|
||||
|
||||
configurationName = mkOption {
|
||||
default = "";
|
||||
example = "Stable 2.6.21";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
GRUB entry name instead of default.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPrepareConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional bash commands to be run at the script that
|
||||
prepares the grub menu entries.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
example = "serial; terminal_output.serial";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional GRUB commands inserted in the configuration file
|
||||
just before the menu entries.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPerEntryConfig = mkOption {
|
||||
default = "";
|
||||
example = "root (hd0)";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Additional GRUB commands inserted in the configuration file
|
||||
at the start of each NixOS menu entry.
|
||||
'';
|
||||
};
|
||||
|
||||
extraEntries = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = ''
|
||||
# GRUB 1 example (not GRUB 2 compatible)
|
||||
title Windows
|
||||
chainloader (hd0,1)+1
|
||||
|
||||
# GRUB 2 example
|
||||
menuentry "Windows7" {
|
||||
title Windows7
|
||||
insmod ntfs
|
||||
set root='(hd1,1)'
|
||||
chainloader +1
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Any additional entries you want added to the GRUB boot menu.
|
||||
'';
|
||||
};
|
||||
|
||||
extraEntriesBeforeNixOS = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether extraEntries are included before the default option.
|
||||
'';
|
||||
};
|
||||
|
||||
extraFiles = mkOption {
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
{ "memtest.bin" = "${pkgs.memtest86plus}/memtest.bin"; }
|
||||
'';
|
||||
description = ''
|
||||
A set of files to be copied to <filename>/boot</filename>.
|
||||
Each attribute name denotes the destination file name in
|
||||
<filename>/boot</filename>, while the corresponding
|
||||
attribute value specifies the source file.
|
||||
'';
|
||||
};
|
||||
|
||||
splashImage = mkOption {
|
||||
default =
|
||||
if cfg.version == 1
|
||||
then pkgs.fetchurl {
|
||||
url = http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz;
|
||||
sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
|
||||
}
|
||||
# GRUB 1.97 doesn't support gzipped XPMs.
|
||||
else ./winkler-gnu-blue-640x480.png;
|
||||
example = null;
|
||||
description = ''
|
||||
Background image used for GRUB. It must be a 640x480,
|
||||
14-colour image in XPM format, optionally compressed with
|
||||
<command>gzip</command> or <command>bzip2</command>. Set to
|
||||
<literal>null</literal> to run GRUB in text mode.
|
||||
'';
|
||||
};
|
||||
|
||||
configurationLimit = mkOption {
|
||||
default = 100;
|
||||
example = 120;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Maximum of configurations in boot menu. GRUB has problems when
|
||||
there are too many entries.
|
||||
'';
|
||||
};
|
||||
|
||||
copyKernels = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether the GRUB menu builder should copy kernels and initial
|
||||
ramdisks to /boot. This is done automatically if /boot is
|
||||
on a different partition than /.
|
||||
'';
|
||||
};
|
||||
|
||||
timeout = mkOption {
|
||||
default = 5;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Timeout (in seconds) until GRUB boots the default menu item.
|
||||
'';
|
||||
};
|
||||
|
||||
default = mkOption {
|
||||
default = 0;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Index of the default menu item to be booted.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
boot.loader.grub.devices = optional (cfg.device != "") cfg.device;
|
||||
|
||||
system.build = mkAssert (cfg.devices != [])
|
||||
"You must set the ‘boot.loader.grub.device’ option to make the system bootable."
|
||||
{ installBootLoader =
|
||||
"PERL5LIB=${makePerlPath [ pkgs.perlPackages.XMLLibXML pkgs.perlPackages.XMLSAX ]} " +
|
||||
"${pkgs.perl}/bin/perl ${./install-grub.pl} ${grubConfig}";
|
||||
inherit grub;
|
||||
};
|
||||
|
||||
# Common attribute for boot loaders so only one of them can be
|
||||
# set at once.
|
||||
system.boot.loader.id = "grub";
|
||||
|
||||
environment.systemPackages = [ grub ];
|
||||
|
||||
boot.loader.grub.extraPrepareConfig =
|
||||
concatStrings (mapAttrsToList (n: v: ''
|
||||
${pkgs.coreutils}/bin/cp -pf "${v}" "/boot/${n}"
|
||||
'') config.boot.loader.grub.extraFiles);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
use XML::LibXML;
|
||||
use File::Basename;
|
||||
use File::Path;
|
||||
use File::stat;
|
||||
use File::Copy;
|
||||
use POSIX;
|
||||
use Cwd;
|
||||
|
||||
my $defaultConfig = $ARGV[1] or die;
|
||||
|
||||
my $dom = XML::LibXML->load_xml(location => $ARGV[0]);
|
||||
|
||||
sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
my $grub = get("grub");
|
||||
my $grubVersion = int(get("version"));
|
||||
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 $configurationLimit = int(get("configurationLimit"));
|
||||
my $copyKernels = get("copyKernels") eq "true";
|
||||
my $timeout = int(get("timeout"));
|
||||
my $defaultEntry = int(get("default"));
|
||||
$ENV{'PATH'} = get("path");
|
||||
|
||||
die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
|
||||
|
||||
print STDERR "updating GRUB $grubVersion menu...\n";
|
||||
|
||||
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) {
|
||||
$copyKernels = 1;
|
||||
}
|
||||
|
||||
|
||||
# Generate the header.
|
||||
my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n";
|
||||
|
||||
if ($grubVersion == 1) {
|
||||
$conf .= "
|
||||
default $defaultEntry
|
||||
timeout $timeout
|
||||
";
|
||||
if ($splashImage) {
|
||||
copy $splashImage, "/boot/background.xpm.gz" or die "cannot copy $splashImage to /boot\n";
|
||||
$conf .= "splashimage $bootRoot/background.xpm.gz\n";
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$conf .= "
|
||||
if [ -s \$prefix/grubenv ]; then
|
||||
load_env
|
||||
fi
|
||||
|
||||
# ‘grub-reboot’ sets a one-time saved entry, which we process here and
|
||||
# then delete.
|
||||
if [ \"\${saved_entry}\" ]; then
|
||||
# The next line *has* to look exactly like this, otherwise KDM's
|
||||
# reboot feature won't work properly with GRUB 2.
|
||||
set default=\"\${saved_entry}\"
|
||||
set saved_entry=
|
||||
set prev_saved_entry=
|
||||
save_env saved_entry
|
||||
save_env prev_saved_entry
|
||||
set timeout=1
|
||||
else
|
||||
set default=$defaultEntry
|
||||
set timeout=$timeout
|
||||
fi
|
||||
|
||||
if loadfont $bootRoot/grub/fonts/unicode.pf2; then
|
||||
set gfxmode=640x480
|
||||
insmod gfxterm
|
||||
insmod vbe
|
||||
terminal_output gfxterm
|
||||
fi
|
||||
";
|
||||
|
||||
if ($splashImage) {
|
||||
# FIXME: GRUB 1.97 doesn't resize the background image if it
|
||||
# doesn't match the video resolution.
|
||||
copy $splashImage, "/boot/background.png" or die "cannot copy $splashImage to /boot\n";
|
||||
$conf .= "
|
||||
insmod png
|
||||
if background_image $bootRoot/background.png; 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
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
$conf .= "$extraConfig\n";
|
||||
|
||||
|
||||
# Generate the menu entries.
|
||||
$conf .= "\n";
|
||||
|
||||
my %copied;
|
||||
mkpath("/boot/kernels", 0, 0755) if $copyKernels;
|
||||
|
||||
sub copyToKernelsDir {
|
||||
my ($path) = @_;
|
||||
return $path unless $copyKernels;
|
||||
$path =~ /\/nix\/store\/(.*)/ or die;
|
||||
my $name = $1; $name =~ s/\//-/g;
|
||||
my $dst = "/boot/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 "$bootRoot/kernels/$name";
|
||||
}
|
||||
|
||||
sub addEntry {
|
||||
my ($name, $path) = @_;
|
||||
return unless -e "$path/kernel" && -e "$path/initrd";
|
||||
|
||||
my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel"));
|
||||
my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd"));
|
||||
my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef;
|
||||
|
||||
# FIXME: $confName
|
||||
|
||||
my $kernelParams =
|
||||
"systemConfig=" . Cwd::abs_path($path) . " " .
|
||||
"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\n";
|
||||
} else {
|
||||
$conf .= "menuentry \"$name\" {\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);
|
||||
|
||||
$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
|
||||
|
||||
# extraEntries could refer to @bootRoot@, which we have to substitute
|
||||
$conf =~ s/\@bootRoot\@/$bootRoot/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;
|
||||
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'";
|
||||
}
|
||||
}
|
||||
|
||||
# Run extraPrepareConfig in sh
|
||||
if ($extraPrepareConfig ne "") {
|
||||
system((get("shell"), "-c", $extraPrepareConfig));
|
||||
}
|
||||
|
||||
# Atomically update the GRUB config.
|
||||
my $confFile = $grubVersion == 1 ? "/boot/grub/menu.lst" : "/boot/grub/grub.cfg";
|
||||
my $tmpFile = $confFile . ".tmp";
|
||||
writeFile($tmpFile, $conf);
|
||||
rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n";
|
||||
|
||||
|
||||
# Remove obsolete files from /boot/kernels.
|
||||
foreach my $fn (glob "/boot/kernels/*") {
|
||||
next if defined $copied{$fn};
|
||||
print STDERR "removing obsolete file $fn\n";
|
||||
unlink $fn;
|
||||
}
|
||||
|
||||
|
||||
# Install GRUB if the version changed from the last time we installed
|
||||
# it. FIXME: shouldn't we reinstall if ‘devices’ changed?
|
||||
my $prevVersion = readFile("/boot/grub/version") // "";
|
||||
if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1" || get("fullVersion") ne $prevVersion) {
|
||||
foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) {
|
||||
$dev = $dev->findvalue(".") or die;
|
||||
next if $dev eq "nodev";
|
||||
print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
|
||||
system("$grub/sbin/grub-install", "--recheck", Cwd::abs_path($dev)) == 0
|
||||
or die "$0: installation of GRUB on $dev failed\n";
|
||||
}
|
||||
writeFile("/boot/grub/version", get("fullVersion"));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# This module adds Memtest86+ to the GRUB boot menu.
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
memtest86 = pkgs.memtest86plus;
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.grub.memtest86 = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Make Memtest86+, a memory testing program, available from the
|
||||
GRUB boot menu.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf config.boot.loader.grub.memtest86 {
|
||||
|
||||
boot.loader.grub.extraEntries = mkFixStrictness (
|
||||
if config.boot.loader.grub.version == 2 then
|
||||
''
|
||||
menuentry "Memtest86+" {
|
||||
linux16 @bootRoot@/memtest.bin
|
||||
}
|
||||
''
|
||||
else
|
||||
throw "Memtest86+ is not supported with GRUB 1.");
|
||||
|
||||
boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
|
||||
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,6 @@
|
||||
This is a resized version of
|
||||
|
||||
http://www.gnu.org/graphics/winkler-gnu-blue.png
|
||||
|
||||
by Kyle Winkler and released under the Free Art License
|
||||
(http://artlibre.org/licence.php/lalgb.html).
|
||||
@@ -0,0 +1,114 @@
|
||||
#! @python@/bin/python
|
||||
import argparse
|
||||
import shutil
|
||||
import os
|
||||
import errno
|
||||
import subprocess
|
||||
import glob
|
||||
import tempfile
|
||||
import errno
|
||||
|
||||
def copy_if_not_exists(source, dest):
|
||||
known_paths.append(dest)
|
||||
if not os.path.exists(dest):
|
||||
shutil.copyfile(source, dest)
|
||||
|
||||
system_dir = lambda generation: "/nix/var/nix/profiles/system-%d-link" % (generation)
|
||||
|
||||
def write_entry(generation, kernel, initrd):
|
||||
entry_file = "@efiSysMountPoint@/loader/entries/nixos-generation-%d.conf" % (generation)
|
||||
generation_dir = os.readlink(system_dir(generation))
|
||||
tmp_path = "%s.tmp" % (entry_file)
|
||||
kernel_params = "systemConfig=%s init=%s/init " % (generation_dir, generation_dir)
|
||||
with open("%s/kernel-params" % (generation_dir)) as params_file:
|
||||
kernel_params = kernel_params + params_file.read()
|
||||
with open(tmp_path, 'w') as f:
|
||||
print >> f, "title NixOS"
|
||||
print >> f, "version Generation %d" % (generation)
|
||||
if machine_id is not None: print >> f, "machine-id %s" % (machine_id)
|
||||
print >> f, "linux %s" % (kernel)
|
||||
print >> f, "initrd %s" % (initrd)
|
||||
print >> f, "options %s" % (kernel_params)
|
||||
os.rename(tmp_path, entry_file)
|
||||
|
||||
def write_loader_conf(generation):
|
||||
with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
|
||||
if "@timeout@" != "":
|
||||
print >> f, "timeout @timeout@"
|
||||
print >> f, "default nixos-generation-%d" % (generation)
|
||||
os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
|
||||
|
||||
def copy_from_profile(generation, name):
|
||||
store_file_path = os.readlink("%s/%s" % (system_dir(generation), name))
|
||||
suffix = os.path.basename(store_file_path)
|
||||
store_dir = os.path.basename(os.path.dirname(store_file_path))
|
||||
efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
|
||||
copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
|
||||
return efi_file_path
|
||||
|
||||
def add_entry(generation):
|
||||
efi_kernel_path = copy_from_profile(generation, "kernel")
|
||||
efi_initrd_path = copy_from_profile(generation, "initrd")
|
||||
write_entry(generation, efi_kernel_path, efi_initrd_path)
|
||||
|
||||
def mkdir_p(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST or not os.path.isdir(path):
|
||||
raise
|
||||
|
||||
def get_generations(profile):
|
||||
gen_list = subprocess.check_output([
|
||||
"@nix@/bin/nix-env",
|
||||
"--list-generations",
|
||||
"-p",
|
||||
"/nix/var/nix/profiles/%s" % (profile)
|
||||
])
|
||||
gen_lines = gen_list.split('\n')
|
||||
gen_lines.pop()
|
||||
return [ int(line.split()[0]) for line in gen_lines ]
|
||||
|
||||
def remove_old_entries(gens):
|
||||
slice_start = len("@efiSysMountPoint@/loader/entries/nixos-generation-")
|
||||
slice_end = -1 * len(".conf")
|
||||
for path in glob.iglob("@efiSysMountPoint@/loader/entries/nixos-generation-[1-9]*.conf"):
|
||||
try:
|
||||
gen = int(path[slice_start:slice_end])
|
||||
if not gen in gens:
|
||||
os.unlink(path)
|
||||
except ValueError:
|
||||
pass
|
||||
for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
|
||||
if not path in known_paths:
|
||||
os.unlink(path)
|
||||
|
||||
parser = argparse.ArgumentParser(description='Update NixOS-related gummiboot files')
|
||||
parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default NixOS config to boot')
|
||||
args = parser.parse_args()
|
||||
|
||||
# We deserve our own env var!
|
||||
if os.getenv("NIXOS_INSTALL_GRUB") == "1":
|
||||
if "@canTouchEfiVariables@" == "1":
|
||||
subprocess.check_call(["@gummiboot@/bin/gummiboot", "--path=@efiSysMountPoint@", "install"])
|
||||
else:
|
||||
subprocess.check_call(["@gummiboot@/bin/gummiboot", "--path=@efiSysMountPoint@", "--no-variables", "install"])
|
||||
|
||||
known_paths = []
|
||||
mkdir_p("@efiSysMountPoint@/efi/nixos")
|
||||
mkdir_p("@efiSysMountPoint@/loader/entries")
|
||||
try:
|
||||
with open("/etc/machine-id") as machine_file:
|
||||
machine_id = machine_file.readlines()[0]
|
||||
except IOError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
machine_id = None
|
||||
|
||||
gens = get_generations("system")
|
||||
for gen in gens:
|
||||
add_entry(gen)
|
||||
if os.readlink(system_dir(gen)) == args.default_config:
|
||||
write_loader_conf(gen)
|
||||
|
||||
remove_old_entries(gens)
|
||||
@@ -0,0 +1,67 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
cfg = config.boot.loader.gummiboot;
|
||||
|
||||
efi = config.boot.loader.efi;
|
||||
|
||||
gummibootBuilder = pkgs.substituteAll {
|
||||
src = ./gummiboot-builder.py;
|
||||
|
||||
isExecutable = true;
|
||||
|
||||
inherit (pkgs) python gummiboot;
|
||||
|
||||
inherit (config.environment) nix;
|
||||
|
||||
inherit (cfg) timeout;
|
||||
|
||||
inherit (efi) efiSysMountPoint canTouchEfiVariables;
|
||||
};
|
||||
in {
|
||||
options.boot.loader.gummiboot = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
|
||||
type = types.bool;
|
||||
|
||||
description = "Whether to enable the gummiboot UEFI boot manager";
|
||||
};
|
||||
|
||||
timeout = mkOption {
|
||||
default = null;
|
||||
|
||||
example = 4;
|
||||
|
||||
type = types.nullOr types.int;
|
||||
|
||||
description = ''
|
||||
Timeout (in seconds) for how long to show the menu (null if none).
|
||||
Note that even with no timeout the menu can be forced if the space
|
||||
key is pressed during bootup
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
|
||||
|
||||
message = "This kernel does not support the EFI boot stub";
|
||||
}
|
||||
];
|
||||
|
||||
system = {
|
||||
build.installBootLoader = gummibootBuilder;
|
||||
|
||||
boot.loader.id = "gummiboot";
|
||||
|
||||
requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
(isYes "EFI_STUB")
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
if test $# -ne 1; then
|
||||
echo "Usage: init-script-builder.sh DEFAULT-CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
defaultConfig="$1"
|
||||
|
||||
|
||||
[ "$(stat -f -c '%i' /)" = "$(stat -f -c '%i' /boot)" ] || {
|
||||
# see grub-menu-builder.sh
|
||||
echo "WARNING: /boot being on a different filesystem not supported by init-script-builder.sh"
|
||||
}
|
||||
|
||||
|
||||
|
||||
target="/sbin/init"
|
||||
targetOther="/boot/init-other-configurations-contents.txt"
|
||||
|
||||
tmp="$target.tmp"
|
||||
tmpOther="$targetOther.tmp"
|
||||
|
||||
|
||||
configurationCounter=0
|
||||
numAlienEntries=`cat <<EOF | egrep '^[[:space:]]*title' | wc -l
|
||||
@extraEntries@
|
||||
EOF`
|
||||
|
||||
|
||||
|
||||
|
||||
# Add an entry to $targetOther
|
||||
addEntry() {
|
||||
local name="$1"
|
||||
local path="$2"
|
||||
local shortSuffix="$3"
|
||||
|
||||
configurationCounter=$((configurationCounter + 1))
|
||||
|
||||
local stage2=$path/init
|
||||
|
||||
content="$(
|
||||
echo "#!/bin/sh"
|
||||
echo "# $name"
|
||||
echo "# created by init-script-builder.sh"
|
||||
echo "export systemConfig=$(readlink -f $path)"
|
||||
echo "exec $stage2"
|
||||
)"
|
||||
|
||||
[ "$path" != "$defaultConfig" ] || {
|
||||
echo "$content" > $tmp
|
||||
echo "# older configurations: $targetOther" >> $tmp
|
||||
chmod +x $tmp
|
||||
}
|
||||
|
||||
echo -e "$content\n\n" >> $tmpOther
|
||||
}
|
||||
|
||||
|
||||
mkdir -p /boot /sbin
|
||||
|
||||
addEntry "NixOS - Default" $defaultConfig ""
|
||||
|
||||
# Add all generations of the system profile to the menu, in reverse
|
||||
# (most recent to least recent) order.
|
||||
for link in $((ls -d $defaultConfig/fine-tune/* ) | sort -n); do
|
||||
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
|
||||
addEntry "NixOS - variation" $link ""
|
||||
done
|
||||
|
||||
for generation in $(
|
||||
(cd /nix/var/nix/profiles && ls -d system-*-link) \
|
||||
| sed 's/system-\([0-9]\+\)-link/\1/' \
|
||||
| sort -n -r); do
|
||||
link=/nix/var/nix/profiles/system-$generation-link
|
||||
date=$(stat --printf="%y\n" $link | sed 's/\..*//')
|
||||
kernelVersion=$(cd $(dirname $(readlink -f $link/kernel))/lib/modules && echo *)
|
||||
addEntry "NixOS - Configuration $generation ($date - $kernelVersion)" $link "$generation ($date)"
|
||||
done
|
||||
|
||||
mv $tmpOther $targetOther
|
||||
mv $tmp $target
|
||||
@@ -0,0 +1,50 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
initScriptBuilder = pkgs.substituteAll {
|
||||
src = ./init-script-builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
boot.loader.initScript = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Some systems require a /sbin/init script which is started.
|
||||
Or having it makes starting NixOS easier.
|
||||
This applies to some kind of hosting services and user mode linux.
|
||||
|
||||
Additionally this script will create
|
||||
/boot/init-other-configurations-contents.txt containing
|
||||
contents of remaining configurations. You can copy paste them into
|
||||
/sbin/init manually running a rescue system or such.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.boot.loader.initScript.enable {
|
||||
|
||||
system.build.installBootLoader = initScriptBuilder;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#! @bash@/bin/sh -e
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
export PATH=/empty
|
||||
for i in @path@; do PATH=$PATH:$i/bin; done
|
||||
|
||||
default=$1
|
||||
if test -z "$1"; then
|
||||
echo "Syntax: builder.sh <DEFAULT-CONFIG>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "updating the boot generations directory..."
|
||||
|
||||
mkdir -p /boot/old
|
||||
|
||||
# Convert a path to a file in the Nix store such as
|
||||
# /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
|
||||
cleanName() {
|
||||
local path="$1"
|
||||
echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
|
||||
}
|
||||
|
||||
# Copy a file from the Nix store to /boot/kernels.
|
||||
declare -A filesCopied
|
||||
|
||||
copyToKernelsDir() {
|
||||
local src="$1"
|
||||
local dst="/boot/old/$(cleanName $src)"
|
||||
# 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 ! test -e $dst; then
|
||||
local dstTmp=$dst.tmp.$$
|
||||
cp $src $dstTmp
|
||||
mv $dstTmp $dst
|
||||
fi
|
||||
filesCopied[$dst]=1
|
||||
result=$dst
|
||||
}
|
||||
|
||||
copyForced() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
cp $src $dst.tmp
|
||||
mv $dst.tmp $dst
|
||||
}
|
||||
|
||||
outdir=/boot/old
|
||||
mkdir -p $outdir || true
|
||||
|
||||
# Copy its kernel and initrd to /boot/kernels.
|
||||
addEntry() {
|
||||
local path="$1"
|
||||
local generation="$2"
|
||||
|
||||
if ! test -e $path/kernel -a -e $path/initrd; then
|
||||
return
|
||||
fi
|
||||
|
||||
local kernel=$(readlink -f $path/kernel)
|
||||
# local initrd=$(readlink -f $path/initrd)
|
||||
|
||||
if test -n "@copyKernels@"; then
|
||||
copyToKernelsDir $kernel; kernel=$result
|
||||
# copyToKernelsDir $initrd; initrd=$result
|
||||
fi
|
||||
|
||||
echo $(readlink -f $path) > $outdir/$generation-system
|
||||
echo $(readlink -f $path/init) > $outdir/$generation-init
|
||||
cp $path/kernel-params $outdir/$generation-cmdline.txt
|
||||
# echo $initrd > $outdir/$generation-initrd
|
||||
echo $kernel > $outdir/$generation-kernel
|
||||
|
||||
if test $(readlink -f "$path") = "$default"; then
|
||||
copyForced $kernel /boot/kernel.img
|
||||
# copyForced $initrd /boot/initrd
|
||||
cp "$(readlink -f "$path/init")" /boot/nixos-init
|
||||
echo "`cat $path/kernel-params` init=$path/init" >/boot/cmdline.txt
|
||||
|
||||
echo "$2" > /boot/defaultgeneration
|
||||
fi
|
||||
}
|
||||
|
||||
# Add all generations of the system profile to the menu, in reverse
|
||||
# (most recent to least recent) order.
|
||||
for generation in $(
|
||||
(cd /nix/var/nix/profiles && ls -d system-*-link) \
|
||||
| sed 's/system-\([0-9]\+\)-link/\1/' \
|
||||
| sort -n -r); do
|
||||
link=/nix/var/nix/profiles/system-$generation-link
|
||||
addEntry $link $generation
|
||||
done
|
||||
|
||||
# Add the firmware files
|
||||
fwdir=@firmware@/share/raspberrypi/boot/
|
||||
copyForced $fwdir/bootcode.bin /boot/bootcode.bin
|
||||
copyForced $fwdir/fixup.dat /boot/fixup.dat
|
||||
copyForced $fwdir/fixup_cd.dat /boot/fixup_cd.dat
|
||||
copyForced $fwdir/start.elf /boot/start.elf
|
||||
copyForced $fwdir/start_cd.elf /boot/start_cd.elf
|
||||
|
||||
# Remove obsolete files from /boot/old.
|
||||
for fn in /boot/old/*linux* /boot/old/*initrd*; do
|
||||
if ! test "${filesCopied[$fn]}" = 1; then
|
||||
rm -vf -- "$fn"
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,38 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
builder = pkgs.substituteAll {
|
||||
src = ./builder.sh;
|
||||
isExecutable = true;
|
||||
inherit (pkgs) bash;
|
||||
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
|
||||
firmware = pkgs.raspberrypifw;
|
||||
};
|
||||
|
||||
platform = pkgs.stdenv.platform;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.loader.raspberryPi.enable = mkOption {
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to create files with the system generations in
|
||||
<literal>/boot</literal>.
|
||||
<literal>/boot/old</literal> will hold files from old generations.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf config.boot.loader.raspberryPi.enable {
|
||||
system.build.installBootLoader = builder;
|
||||
system.boot.loader.id = "raspberrypi";
|
||||
system.boot.loader.kernelFile = platform.kernelTarget;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
luks = config.boot.initrd.luks;
|
||||
|
||||
openCommand = { name, device, keyFile, keyFileSize, allowDiscards, ... }: ''
|
||||
# Wait for luksRoot to appear, e.g. if on a usb drive.
|
||||
# XXX: copied and adapted from stage-1-init.sh - should be
|
||||
# available as a function.
|
||||
if ! test -e ${device}; then
|
||||
echo -n "waiting 10 seconds for device ${device} to appear..."
|
||||
for try in $(seq 10); do
|
||||
sleep 1
|
||||
if test -e ${device}; then break; fi
|
||||
echo -n .
|
||||
done
|
||||
echo "ok"
|
||||
fi
|
||||
|
||||
${optionalString (keyFile != null) ''
|
||||
if ! test -e ${keyFile}; then
|
||||
echo -n "waiting 10 seconds for key file ${keyFile} to appear..."
|
||||
for try in $(seq 10); do
|
||||
sleep 1
|
||||
if test -e ${keyFile}; then break; fi
|
||||
echo -n .
|
||||
done
|
||||
echo "ok"
|
||||
fi
|
||||
''}
|
||||
|
||||
# open luksRoot and scan for logical volumes
|
||||
cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
|
||||
${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"}
|
||||
'';
|
||||
|
||||
isPreLVM = f: f.preLVM;
|
||||
preLVM = filter isPreLVM luks.devices;
|
||||
postLVM = filter (f: !(isPreLVM f)) luks.devices;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options = {
|
||||
boot.initrd.luks.enable = mkOption {
|
||||
default = false;
|
||||
description = "Obsolete.";
|
||||
};
|
||||
|
||||
boot.initrd.luks.mitigateDMAAttacks = mkOption {
|
||||
default = true;
|
||||
description = ''
|
||||
Unless enabled, encryption keys can be easily recovered by an attacker with physical
|
||||
access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
|
||||
More information: http://en.wikipedia.org/wiki/DMA_attack
|
||||
|
||||
This option blacklists FireWire drivers, but doesn't remove them. You can manually
|
||||
load the drivers if you need to use a FireWire device, but don't forget to unload them!
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.luks.cryptoModules = mkOption {
|
||||
default =
|
||||
[ "aes" "aes_generic" "blowfish" "twofish"
|
||||
"serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
|
||||
(if pkgs.stdenv.system == "x86_64-linux" then "aes_x86_64" else "aes_i586")
|
||||
];
|
||||
description = ''
|
||||
A list of cryptographic kernel modules needed to decrypt the root device(s).
|
||||
The default includes all common modules.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.luks.devices = mkOption {
|
||||
default = [ ];
|
||||
example = [ { name = "luksroot"; device = "/dev/sda3"; preLVM = true; } ];
|
||||
description = ''
|
||||
The list of devices that should be decrypted using LUKS before trying to mount the
|
||||
root partition. This works for both LVM-over-LUKS and LUKS-over-LVM setups.
|
||||
|
||||
The devices are decrypted to the device mapper names defined.
|
||||
|
||||
Make sure that initrd has the crypto modules needed for decryption.
|
||||
'';
|
||||
|
||||
type = types.listOf types.optionSet;
|
||||
|
||||
options = {
|
||||
|
||||
name = mkOption {
|
||||
example = "luksroot";
|
||||
type = types.string;
|
||||
description = "Named to be used for the generated device in /dev/mapper.";
|
||||
};
|
||||
|
||||
device = mkOption {
|
||||
example = "/dev/sda2";
|
||||
type = types.string;
|
||||
description = "Path of the underlying block device.";
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
default = null;
|
||||
example = "/dev/sdb1";
|
||||
type = types.nullOr types.string;
|
||||
description = ''
|
||||
The name of the file (can be a raw device or a partition) that
|
||||
should be used as the decryption key for the encrypted device. If
|
||||
not specified, you will be prompted for a passphrase instead.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFileSize = mkOption {
|
||||
default = null;
|
||||
example = 4096;
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
The size of the key file. Use this if only the beginning of the
|
||||
key file should be used as a key (often the case if a raw device
|
||||
or partition is used as key file). If not specified, the whole
|
||||
<literal>keyFile</literal> will be used decryption, instead of just
|
||||
the first <literal>keyFileSize</literal> bytes.
|
||||
'';
|
||||
};
|
||||
|
||||
preLVM = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = "Whether the luksOpen will be attempted before LVM scan or after it.";
|
||||
};
|
||||
|
||||
allowDiscards = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to allow TRIM requests to the underlying device. This option
|
||||
has security implications, please read the LUKS documentation before
|
||||
activating in.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (luks.devices != []) {
|
||||
|
||||
# actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
|
||||
boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
|
||||
["firewire_ohci" "firewire_core" "firewire_sbp2"];
|
||||
|
||||
# Some modules that may be needed for mounting anything ciphered
|
||||
boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" ] ++ luks.cryptoModules;
|
||||
|
||||
# copy the cryptsetup binary and it's dependencies
|
||||
boot.initrd.extraUtilsCommands = ''
|
||||
cp -pdv ${pkgs.cryptsetup}/sbin/cryptsetup $out/bin
|
||||
# XXX: do we have a function that does this?
|
||||
for lib in $(ldd $out/bin/cryptsetup |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do
|
||||
cp -pdvn $lib $out/lib
|
||||
cp -pvn $(readlink -f $lib) $out/lib
|
||||
done
|
||||
'';
|
||||
|
||||
boot.initrd.extraUtilsCommandsTest = ''
|
||||
$out/bin/cryptsetup --version
|
||||
'';
|
||||
|
||||
boot.initrd.preLVMCommands = concatMapStrings openCommand preLVM;
|
||||
boot.initrd.postDeviceCommands = concatMapStrings openCommand postLVM;
|
||||
|
||||
environment.systemPackages = [ pkgs.cryptsetup ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
system.sbin.modprobe = mkOption {
|
||||
internal = true;
|
||||
default = pkgs.writeTextFile {
|
||||
name = "modprobe";
|
||||
destination = "/sbin/modprobe";
|
||||
executable = true;
|
||||
text =
|
||||
''
|
||||
#! ${pkgs.stdenv.shell}
|
||||
export MODULE_DIR=/run/current-system/kernel-modules/lib/modules
|
||||
|
||||
# Fall back to the kernel modules used at boot time if the
|
||||
# modules in the current configuration don't match the
|
||||
# running kernel.
|
||||
if [ ! -d "$MODULE_DIR/$(${pkgs.coreutils}/bin/uname -r)" ]; then
|
||||
MODULE_DIR=/run/booted-system/kernel-modules/lib/modules/
|
||||
fi
|
||||
|
||||
exec ${pkgs.kmod}/sbin/modprobe "$@"
|
||||
'';
|
||||
};
|
||||
description = ''
|
||||
Wrapper around modprobe that sets the path to the modules
|
||||
tree.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.blacklistedKernelModules = mkOption {
|
||||
default = [];
|
||||
example = [ "cirrusfb" "i2c_piix4" ];
|
||||
description = ''
|
||||
List of names of kernel modules that should not be loaded
|
||||
automatically by the hardware probing code.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.extraModprobeConfig = mkOption {
|
||||
default = "";
|
||||
example =
|
||||
''
|
||||
options parport_pc io=0x378 irq=7 dma=1
|
||||
'';
|
||||
description = ''
|
||||
Any additional configuration to be appended to the generated
|
||||
<filename>modprobe.conf</filename>. This is typically used to
|
||||
specify module options. See
|
||||
<citerefentry><refentrytitle>modprobe.conf</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
type = types.lines;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
environment.etc = singleton
|
||||
{ source = pkgs.writeText "modprobe.conf"
|
||||
''
|
||||
${flip concatMapStrings config.boot.blacklistedKernelModules (name: ''
|
||||
blacklist ${name}
|
||||
'')}
|
||||
${config.boot.extraModprobeConfig}
|
||||
'';
|
||||
target = "modprobe.d/nixos.conf";
|
||||
};
|
||||
|
||||
environment.systemPackages = [ config.system.sbin.modprobe pkgs.kmod ];
|
||||
|
||||
boot.blacklistedKernelModules =
|
||||
[ # This module is for debugging and generates gigantic amounts
|
||||
# of log output, so it should never be loaded automatically.
|
||||
"evbug"
|
||||
|
||||
# This module causes ALSA to occassionally select the wrong
|
||||
# default sound device, and is little more than an annoyance
|
||||
# on modern machines.
|
||||
"snd_pcsp"
|
||||
|
||||
# The cirrusfb module prevents X11 from starting. FIXME:
|
||||
# Ubuntu blacklists all framebuffer devices because they're
|
||||
# "buggy" and cause suspend problems. Maybe we should too?
|
||||
"cirrusfb"
|
||||
];
|
||||
|
||||
system.activationScripts.modprobe =
|
||||
''
|
||||
# Allow the kernel to find our wrapped modprobe (which searches
|
||||
# in the right location in the Nix store for kernel modules).
|
||||
# We need this when the kernel (or some module) auto-loads a
|
||||
# module.
|
||||
echo ${config.system.sbin.modprobe}/sbin/modprobe > /proc/sys/kernel/modprobe
|
||||
'';
|
||||
|
||||
environment.variables.MODULE_DIR = "/run/current-system/kernel-modules/lib/modules";
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
struct statvfs stat;
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s PATH", argv[0]);
|
||||
exit(2);
|
||||
}
|
||||
if (statvfs(argv[1], &stat) != 0) {
|
||||
perror("statvfs");
|
||||
exit(3);
|
||||
}
|
||||
if (stat.f_flag & ST_RDONLY)
|
||||
exit(0);
|
||||
else
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
{
|
||||
|
||||
# This unit saves the value of the system clock to the hardware
|
||||
# clock on shutdown.
|
||||
systemd.units."save-hwclock.service" =
|
||||
{ wantedBy = [ "shutdown.target" ];
|
||||
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
Description=Save Hardware Clock
|
||||
DefaultDependencies=no
|
||||
Before=shutdown.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=${pkgs.utillinux}/sbin/hwclock --systohc ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}
|
||||
'';
|
||||
};
|
||||
|
||||
boot.kernel.sysctl."kernel.poweroff_cmd" = "${config.systemd.package}/sbin/poweroff";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
#! @shell@
|
||||
|
||||
targetRoot=/mnt-root
|
||||
console=tty1
|
||||
|
||||
export LD_LIBRARY_PATH=@extraUtils@/lib
|
||||
export PATH=@extraUtils@/bin:@extraUtils@/sbin
|
||||
|
||||
|
||||
fail() {
|
||||
if [ -n "$panicOnFail" ]; then exit 1; fi
|
||||
|
||||
# If starting stage 2 failed, allow the user to repair the problem
|
||||
# in an interactive shell.
|
||||
cat <<EOF
|
||||
|
||||
An error occured in stage 1 of the boot process, which must mount the
|
||||
root filesystem on \`$targetRoot' and then start stage 2. Press one
|
||||
of the following keys:
|
||||
|
||||
EOF
|
||||
if [ -n "$allowShell" ]; then cat <<EOF
|
||||
i) to launch an interactive shell
|
||||
f) to start an interactive shell having pid 1 (needed if you want to
|
||||
start stage 2's init manually)
|
||||
EOF
|
||||
fi
|
||||
cat <<EOF
|
||||
r) to reboot immediately
|
||||
*) to ignore the error and continue
|
||||
EOF
|
||||
|
||||
read reply
|
||||
|
||||
if [ -n "$allowShell" -a "$reply" = f ]; then
|
||||
exec setsid @shell@ -c "@shell@ < /dev/$console >/dev/$console 2>/dev/$console"
|
||||
elif [ -n "$allowShell" -a "$reply" = i ]; then
|
||||
echo "Starting interactive shell..."
|
||||
setsid @shell@ -c "@shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
|
||||
elif [ "$reply" = r ]; then
|
||||
echo "Rebooting..."
|
||||
reboot -f
|
||||
else
|
||||
echo "Continuing..."
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'fail' 0
|
||||
|
||||
|
||||
# Print a greeting.
|
||||
echo
|
||||
echo "[1;32m<<< NixOS Stage 1 >>>[0m"
|
||||
echo
|
||||
|
||||
|
||||
# Mount special file systems.
|
||||
mkdir -p /etc
|
||||
touch /etc/fstab # to shut up mount
|
||||
touch /etc/mtab # to shut up mke2fs
|
||||
mkdir -p /proc
|
||||
mount -t proc none /proc
|
||||
mkdir -p /sys
|
||||
mount -t sysfs none /sys
|
||||
mount -t devtmpfs -o "size=@devSize@" none /dev
|
||||
mkdir -p /run
|
||||
mount -t tmpfs -o "mode=0755,size=@runSize@" none /run
|
||||
|
||||
|
||||
# Process the kernel command line.
|
||||
export stage2Init=/init
|
||||
for o in $(cat /proc/cmdline); do
|
||||
case $o in
|
||||
console=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
params=$2
|
||||
set -- $(IFS=,; echo $params)
|
||||
console=$1
|
||||
;;
|
||||
init=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
stage2Init=$2
|
||||
;;
|
||||
boot.trace|debugtrace)
|
||||
# Show each command.
|
||||
set -x
|
||||
;;
|
||||
boot.shell_on_fail)
|
||||
allowShell=1
|
||||
;;
|
||||
boot.debug1|debug1) # stop right away
|
||||
allowShell=1
|
||||
fail
|
||||
;;
|
||||
boot.debug1devices) # stop after loading modules and creating device nodes
|
||||
allowShell=1
|
||||
debug1devices=1
|
||||
;;
|
||||
boot.debug1mounts) # stop after mounting file systems
|
||||
allowShell=1
|
||||
debug1mounts=1
|
||||
;;
|
||||
boot.panic_on_fail|stage1panic=1)
|
||||
panicOnFail=1
|
||||
;;
|
||||
root=*)
|
||||
# If a root device is specified on the kernel command
|
||||
# line, make it available through the symlink /dev/root.
|
||||
# Recognise LABEL= and UUID= to support UNetbootin.
|
||||
set -- $(IFS==; echo $o)
|
||||
if [ $2 = "LABEL" ]; then
|
||||
root="/dev/disk/by-label/$3"
|
||||
elif [ $2 = "UUID" ]; then
|
||||
root="/dev/disk/by-uuid/$3"
|
||||
else
|
||||
root=$2
|
||||
fi
|
||||
ln -s "$root" /dev/root
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# Load the required kernel modules.
|
||||
mkdir -p /lib
|
||||
ln -s @modulesClosure@/lib/modules /lib/modules
|
||||
echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
|
||||
for i in @kernelModules@; do
|
||||
echo "loading module $(basename $i)..."
|
||||
modprobe $i || true
|
||||
done
|
||||
|
||||
|
||||
# Create device nodes in /dev.
|
||||
echo "running udev..."
|
||||
mkdir -p /etc/udev
|
||||
ln -sfn @udevRules@ /etc/udev/rules.d
|
||||
mkdir -p /dev/.mdadm
|
||||
systemd-udevd --daemon
|
||||
udevadm trigger --action=add
|
||||
udevadm settle || true
|
||||
modprobe scsi_wait_scan || true
|
||||
udevadm settle || true
|
||||
|
||||
|
||||
# Load boot-time keymap before any LVM/LUKS initialization
|
||||
@extraUtils@/bin/busybox loadkmap < "@busyboxKeymap@"
|
||||
|
||||
|
||||
# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
|
||||
@preLVMCommands@
|
||||
|
||||
|
||||
echo "starting device mapper and LVM..."
|
||||
lvm vgchange -ay
|
||||
|
||||
if test -n "$debug1devices"; then fail; fi
|
||||
|
||||
|
||||
@postDeviceCommands@
|
||||
|
||||
|
||||
# Try to resume - all modules are loaded now, and devices exist
|
||||
if test -e /sys/power/tuxonice/resume; then
|
||||
if test -n "$(cat /sys/power/tuxonice/resume)"; then
|
||||
echo 0 > /sys/power/tuxonice/user_interface/enabled
|
||||
echo 1 > /sys/power/tuxonice/do_resume || echo "failed to resume..."
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -e /sys/power/resume -a -e /sys/power/disk; then
|
||||
echo "@resumeDevice@" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
|
||||
echo shutdown > /sys/power/disk
|
||||
fi
|
||||
|
||||
|
||||
# Return true if the machine is on AC power, or if we can't determine
|
||||
# whether it's on AC power.
|
||||
onACPower() {
|
||||
! test -d "/proc/acpi/battery" ||
|
||||
! ls /proc/acpi/battery/BAT[0-9]* > /dev/null 2>&1 ||
|
||||
! cat /proc/acpi/battery/BAT*/state | grep "^charging state" | grep -q "discharg"
|
||||
}
|
||||
|
||||
|
||||
# Check the specified file system, if appropriate.
|
||||
checkFS() {
|
||||
local device="$1"
|
||||
local fsType="$2"
|
||||
|
||||
# Only check block devices.
|
||||
if [ ! -b "$device" ]; then return 0; fi
|
||||
|
||||
# Don't check ROM filesystems.
|
||||
if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
|
||||
|
||||
# If we couldn't figure out the FS type, then skip fsck.
|
||||
if [ "$fsType" = auto ]; then
|
||||
echo 'cannot check filesystem with type "auto"!'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Optionally, skip fsck on journaling filesystems. This option is
|
||||
# a hack - it's mostly because e2fsck on ext3 takes much longer to
|
||||
# recover the journal than the ext3 implementation in the kernel
|
||||
# does (minutes versus seconds).
|
||||
if test -z "@checkJournalingFS@" -a \
|
||||
\( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
|
||||
-o "$fsType" = xfs -o "$fsType" = jfs \)
|
||||
then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Don't run `fsck' if the machine is on battery power. !!! Is
|
||||
# this a good idea?
|
||||
if ! onACPower; then
|
||||
echo "on battery power, so no \`fsck' will be performed on \`$device'"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "checking $device..."
|
||||
|
||||
fsckFlags=
|
||||
if test "$fsType" != "btrfs"; then
|
||||
fsckFlags="-V -a"
|
||||
fi
|
||||
fsck $fsckFlags "$device"
|
||||
fsckResult=$?
|
||||
|
||||
if test $(($fsckResult | 2)) = $fsckResult; then
|
||||
echo "fsck finished, rebooting..."
|
||||
sleep 3
|
||||
reboot -f
|
||||
fi
|
||||
|
||||
if test $(($fsckResult | 4)) = $fsckResult; then
|
||||
echo "$device has unrepaired errors, please fix them manually."
|
||||
fail
|
||||
fi
|
||||
|
||||
if test $fsckResult -ge 8; then
|
||||
echo "fsck on $device failed."
|
||||
fail
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Function for mounting a file system.
|
||||
mountFS() {
|
||||
local device="$1"
|
||||
local mountPoint="$2"
|
||||
local options="$3"
|
||||
local fsType="$4"
|
||||
|
||||
if [ "$fsType" = auto ]; then
|
||||
fsType=$(blkid -o value -s TYPE "$device")
|
||||
if [ -z "$fsType" ]; then fsType=auto; fi
|
||||
fi
|
||||
|
||||
echo "$device /mnt-root$mountPoint $fsType $options" >> /etc/fstab
|
||||
|
||||
checkFS "$device" "$fsType"
|
||||
|
||||
echo "mounting $device on $mountPoint..."
|
||||
|
||||
mkdir -p "/mnt-root$mountPoint" || true
|
||||
|
||||
# For CIFS mounts, retry a few times before giving up.
|
||||
local n=0
|
||||
while true; do
|
||||
mount "/mnt-root$mountPoint" && break
|
||||
if [ "$fsType" != cifs -o "$n" -ge 10 ]; then fail; break; fi
|
||||
echo "retrying..."
|
||||
n=$((n + 1))
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# Try to find and mount the root device.
|
||||
mkdir /mnt-root
|
||||
|
||||
exec 3< @fsInfo@
|
||||
|
||||
while read -u 3 mountPoint; do
|
||||
read -u 3 device
|
||||
read -u 3 fsType
|
||||
read -u 3 options
|
||||
|
||||
# !!! Really quick hack to support bind mounts, i.e., where the
|
||||
# "device" should be taken relative to /mnt-root, not /. Assume
|
||||
# that every device that starts with / but doesn't start with /dev
|
||||
# is a bind mount.
|
||||
pseudoDevice=
|
||||
case $device in
|
||||
/dev/*)
|
||||
;;
|
||||
//*)
|
||||
# Don't touch SMB/CIFS paths.
|
||||
pseudoDevice=1
|
||||
;;
|
||||
/*)
|
||||
device=/mnt-root$device
|
||||
;;
|
||||
*)
|
||||
# Not an absolute path; assume that it's a pseudo-device
|
||||
# like an NFS path (e.g. "server:/path").
|
||||
pseudoDevice=1
|
||||
;;
|
||||
esac
|
||||
|
||||
# USB storage devices tend to appear with some delay. It would be
|
||||
# great if we had a way to synchronously wait for them, but
|
||||
# alas... So just wait for a few seconds for the device to
|
||||
# appear. If it doesn't appear, try to mount it anyway (and
|
||||
# probably fail). This is a fallback for non-device "devices"
|
||||
# that we don't properly recognise.
|
||||
if test -z "$pseudoDevice" -a ! -e $device; then
|
||||
echo -n "waiting for device $device to appear..."
|
||||
for try in $(seq 1 20); do
|
||||
sleep 1
|
||||
if test -e $device; then break; fi
|
||||
echo -n "."
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
# Wait once more for the udev queue to empty, just in case it's
|
||||
# doing something with $device right now.
|
||||
udevadm settle || true
|
||||
|
||||
mountFS "$device" "$mountPoint" "$options" "$fsType"
|
||||
done
|
||||
|
||||
exec 3>&-
|
||||
|
||||
|
||||
@postMountCommands@
|
||||
|
||||
|
||||
# Stop udevd.
|
||||
udevadm control --exit || true
|
||||
|
||||
# Kill any remaining processes, just to be sure we're not taking any
|
||||
# with us into stage 2. unionfs-fuse mounts require the unionfs process.
|
||||
pkill -9 -v '(1|unionfs)'
|
||||
|
||||
|
||||
if test -n "$debug1mounts"; then fail; fi
|
||||
|
||||
|
||||
# Restore /proc/sys/kernel/modprobe to its original value.
|
||||
echo /sbin/modprobe > /proc/sys/kernel/modprobe
|
||||
|
||||
|
||||
# Start stage 2. `switch_root' deletes all files in the ramfs on the
|
||||
# current root. Note that $stage2Init might be an absolute symlink,
|
||||
# in which case "-e" won't work because we're not in the chroot yet.
|
||||
if ! test -e "$targetRoot/$stage2Init" -o -L "$targetRoot/$stage2Init"; then
|
||||
echo "stage 2 init script ($targetRoot/$stage2Init) not found"
|
||||
fail
|
||||
fi
|
||||
|
||||
mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
|
||||
|
||||
mount --move /proc $targetRoot/proc
|
||||
mount --move /sys $targetRoot/sys
|
||||
mount --move /dev $targetRoot/dev
|
||||
mount --move /run $targetRoot/run
|
||||
|
||||
exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
|
||||
|
||||
fail # should never be reached
|
||||
@@ -0,0 +1,343 @@
|
||||
# This module builds the initial ramdisk, which contains an init
|
||||
# script that performs the first stage of booting the system: it loads
|
||||
# the modules necessary to mount the root file system, then calls the
|
||||
# init in the root file system to start the second boot stage.
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
udev = config.systemd.package;
|
||||
|
||||
kernelPackages = config.boot.kernelPackages;
|
||||
modulesTree = config.system.modulesTree;
|
||||
|
||||
|
||||
# Determine the set of modules that we need to mount the root FS.
|
||||
modulesClosure = pkgs.makeModulesClosure {
|
||||
rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules;
|
||||
kernel = modulesTree;
|
||||
allowMissing = true;
|
||||
};
|
||||
|
||||
|
||||
needsCifsUtils = kernelPackages.kernel ? features
|
||||
&& kernelPackages.kernel.features ? needsCifsUtils
|
||||
&& kernelPackages.kernel.features.needsCifsUtils
|
||||
&& any (fs: fs.fsType == "cifs") fileSystems;
|
||||
|
||||
busybox =
|
||||
if needsCifsUtils
|
||||
then pkgs.busybox.override {
|
||||
extraConfig = ''
|
||||
CONFIG_FEATURE_MOUNT_CIFS n
|
||||
CONFIG_FEATURE_MOUNT_HELPERS y
|
||||
'';
|
||||
}
|
||||
else pkgs.busybox;
|
||||
|
||||
|
||||
# Some additional utilities needed in stage 1, like mount, lvm, fsck
|
||||
# etc. We don't want to bring in all of those packages, so we just
|
||||
# copy what we need. Instead of using statically linked binaries,
|
||||
# we just copy what we need from Glibc and use patchelf to make it
|
||||
# work.
|
||||
extraUtils = pkgs.runCommand "extra-utils"
|
||||
{ buildInputs = [pkgs.nukeReferences];
|
||||
allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd
|
||||
doublePatchelf = pkgs.stdenv.isArm;
|
||||
}
|
||||
''
|
||||
mkdir -p $out/bin $out/lib
|
||||
|
||||
# Copy what we need from Glibc.
|
||||
cp -pv ${pkgs.glibc}/lib/ld*.so.? $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/libc.so.* $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/libm.so.* $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/libpthread.so.* $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/librt.so.* $out/lib
|
||||
cp -pv ${pkgs.glibc}/lib/libdl.so.* $out/lib
|
||||
cp -pv ${pkgs.gcc.gcc}/lib*/libgcc_s.so.* $out/lib
|
||||
|
||||
# Copy BusyBox.
|
||||
cp -rvd ${busybox}/{bin,sbin} $out/
|
||||
chmod -R u+w $out
|
||||
|
||||
# Copy some utillinux stuff.
|
||||
cp -v ${pkgs.utillinux}/sbin/blkid $out/bin
|
||||
cp -pdv ${pkgs.utillinux}/lib/libblkid*.so.* $out/lib
|
||||
cp -pdv ${pkgs.utillinux}/lib/libuuid*.so.* $out/lib
|
||||
|
||||
# Copy dmsetup and lvm.
|
||||
cp -v ${pkgs.lvm2}/sbin/dmsetup $out/bin/dmsetup
|
||||
cp -v ${pkgs.lvm2}/sbin/lvm $out/bin/lvm
|
||||
cp -v ${pkgs.lvm2}/lib/libdevmapper.so.*.* $out/lib
|
||||
cp -v ${pkgs.systemd}/lib/libsystemd-daemon.so.* $out/lib
|
||||
|
||||
# Add RAID mdadm tool.
|
||||
cp -v ${pkgs.mdadm}/sbin/mdadm $out/bin/mdadm
|
||||
|
||||
# Copy udev.
|
||||
cp -v ${udev}/lib/systemd/systemd-udevd ${udev}/bin/udevadm $out/bin
|
||||
cp -v ${udev}/lib/udev/*_id $out/bin
|
||||
cp -pdv ${udev}/lib/libudev.so.* $out/lib
|
||||
cp -v ${pkgs.kmod}/lib/libkmod.so.* $out/lib
|
||||
cp -v ${pkgs.acl}/lib/libacl.so.* $out/lib
|
||||
cp -v ${pkgs.attr}/lib/libattr.so.* $out/lib
|
||||
|
||||
# Copy modprobe.
|
||||
cp -v ${pkgs.kmod}/bin/kmod $out/bin/
|
||||
ln -s kmod $out/bin/modprobe
|
||||
|
||||
# Maybe copy cifs utils
|
||||
${optionalString needsCifsUtils ''
|
||||
cp -v ${pkgs.cifs_utils}/sbin/mount.cifs $out/bin
|
||||
''}
|
||||
|
||||
${config.boot.initrd.extraUtilsCommands}
|
||||
|
||||
# Strip binaries further than normal.
|
||||
chmod -R u+w $out
|
||||
stripDirs "lib bin" "-s"
|
||||
|
||||
# Run patchelf to make the programs refer to the copied libraries.
|
||||
for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done
|
||||
|
||||
for i in $out/bin/*; do
|
||||
if ! test -L $i; then
|
||||
echo "patching $i..."
|
||||
patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
|
||||
if [ -n "$doublePatchelf" ]; then
|
||||
patchelf --set-interpreter $out/lib/ld*.so.? --set-rpath $out/lib $i || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Make sure that the patchelf'ed binaries still work.
|
||||
echo "testing patched programs..."
|
||||
$out/bin/ash -c 'echo hello world' | grep "hello world"
|
||||
export LD_LIBRARY_PATH=$out/lib
|
||||
$out/bin/mount --help 2>&1 | grep "BusyBox"
|
||||
$out/bin/udevadm --version
|
||||
$out/bin/dmsetup --version 2>&1 | tee -a log | grep "version:"
|
||||
LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep "LVM"
|
||||
$out/bin/mdadm --version
|
||||
|
||||
${config.boot.initrd.extraUtilsCommandsTest}
|
||||
''; # */
|
||||
|
||||
|
||||
# The initrd only has to mount / or any FS marked as necessary for
|
||||
# booting (such as the FS containing /nix/store, or an FS needed for
|
||||
# mounting /, like / on a loopback).
|
||||
fileSystems = filter
|
||||
(fs: fs.neededForBoot || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ])
|
||||
(attrValues config.fileSystems);
|
||||
|
||||
|
||||
udevRules = pkgs.stdenv.mkDerivation {
|
||||
name = "udev-rules";
|
||||
buildCommand = ''
|
||||
ensureDir $out
|
||||
|
||||
echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules
|
||||
|
||||
cp -v ${udev}/lib/udev/rules.d/60-cdrom_id.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/60-persistent-storage.rules $out/
|
||||
cp -v ${udev}/lib/udev/rules.d/80-drivers.rules $out/
|
||||
cp -v ${pkgs.lvm2}/lib/udev/rules.d/*.rules $out/
|
||||
cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
|
||||
|
||||
for i in $out/*.rules; do
|
||||
substituteInPlace $i \
|
||||
--replace ata_id ${extraUtils}/bin/ata_id \
|
||||
--replace scsi_id ${extraUtils}/bin/scsi_id \
|
||||
--replace cdrom_id ${extraUtils}/bin/cdrom_id \
|
||||
--replace ${pkgs.utillinux}/sbin/blkid ${extraUtils}/bin/blkid \
|
||||
--replace /sbin/blkid ${extraUtils}/bin/blkid \
|
||||
--replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \
|
||||
--replace /sbin/mdadm ${extraUtils}/bin/mdadm
|
||||
done
|
||||
|
||||
# Work around a bug in QEMU, which doesn't implement the "READ
|
||||
# DISC INFORMATION" SCSI command:
|
||||
# https://bugzilla.redhat.com/show_bug.cgi?id=609049
|
||||
# As a result, `cdrom_id' doesn't print
|
||||
# ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the
|
||||
# /dev/disk/by-label symlinks from being created. We need these
|
||||
# in the NixOS installation CD, so use ID_CDROM_MEDIA in the
|
||||
# corresponding udev rules for now. This was the behaviour in
|
||||
# udev <= 154. See also
|
||||
# http://www.spinics.net/lists/hotplug/msg03935.html
|
||||
substituteInPlace $out/60-persistent-storage.rules \
|
||||
--replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
|
||||
''; # */
|
||||
};
|
||||
|
||||
|
||||
# The binary keymap for busybox to load at boot.
|
||||
busyboxKeymap = pkgs.runCommand "boottime-keymap"
|
||||
{ preferLocalBuild = true; }
|
||||
''
|
||||
${pkgs.kbd}/bin/loadkeys -qb "${config.i18n.consoleKeyMap}" > $out ||
|
||||
${pkgs.kbd}/bin/loadkeys -qbu "${config.i18n.consoleKeyMap}" > $out
|
||||
'';
|
||||
|
||||
|
||||
# The init script of boot stage 1 (loading kernel modules for
|
||||
# mounting the root FS).
|
||||
bootStage1 = pkgs.substituteAll {
|
||||
src = ./stage-1-init.sh;
|
||||
|
||||
shell = "${extraUtils}/bin/ash";
|
||||
|
||||
isExecutable = true;
|
||||
|
||||
inherit udevRules extraUtils modulesClosure busyboxKeymap;
|
||||
|
||||
inherit (config.boot) resumeDevice devSize runSize;
|
||||
|
||||
inherit (config.boot.initrd) checkJournalingFS
|
||||
preLVMCommands postDeviceCommands postMountCommands kernelModules;
|
||||
|
||||
fsInfo =
|
||||
let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType fs.options ];
|
||||
in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
|
||||
};
|
||||
|
||||
|
||||
# The closure of the init script of boot stage 1 is what we put in
|
||||
# the initial RAM disk.
|
||||
initialRamdisk = pkgs.makeInitrd {
|
||||
inherit (config.boot.initrd) compressor;
|
||||
|
||||
contents =
|
||||
[ { object = bootStage1;
|
||||
symlink = "/init";
|
||||
}
|
||||
{ object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf;
|
||||
symlink = "/etc/mdadm.conf";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot.resumeDevice = mkOption {
|
||||
default = "";
|
||||
example = "0:0";
|
||||
description = "
|
||||
Device for manual resume attempt during boot. Looks like
|
||||
major:minor. ls -l /dev/SWAP_PARTION shows them.
|
||||
";
|
||||
};
|
||||
|
||||
boot.initrd.checkJournalingFS = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to run fsck on journaling filesystems such as ext3.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.mdadmConf = mkOption {
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Contents of /etc/mdadm.conf at initrd.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.preLVMCommands = mkOption {
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Shell commands to be executed immediately before lvm discovery.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.postDeviceCommands = mkOption {
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Shell commands to be executed immediately after stage 1 of the
|
||||
boot has loaded kernel modules and created device nodes in
|
||||
/dev.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.postMountCommands = mkOption {
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Shell commands to be executed immediately after the stage 1
|
||||
filesystems have been mounted.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraUtilsCommands = mkOption {
|
||||
internal = true;
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Shell commands to be executed in the builder of the
|
||||
extra-utils derivation. This can be used to provide
|
||||
additional utilities in the initial ramdisk.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.extraUtilsCommandsTest = mkOption {
|
||||
internal = true;
|
||||
default = "";
|
||||
type = with types; string;
|
||||
description = ''
|
||||
Shell commands to be executed in the builder of the
|
||||
extra-utils derivation after patchelf has done its
|
||||
job. This can be used to test additional utilities
|
||||
copied in extraUtilsCommands.
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.compressor = mkOption {
|
||||
default = "gzip -9";
|
||||
|
||||
type = types.string;
|
||||
|
||||
description = "The compressor to use on the initrd";
|
||||
|
||||
example = "xz";
|
||||
};
|
||||
|
||||
fileSystems = mkOption {
|
||||
options.neededForBoot = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If set, this file system will be mounted in the initial
|
||||
ramdisk. By default, this applies to the root file system
|
||||
and to the file system containing
|
||||
<filename>/nix/store</filename>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = {
|
||||
|
||||
system.build.bootStage1 = bootStage1;
|
||||
system.build.initialRamdisk = initialRamdisk;
|
||||
system.build.extraUtils = extraUtils;
|
||||
|
||||
system.requiredKernelConfig = with config.lib.kernelConfig; [
|
||||
(isYes "TMPFS")
|
||||
(isYes "BLK_DEV_INITRD")
|
||||
];
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#! @shell@
|
||||
|
||||
systemConfig=@systemConfig@
|
||||
|
||||
export HOME=/root
|
||||
|
||||
|
||||
# Print a greeting.
|
||||
echo
|
||||
echo -e "\e[1;32m<<< NixOS Stage 2 >>>\e[0m"
|
||||
echo
|
||||
|
||||
|
||||
# Set the PATH.
|
||||
setPath() {
|
||||
local dirs="$1"
|
||||
export PATH=/empty
|
||||
for i in $dirs; do
|
||||
PATH=$PATH:$i/bin
|
||||
if test -e $i/sbin; then
|
||||
PATH=$PATH:$i/sbin
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
setPath "@path@"
|
||||
|
||||
|
||||
# Normally, stage 1 mounts the root filesystem read/writable.
|
||||
# However, in some environments, stage 2 is executed directly, and the
|
||||
# root is read-only. So make it writable here.
|
||||
mount -n -o remount,rw /
|
||||
|
||||
|
||||
# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a
|
||||
# stage 1, we need to do that here.
|
||||
if [ ! -e /proc/1 ]; then
|
||||
mkdir -m 0755 -p /proc
|
||||
mount -n -t proc none /proc
|
||||
mkdir -m 0755 -p /dev
|
||||
mount -t devtmpfs none /dev
|
||||
fi
|
||||
|
||||
|
||||
echo "booting system configuration $systemConfig" > /dev/kmsg
|
||||
|
||||
|
||||
# Make /nix/store a read-only bind mount to enforce immutability of
|
||||
# the Nix store. Note that we can't use "chown root:nixbld" here
|
||||
# because users/groups might not exist yet.
|
||||
chown 0:30000 /nix/store
|
||||
chmod 1775 /nix/store
|
||||
if [ -n "@readOnlyStore@" ]; then
|
||||
if ! readonly-mountpoint /nix/store; then
|
||||
mount --bind /nix/store /nix/store
|
||||
mount -o remount,ro,bind /nix/store
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Provide a /etc/mtab.
|
||||
mkdir -m 0755 -p /etc
|
||||
test -e /etc/fstab || touch /etc/fstab # to shut up mount
|
||||
rm -f /etc/mtab* # not that we care about stale locks
|
||||
ln -s /proc/mounts /etc/mtab
|
||||
|
||||
|
||||
# Process the kernel command line.
|
||||
for o in $(cat /proc/cmdline); do
|
||||
case $o in
|
||||
boot.debugtrace)
|
||||
# Show each command.
|
||||
set -x
|
||||
;;
|
||||
resume=*)
|
||||
set -- $(IFS==; echo $o)
|
||||
resumeDevice=$2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# More special file systems, initialise required directories.
|
||||
mkdir -m 0755 /dev/shm
|
||||
mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" tmpfs /dev/shm
|
||||
mkdir -m 0755 -p /dev/pts
|
||||
[ -e /proc/bus/usb ] && mount -t usbfs none /proc/bus/usb # UML doesn't have USB by default
|
||||
mkdir -m 01777 -p /tmp
|
||||
mkdir -m 0755 -p /var /var/log /var/lib /var/db
|
||||
mkdir -m 0755 -p /nix/var
|
||||
mkdir -m 0700 -p /root
|
||||
mkdir -m 0755 -p /bin # for the /bin/sh symlink
|
||||
mkdir -m 0755 -p /home
|
||||
mkdir -m 0755 -p /etc/nixos
|
||||
|
||||
|
||||
# Miscellaneous boot time cleanup.
|
||||
rm -rf /var/run /var/lock
|
||||
rm -f /etc/resolv.conf
|
||||
touch /etc/resolv.conf
|
||||
rm -f /etc/{group,passwd,shadow}.lock
|
||||
|
||||
if test -n "@cleanTmpDir@"; then
|
||||
echo -n "cleaning \`/tmp'..."
|
||||
find /tmp -maxdepth 1 -mindepth 1 -print0 | xargs -0r rm -rf --one-file-system
|
||||
echo " done"
|
||||
else
|
||||
# Get rid of ICE locks...
|
||||
rm -rf /tmp/.ICE-unix
|
||||
fi
|
||||
|
||||
# ... and ensure that it's owned by root.
|
||||
mkdir -m 1777 /tmp/.ICE-unix
|
||||
|
||||
# This is a good time to clean up /nix/var/nix/chroots. Doing an `rm
|
||||
# -rf' on it isn't safe in general because it can contain bind mounts
|
||||
# to /nix/store and other places. But after rebooting these are all
|
||||
# gone, of course.
|
||||
rm -rf /nix/var/nix/chroots # recreated in activate-configuration.sh
|
||||
|
||||
|
||||
# Also get rid of temporary GC roots.
|
||||
rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots
|
||||
|
||||
|
||||
# Create a tmpfs on /run to hold runtime state for programs such as
|
||||
# udev (if stage 1 hasn't already done so).
|
||||
if ! mountpoint -q /run; then
|
||||
rm -rf /run
|
||||
mkdir -m 0755 -p /run
|
||||
mount -t tmpfs -o "mode=0755,size=@runSize@" none /run
|
||||
fi
|
||||
|
||||
mkdir -m 0755 -p /run/lock
|
||||
|
||||
|
||||
# For backwards compatibility, symlink /var/run to /run, and /var/lock
|
||||
# to /run/lock.
|
||||
ln -s /run /var/run
|
||||
ln -s /run/lock /var/lock
|
||||
|
||||
|
||||
# Clear the resume device.
|
||||
if test -n "$resumeDevice"; then
|
||||
mkswap "$resumeDevice" || echo 'Failed to clear saved image.'
|
||||
fi
|
||||
|
||||
|
||||
# Run the script that performs all configuration activation that does
|
||||
# not have to be done at boot time.
|
||||
echo "running activation script..."
|
||||
$systemConfig/activate
|
||||
|
||||
|
||||
# Record the boot configuration.
|
||||
ln -sfn "$systemConfig" /run/booted-system
|
||||
|
||||
# Prevent the booted system form being garbage-collected If it weren't
|
||||
# a gcroot, if we were running a different kernel, switched system,
|
||||
# and garbage collected all, we could not load kernel modules anymore.
|
||||
ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
|
||||
|
||||
|
||||
# Run any user-specified commands.
|
||||
@shell@ @postBootCommands@
|
||||
|
||||
|
||||
# Start systemd.
|
||||
echo "starting systemd..."
|
||||
PATH=/run/current-system/systemd/lib/systemd \
|
||||
MODULE_DIR=/run/booted-system/kernel-modules/lib/modules \
|
||||
LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \
|
||||
exec systemd --log-target=journal # --log-level=debug --log-target=console --crash-shell
|
||||
@@ -0,0 +1,100 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
kernel = config.boot.kernelPackages.kernel;
|
||||
activateConfiguration = config.system.activationScripts.script;
|
||||
|
||||
readonlyMountpoint = pkgs.runCommand "readonly-mountpoint" {} ''
|
||||
mkdir -p $out/bin
|
||||
cc -O3 ${./readonly-mountpoint.c} -o $out/bin/readonly-mountpoint
|
||||
strip -s $out/bin/readonly-mountpoint
|
||||
'';
|
||||
|
||||
bootStage2 = pkgs.substituteAll {
|
||||
src = ./stage-2-init.sh;
|
||||
shellDebug = "${pkgs.bashInteractive}/bin/bash";
|
||||
isExecutable = true;
|
||||
inherit (config.boot) devShmSize runSize cleanTmpDir;
|
||||
inherit (config.nix) readOnlyStore;
|
||||
ttyGid = config.ids.gids.tty;
|
||||
path =
|
||||
[ pkgs.coreutils
|
||||
pkgs.utillinux
|
||||
pkgs.sysvtools
|
||||
] ++ (optional config.boot.cleanTmpDir pkgs.findutils)
|
||||
++ optional config.nix.readOnlyStore readonlyMountpoint;
|
||||
postBootCommands = pkgs.writeText "local-cmds"
|
||||
''
|
||||
${config.boot.postBootCommands}
|
||||
${config.powerManagement.powerUpCommands}
|
||||
'';
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
|
||||
boot = {
|
||||
|
||||
postBootCommands = mkOption {
|
||||
default = "";
|
||||
example = "rm -f /var/log/messages";
|
||||
type = types.string;
|
||||
description = ''
|
||||
Shell commands to be executed just before systemd is started.
|
||||
'';
|
||||
};
|
||||
|
||||
devSize = mkOption {
|
||||
default = "5%";
|
||||
example = "32m";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Size limit for the /dev tmpfs. Look at mount(8), tmpfs size option,
|
||||
for the accepted syntax.
|
||||
'';
|
||||
};
|
||||
|
||||
devShmSize = mkOption {
|
||||
default = "50%";
|
||||
example = "256m";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Size limit for the /dev/shm tmpfs. Look at mount(8), tmpfs size option,
|
||||
for the accepted syntax.
|
||||
'';
|
||||
};
|
||||
|
||||
runSize = mkOption {
|
||||
default = "25%";
|
||||
example = "256m";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Size limit for the /run tmpfs. Look at mount(8), tmpfs size option,
|
||||
for the accepted syntax.
|
||||
'';
|
||||
};
|
||||
|
||||
cleanTmpDir = mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Delete all files in /tmp/ during boot.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
config = {
|
||||
|
||||
system.build.bootStage2 = bootStage2;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
{ config, pkgs }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
rec {
|
||||
|
||||
unitOptions = {
|
||||
|
||||
enable = mkOption {
|
||||
default = true;
|
||||
types = types.bool;
|
||||
description = ''
|
||||
If set to false, this unit will be a symlink to
|
||||
/dev/null. This is primarily useful to prevent specific
|
||||
template instances (e.g. <literal>serial-getty@ttyS0</literal>)
|
||||
from being started.
|
||||
'';
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
default = "";
|
||||
types = types.uniq types.string;
|
||||
description = "Description of this unit used in systemd messages and progress indicators.";
|
||||
};
|
||||
|
||||
requires = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
Start the specified units when this unit is started, and stop
|
||||
this unit when the specified units are stopped or fail.
|
||||
'';
|
||||
};
|
||||
|
||||
wants = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
Start the specified units when this unit is started.
|
||||
'';
|
||||
};
|
||||
|
||||
after = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
If the specified units are started at the same time as
|
||||
this unit, delay this unit until they have started.
|
||||
'';
|
||||
};
|
||||
|
||||
before = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
If the specified units are started at the same time as
|
||||
this unit, delay them until this unit has started.
|
||||
'';
|
||||
};
|
||||
|
||||
bindsTo = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
Like ‘requires’, but in addition, if the specified units
|
||||
unexpectedly disappear, this unit will be stopped as well.
|
||||
'';
|
||||
};
|
||||
|
||||
partOf = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
If the specified units are stopped or restarted, then this
|
||||
unit is stopped or restarted as well.
|
||||
'';
|
||||
};
|
||||
|
||||
conflicts = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = ''
|
||||
If the specified units are started, then this unit is stopped
|
||||
and vice versa.
|
||||
'';
|
||||
};
|
||||
|
||||
requiredBy = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = "Units that require (i.e. depend on and need to go down with) this unit.";
|
||||
};
|
||||
|
||||
wantedBy = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = "Units that want (i.e. depend on) this unit.";
|
||||
};
|
||||
|
||||
unitConfig = mkOption {
|
||||
default = {};
|
||||
example = { RequiresMountsFor = "/data"; };
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Unit]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
restartTriggers = mkOption {
|
||||
default = [];
|
||||
description = ''
|
||||
An arbitrary list of items such as derivations. If any item
|
||||
in the list changes between reconfigurations, the service will
|
||||
be restarted.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
serviceOptions = unitOptions // {
|
||||
|
||||
environment = mkOption {
|
||||
default = {};
|
||||
type = types.attrs;
|
||||
example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
|
||||
description = "Environment variables passed to the service's processes.";
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
default = [];
|
||||
apply = ps: "${makeSearchPath "bin" ps}:${makeSearchPath "sbin" ps}";
|
||||
description = ''
|
||||
Packages added to the service's <envar>PATH</envar>
|
||||
environment variable. Both the <filename>bin</filename>
|
||||
and <filename>sbin</filename> subdirectories of each
|
||||
package are added.
|
||||
'';
|
||||
};
|
||||
|
||||
serviceConfig = mkOption {
|
||||
default = {};
|
||||
example =
|
||||
{ StartLimitInterval = 10;
|
||||
RestartSec = 5;
|
||||
};
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Service]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.service</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
|
||||
check = v:
|
||||
let assertValueOneOf = name: values: attr:
|
||||
let val = getAttr name attr;
|
||||
in optional ( hasAttr name attr && !elem val values) "${name} ${val} not known to systemd";
|
||||
checkType = assertValueOneOf "Type" ["simple" "forking" "oneshot" "dbus" "notify" "idle"];
|
||||
checkRestart = assertValueOneOf "Restart" ["no" "on-success" "on-failure" "on-abort" "always"];
|
||||
errors = concatMap (c: c v) [checkType checkRestart];
|
||||
in if errors == [] then true
|
||||
else builtins.trace (concatStringsSep "\n" errors) false;
|
||||
};
|
||||
|
||||
script = mkOption {
|
||||
type = types.uniq types.string;
|
||||
default = "";
|
||||
description = "Shell commands executed as the service's main process.";
|
||||
};
|
||||
|
||||
scriptArgs = mkOption {
|
||||
type = types.uniq types.string;
|
||||
default = "";
|
||||
description = "Arguments passed to the main process script.";
|
||||
};
|
||||
|
||||
preStart = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed before the service's main process
|
||||
is started.
|
||||
'';
|
||||
};
|
||||
|
||||
postStart = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed after the service's main process
|
||||
is started.
|
||||
'';
|
||||
};
|
||||
|
||||
postStop = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed after the service's main process
|
||||
has exited.
|
||||
'';
|
||||
};
|
||||
|
||||
restartIfChanged = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether the service should be restarted during a NixOS
|
||||
configuration switch if its definition has changed.
|
||||
'';
|
||||
};
|
||||
|
||||
stopIfChanged = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If set, a changed unit is restarted by calling
|
||||
<command>systemctl stop</command> in the old configuration,
|
||||
then <command>systemctl start</command> in the new one.
|
||||
Otherwise, it is restarted in a single step using
|
||||
<command>systemctl restart</command> in the new configuration.
|
||||
The latter is less correct because it runs the
|
||||
<literal>ExecStop</literal> commands from the new
|
||||
configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
startAt = mkOption {
|
||||
type = types.uniq types.string;
|
||||
default = "";
|
||||
example = "Sun 14:00:00";
|
||||
description = ''
|
||||
Automatically start this unit at the given date/time, which
|
||||
must be in the format described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry>. This is equivalent
|
||||
to adding a corresponding timer unit with
|
||||
<option>OnCalendar</option> set to the value given here.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
socketOptions = unitOptions // {
|
||||
|
||||
listenStreams = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
example = [ "0.0.0.0:993" "/run/my-socket" ];
|
||||
description = ''
|
||||
For each item in this list, a <literal>ListenStream</literal>
|
||||
option in the <literal>[Socket]</literal> section will be created.
|
||||
'';
|
||||
};
|
||||
|
||||
socketConfig = mkOption {
|
||||
default = {};
|
||||
example = { ListenStream = "/run/my-socket"; };
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Socket]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.socket</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
timerOptions = unitOptions // {
|
||||
|
||||
timerConfig = mkOption {
|
||||
default = {};
|
||||
example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Timer]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.timer</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> and
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
mountOptions = unitOptions // {
|
||||
|
||||
what = mkOption {
|
||||
example = "/dev/sda1";
|
||||
type = types.uniq types.string;
|
||||
description = "Absolute path of device node, file or other resource. (Mandatory)";
|
||||
};
|
||||
|
||||
where = mkOption {
|
||||
example = "/mnt";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Absolute path of a directory of the mount point.
|
||||
Will be created if it doesn't exist. (Mandatory)
|
||||
'';
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
default = "";
|
||||
example = "ext4";
|
||||
type = types.uniq types.string;
|
||||
description = "File system type.";
|
||||
};
|
||||
|
||||
options = mkOption {
|
||||
default = "";
|
||||
example = "noatime";
|
||||
type = types.string;
|
||||
merge = concatStringsSep ",";
|
||||
description = "Options used to mount the file system.";
|
||||
};
|
||||
|
||||
mountConfig = mkOption {
|
||||
default = {};
|
||||
example = { DirectoryMode = "0775"; };
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Mount]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.mount</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
automountOptions = unitOptions // {
|
||||
|
||||
where = mkOption {
|
||||
example = "/mnt";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Absolute path of a directory of the mount point.
|
||||
Will be created if it doesn't exist. (Mandatory)
|
||||
'';
|
||||
};
|
||||
|
||||
automountConfig = mkOption {
|
||||
default = {};
|
||||
example = { DirectoryMode = "0775"; };
|
||||
type = types.attrs;
|
||||
description = ''
|
||||
Each attribute in this set specifies an option in the
|
||||
<literal>[Automount]</literal> section of the unit. See
|
||||
<citerefentry><refentrytitle>systemd.automount</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,678 @@
|
||||
{ config, pkgs, utils, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
with utils;
|
||||
with import ./systemd-unit-options.nix { inherit config pkgs; };
|
||||
|
||||
let
|
||||
|
||||
cfg = config.systemd;
|
||||
|
||||
systemd = cfg.package;
|
||||
|
||||
makeUnit = name: unit:
|
||||
pkgs.runCommand "unit" { inherit (unit) text; preferLocalBuild = true; }
|
||||
(if unit.enable then ''
|
||||
mkdir -p $out
|
||||
echo -n "$text" > $out/${name}
|
||||
'' else ''
|
||||
mkdir -p $out
|
||||
ln -s /dev/null $out/${name}
|
||||
'');
|
||||
|
||||
upstreamUnits =
|
||||
[ # Targets.
|
||||
"basic.target"
|
||||
"sysinit.target"
|
||||
"sockets.target"
|
||||
"graphical.target"
|
||||
"multi-user.target"
|
||||
"getty.target"
|
||||
"network.target"
|
||||
"network-online.target"
|
||||
"nss-lookup.target"
|
||||
"nss-user-lookup.target"
|
||||
"time-sync.target"
|
||||
#"cryptsetup.target"
|
||||
"sigpwr.target"
|
||||
"timers.target"
|
||||
"paths.target"
|
||||
|
||||
# Rescue mode.
|
||||
"rescue.target"
|
||||
"rescue.service"
|
||||
|
||||
# Udev.
|
||||
"systemd-udevd-control.socket"
|
||||
"systemd-udevd-kernel.socket"
|
||||
"systemd-udevd.service"
|
||||
"systemd-udev-settle.service"
|
||||
"systemd-udev-trigger.service"
|
||||
|
||||
# Hardware (started by udev when a relevant device is plugged in).
|
||||
"sound.target"
|
||||
"bluetooth.target"
|
||||
"printer.target"
|
||||
"smartcard.target"
|
||||
|
||||
# Login stuff.
|
||||
"systemd-logind.service"
|
||||
"autovt@.service"
|
||||
#"systemd-vconsole-setup.service"
|
||||
"systemd-user-sessions.service"
|
||||
"dbus-org.freedesktop.login1.service"
|
||||
"user@.service"
|
||||
|
||||
# Journal.
|
||||
"systemd-journald.socket"
|
||||
"systemd-journald.service"
|
||||
"systemd-journal-flush.service"
|
||||
"syslog.socket"
|
||||
|
||||
# SysV init compatibility.
|
||||
"systemd-initctl.socket"
|
||||
"systemd-initctl.service"
|
||||
|
||||
# Kernel module loading.
|
||||
#"systemd-modules-load.service"
|
||||
|
||||
# Filesystems.
|
||||
"systemd-fsck@.service"
|
||||
"systemd-fsck-root.service"
|
||||
"systemd-remount-fs.service"
|
||||
"local-fs.target"
|
||||
"local-fs-pre.target"
|
||||
"remote-fs.target"
|
||||
"remote-fs-pre.target"
|
||||
"swap.target"
|
||||
"dev-hugepages.mount"
|
||||
"dev-mqueue.mount"
|
||||
"sys-fs-fuse-connections.mount"
|
||||
"sys-kernel-config.mount"
|
||||
"sys-kernel-debug.mount"
|
||||
|
||||
# Hibernate / suspend.
|
||||
"hibernate.target"
|
||||
"suspend.target"
|
||||
"sleep.target"
|
||||
"hybrid-sleep.target"
|
||||
"systemd-hibernate.service"
|
||||
"systemd-suspend.service"
|
||||
"systemd-hybrid-sleep.service"
|
||||
"systemd-shutdownd.socket"
|
||||
"systemd-shutdownd.service"
|
||||
|
||||
# Reboot stuff.
|
||||
"reboot.target"
|
||||
"systemd-reboot.service"
|
||||
"poweroff.target"
|
||||
"systemd-poweroff.service"
|
||||
"halt.target"
|
||||
"systemd-halt.service"
|
||||
"ctrl-alt-del.target"
|
||||
"shutdown.target"
|
||||
"umount.target"
|
||||
"final.target"
|
||||
"kexec.target"
|
||||
"systemd-kexec.service"
|
||||
|
||||
# Password entry.
|
||||
"systemd-ask-password-console.path"
|
||||
"systemd-ask-password-console.service"
|
||||
"systemd-ask-password-wall.path"
|
||||
"systemd-ask-password-wall.service"
|
||||
]
|
||||
|
||||
++ optionals cfg.enableEmergencyMode [
|
||||
"emergency.target"
|
||||
"emergency.service"
|
||||
];
|
||||
|
||||
upstreamWants =
|
||||
[ #"basic.target.wants"
|
||||
"sysinit.target.wants"
|
||||
"sockets.target.wants"
|
||||
"local-fs.target.wants"
|
||||
"multi-user.target.wants"
|
||||
"shutdown.target.wants"
|
||||
"timers.target.wants"
|
||||
];
|
||||
|
||||
makeJobScript = name: text:
|
||||
let x = pkgs.writeTextFile { name = "unit-script"; executable = true; destination = "/bin/${name}"; inherit text; };
|
||||
in "${x}/bin/${name}";
|
||||
|
||||
unitConfig = { name, config, ... }: {
|
||||
config = {
|
||||
unitConfig =
|
||||
{ Requires = concatStringsSep " " config.requires;
|
||||
Wants = concatStringsSep " " config.wants;
|
||||
After = concatStringsSep " " config.after;
|
||||
Before = concatStringsSep " " config.before;
|
||||
BindsTo = concatStringsSep " " config.bindsTo;
|
||||
PartOf = concatStringsSep " " config.partOf;
|
||||
Conflicts = concatStringsSep " " config.conflicts;
|
||||
"X-Restart-Triggers" = toString config.restartTriggers;
|
||||
} // optionalAttrs (config.description != "") {
|
||||
Description = config.description;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
serviceConfig = { name, config, ... }: {
|
||||
config = {
|
||||
# Default path for systemd services. Should be quite minimal.
|
||||
path =
|
||||
[ pkgs.coreutils
|
||||
pkgs.findutils
|
||||
pkgs.gnugrep
|
||||
pkgs.gnused
|
||||
systemd
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
mountConfig = { name, config, ... }: {
|
||||
config = {
|
||||
mountConfig =
|
||||
{ What = config.what;
|
||||
Where = config.where;
|
||||
} // optionalAttrs (config.type != "") {
|
||||
Type = config.type;
|
||||
} // optionalAttrs (config.options != "") {
|
||||
Options = config.options;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
automountConfig = { name, config, ... }: {
|
||||
config = {
|
||||
automountConfig =
|
||||
{ Where = config.where;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
toOption = x:
|
||||
if x == true then "true"
|
||||
else if x == false then "false"
|
||||
else toString x;
|
||||
|
||||
attrsToSection = as:
|
||||
concatStrings (concatLists (mapAttrsToList (name: value:
|
||||
map (x: ''
|
||||
${name}=${toOption x}
|
||||
'')
|
||||
(if isList value then value else [value]))
|
||||
as));
|
||||
|
||||
targetToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
serviceToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
|
||||
[Service]
|
||||
Environment=PATH=${def.path}
|
||||
Environment=LD_LIBRARY_PATH=
|
||||
${let env = cfg.globalEnvironment // def.environment;
|
||||
in concatMapStrings (n: "Environment=\"${n}=${getAttr n env}\"\n") (attrNames env)}
|
||||
${optionalString (!def.restartIfChanged) "X-RestartIfChanged=false"}
|
||||
${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
|
||||
|
||||
${optionalString (def.preStart != "") ''
|
||||
ExecStartPre=${makeJobScript "${name}-pre-start" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${def.preStart}
|
||||
''}
|
||||
''}
|
||||
|
||||
${optionalString (def.script != "") ''
|
||||
ExecStart=${makeJobScript "${name}-start" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${def.script}
|
||||
''} ${def.scriptArgs}
|
||||
''}
|
||||
|
||||
${optionalString (def.postStart != "") ''
|
||||
ExecStartPost=${makeJobScript "${name}-post-start" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${def.postStart}
|
||||
''}
|
||||
''}
|
||||
|
||||
${optionalString (def.postStop != "") ''
|
||||
ExecStopPost=${makeJobScript "${name}-post-stop" ''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${def.postStop}
|
||||
''}
|
||||
''}
|
||||
|
||||
${attrsToSection def.serviceConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
socketToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
|
||||
[Socket]
|
||||
${attrsToSection def.socketConfig}
|
||||
${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
|
||||
'';
|
||||
};
|
||||
|
||||
timerToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
|
||||
[Timer]
|
||||
${attrsToSection def.timerConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
mountToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
|
||||
[Mount]
|
||||
${attrsToSection def.mountConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
automountToUnit = name: def:
|
||||
{ inherit (def) wantedBy requiredBy enable;
|
||||
text =
|
||||
''
|
||||
[Unit]
|
||||
${attrsToSection def.unitConfig}
|
||||
|
||||
[Automount]
|
||||
${attrsToSection def.automountConfig}
|
||||
'';
|
||||
};
|
||||
|
||||
nixosUnits = mapAttrsToList makeUnit cfg.units;
|
||||
|
||||
units = pkgs.runCommand "units" { preferLocalBuild = true; }
|
||||
''
|
||||
mkdir -p $out
|
||||
for i in ${toString upstreamUnits}; do
|
||||
fn=${systemd}/example/systemd/system/$i
|
||||
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
|
||||
if [ -L $fn ]; then
|
||||
cp -pd $fn $out/
|
||||
else
|
||||
ln -s $fn $out/
|
||||
fi
|
||||
done
|
||||
|
||||
for i in ${toString upstreamWants}; do
|
||||
fn=${systemd}/example/systemd/system/$i
|
||||
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
|
||||
x=$out/$(basename $fn)
|
||||
mkdir $x
|
||||
for i in $fn/*; do
|
||||
y=$x/$(basename $i)
|
||||
cp -pd $i $y
|
||||
if ! [ -e $y ]; then rm -v $y; fi
|
||||
done
|
||||
done
|
||||
|
||||
for i in ${toString nixosUnits}; do
|
||||
ln -s $i/* $out/
|
||||
done
|
||||
|
||||
for i in ${toString cfg.packages}; do
|
||||
ln -s $i/etc/systemd/system/* $out/
|
||||
done
|
||||
|
||||
${concatStrings (mapAttrsToList (name: unit:
|
||||
concatMapStrings (name2: ''
|
||||
mkdir -p $out/${name2}.wants
|
||||
ln -sfn ../${name} $out/${name2}.wants/
|
||||
'') unit.wantedBy) cfg.units)}
|
||||
|
||||
${concatStrings (mapAttrsToList (name: unit:
|
||||
concatMapStrings (name2: ''
|
||||
mkdir -p $out/${name2}.requires
|
||||
ln -sfn ../${name} $out/${name2}.requires/
|
||||
'') unit.requiredBy) cfg.units)}
|
||||
|
||||
ln -s ${cfg.defaultUnit} $out/default.target
|
||||
|
||||
ln -s rescue.target $out/kbrequest.target
|
||||
|
||||
mkdir -p $out/getty.target.wants/
|
||||
ln -s ../getty@tty1.service $out/getty.target.wants/
|
||||
|
||||
ln -s ../local-fs.target ../remote-fs.target ../network.target ../nss-lookup.target \
|
||||
../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
|
||||
''; # */
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
systemd.package = mkOption {
|
||||
default = pkgs.systemd;
|
||||
type = types.package;
|
||||
description = "The systemd package.";
|
||||
};
|
||||
|
||||
systemd.units = mkOption {
|
||||
description = "Definition of systemd units.";
|
||||
default = {};
|
||||
type = types.attrsOf types.optionSet;
|
||||
options = {
|
||||
text = mkOption {
|
||||
types = types.uniq types.string;
|
||||
description = "Text of this systemd unit.";
|
||||
};
|
||||
enable = mkOption {
|
||||
default = true;
|
||||
types = types.bool;
|
||||
description = ''
|
||||
If set to false, this unit will be a symlink to
|
||||
/dev/null. This is primarily useful to prevent specific
|
||||
template instances (e.g. <literal>serial-getty@ttyS0</literal>)
|
||||
from being started.
|
||||
'';
|
||||
};
|
||||
requiredBy = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = "Units that require (i.e. depend on and need to go down with) this unit.";
|
||||
};
|
||||
wantedBy = mkOption {
|
||||
default = [];
|
||||
types = types.listOf types.string;
|
||||
description = "Units that want (i.e. depend on) this unit.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.packages = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.package;
|
||||
description = "Packages providing systemd units.";
|
||||
};
|
||||
|
||||
systemd.targets = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.optionSet;
|
||||
options = [ unitOptions unitConfig ];
|
||||
description = "Definition of systemd target units.";
|
||||
};
|
||||
|
||||
systemd.services = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.optionSet;
|
||||
options = [ serviceOptions unitConfig serviceConfig ];
|
||||
description = "Definition of systemd service units.";
|
||||
};
|
||||
|
||||
systemd.sockets = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.optionSet;
|
||||
options = [ socketOptions unitConfig ];
|
||||
description = "Definition of systemd socket units.";
|
||||
};
|
||||
|
||||
systemd.timers = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.optionSet;
|
||||
options = [ timerOptions unitConfig ];
|
||||
description = "Definition of systemd timer units.";
|
||||
};
|
||||
|
||||
systemd.mounts = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.optionSet;
|
||||
options = [ mountOptions unitConfig mountConfig ];
|
||||
description = ''
|
||||
Definition of systemd mount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.automounts = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.optionSet;
|
||||
options = [ automountOptions unitConfig automountConfig ];
|
||||
description = ''
|
||||
Definition of systemd automount units.
|
||||
This is a list instead of an attrSet, because systemd mandates the names to be derived from
|
||||
the 'where' attribute.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.defaultUnit = mkOption {
|
||||
default = "multi-user.target";
|
||||
type = types.uniq types.string;
|
||||
description = "Default unit started when the system boots.";
|
||||
};
|
||||
|
||||
systemd.globalEnvironment = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = { TZ = "CET"; };
|
||||
description = ''
|
||||
Environment variables passed to <emphasis>all</emphasis> systemd units.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.console = mkOption {
|
||||
default = "";
|
||||
type = types.uniq types.string;
|
||||
description = "If non-empty, write log messages to the specified TTY device.";
|
||||
};
|
||||
|
||||
services.journald.rateLimitInterval = mkOption {
|
||||
default = "10s";
|
||||
type = types.uniq types.string;
|
||||
description = ''
|
||||
Configures the rate limiting interval that is applied to all
|
||||
messages generated on the system. This rate limiting is applied
|
||||
per-service, so that two services which log do not interfere with
|
||||
each other's limit. The value may be specified in the following
|
||||
units: s, min, h, ms, us. To turn off any kind of rate limiting,
|
||||
set either value to 0.
|
||||
'';
|
||||
};
|
||||
|
||||
services.journald.rateLimitBurst = mkOption {
|
||||
default = 100;
|
||||
type = types.uniq types.int;
|
||||
description = ''
|
||||
Configures the rate limiting burst limit (number of messages per
|
||||
interval) that is applied to all messages generated on the system.
|
||||
This rate limiting is applied per-service, so that two services
|
||||
which log do not interfere with each other's limit.
|
||||
'';
|
||||
};
|
||||
|
||||
services.logind.extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.uniq types.string;
|
||||
example = "HandleLidSwitch=ignore";
|
||||
description = ''
|
||||
Extra config options for systemd-logind. See man logind.conf for
|
||||
available options.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.enableEmergencyMode = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable emergency mode, which is an
|
||||
<command>sulogin</command> shell started on the console if
|
||||
mounting a filesystem fails. Since some machines (like EC2
|
||||
instances) have no console of any kind, emergency mode doesn't
|
||||
make sense, and it's better to continue with the boot insofar
|
||||
as possible.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
system.build.units = units;
|
||||
|
||||
environment.systemPackages = [ systemd ];
|
||||
|
||||
environment.etc."systemd/system".source = units;
|
||||
|
||||
environment.etc."systemd/system.conf".text =
|
||||
''
|
||||
[Manager]
|
||||
'';
|
||||
|
||||
environment.etc."systemd/journald.conf".text =
|
||||
''
|
||||
[Journal]
|
||||
RateLimitInterval=${config.services.journald.rateLimitInterval}
|
||||
RateLimitBurst=${toString config.services.journald.rateLimitBurst}
|
||||
${optionalString (config.services.journald.console != "") ''
|
||||
ForwardToConsole=yes
|
||||
TTYPath=${config.services.journald.console}
|
||||
''}
|
||||
'';
|
||||
|
||||
environment.etc."systemd/logind.conf".text =
|
||||
''
|
||||
[Login]
|
||||
${config.services.logind.extraConfig}
|
||||
'';
|
||||
|
||||
environment.etc."systemd/sleep.conf".text =
|
||||
''
|
||||
[Sleep]
|
||||
'';
|
||||
|
||||
system.activationScripts.systemd = stringAfter [ "groups" ]
|
||||
''
|
||||
mkdir -m 0755 -p /var/lib/udev
|
||||
mkdir -p /var/log/journal
|
||||
chmod 0755 /var/log/journal
|
||||
|
||||
# Regenerate the hardware database /var/lib/udev/hwdb.bin
|
||||
# whenever systemd changes.
|
||||
if [ ! -e /var/lib/udev/prev-systemd -o "$(readlink /var/lib/udev/prev-systemd)" != ${systemd} ]; then
|
||||
echo "regenerating udev hardware database..."
|
||||
${systemd}/bin/udevadm hwdb --update && ln -sfn ${systemd} /var/lib/udev/prev-systemd
|
||||
fi
|
||||
|
||||
# Make all journals readable to users in the wheel and adm
|
||||
# groups, in addition to those in the systemd-journal group.
|
||||
# Users can always read their own journals.
|
||||
${pkgs.acl}/bin/setfacl -nm g:wheel:rx,d:g:wheel:rx,g:adm:rx,d:g:adm:rx /var/log/journal
|
||||
'';
|
||||
|
||||
# Target for ‘charon send-keys’ to hook into.
|
||||
systemd.targets.keys =
|
||||
{ description = "Security Keys";
|
||||
};
|
||||
|
||||
systemd.units =
|
||||
mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
|
||||
// mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
|
||||
// listToAttrs (map
|
||||
(v: let n = escapeSystemdPath v.where;
|
||||
in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
|
||||
|
||||
system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [
|
||||
"CGROUPS" "AUTOFS4_FS" "DEVTMPFS"
|
||||
];
|
||||
|
||||
environment.shellAliases =
|
||||
{ start = "systemctl start";
|
||||
stop = "systemctl stop";
|
||||
restart = "systemctl restart";
|
||||
status = "systemctl status";
|
||||
};
|
||||
|
||||
users.extraGroups.systemd-journal.gid = config.ids.gids.systemd-journal;
|
||||
|
||||
# Generate timer units for all services that have a ‘startAt’ value.
|
||||
systemd.timers =
|
||||
mapAttrs (name: service:
|
||||
{ wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = service.startAt;
|
||||
})
|
||||
(filterAttrs (name: service: service.startAt != "") cfg.services);
|
||||
|
||||
# FIXME: These are borrowed from upstream systemd.
|
||||
systemd.services."systemd-update-utmp" =
|
||||
{ description = "Update UTMP about System Reboot/Shutdown";
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
after = [ "systemd-remount-fs.service" ];
|
||||
before = [ "sysinit.target" "shutdown.target" ];
|
||||
conflicts = [ "shutdown.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/var/log";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${systemd}/lib/systemd/systemd-update-utmp reboot";
|
||||
ExecStop = "${systemd}/lib/systemd/systemd-update-utmp shutdown";
|
||||
};
|
||||
restartIfChanged = false;
|
||||
};
|
||||
|
||||
systemd.services."systemd-random-seed" =
|
||||
{ description = "Load/Save Random Seed";
|
||||
wantedBy = [ "sysinit.target" "multi-user.target" ];
|
||||
after = [ "systemd-remount-fs.service" ];
|
||||
before = [ "sysinit.target" "shutdown.target" ];
|
||||
conflicts = [ "shutdown.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/var/lib";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${systemd}/lib/systemd/systemd-random-seed load";
|
||||
ExecStop = "${systemd}/lib/systemd/systemd-random-seed save";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
# Management of static files in /etc.
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
|
||||
let
|
||||
|
||||
etc' = filter (f: f.enable) (attrValues config.environment.etc);
|
||||
|
||||
etc = pkgs.stdenv.mkDerivation {
|
||||
name = "etc";
|
||||
|
||||
builder = ./make-etc.sh;
|
||||
|
||||
preferLocalBuild = true;
|
||||
|
||||
/* !!! Use toXML. */
|
||||
sources = map (x: x.source) etc';
|
||||
targets = map (x: x.target) etc';
|
||||
modes = map (x: x.mode) etc';
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
environment.etc = mkOption {
|
||||
type = types.loaOf types.optionSet;
|
||||
default = {};
|
||||
example =
|
||||
{ hosts =
|
||||
{ source = "/nix/store/.../etc/dir/file.conf.example";
|
||||
mode = "0440";
|
||||
};
|
||||
"default/useradd".text = "GROUP=100 ...";
|
||||
};
|
||||
description = ''
|
||||
Set of files that have to be linked in <filename>/etc</filename>.
|
||||
'';
|
||||
|
||||
options = singleton ({ name, config, ... }:
|
||||
{ options = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether this /etc file should be generated. This
|
||||
option allows specific /etc files to be disabled.
|
||||
'';
|
||||
};
|
||||
|
||||
target = mkOption {
|
||||
description = ''
|
||||
Name of symlink (relative to
|
||||
<filename>/etc</filename>). Defaults to the attribute
|
||||
name.
|
||||
'';
|
||||
};
|
||||
|
||||
text = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.string;
|
||||
description = "Text of the file.";
|
||||
};
|
||||
|
||||
source = mkOption {
|
||||
types = types.path;
|
||||
description = "Path of the source file.";
|
||||
};
|
||||
|
||||
mode = mkOption {
|
||||
default = "symlink";
|
||||
example = "0600";
|
||||
description = ''
|
||||
If set to something else than <literal>symlink</literal>,
|
||||
the file is copied instead of symlinked, with the given
|
||||
file mode.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = {
|
||||
target = mkDefault name;
|
||||
source = mkIf (config.text != null)
|
||||
(mkDefault (pkgs.writeText "etc-file" config.text));
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
system.build.etc = etc;
|
||||
|
||||
system.activationScripts.etc = stringAfter [ "stdio" ]
|
||||
''
|
||||
# Set up the statically computed bits of /etc.
|
||||
echo "setting up /etc..."
|
||||
${pkgs.perl}/bin/perl ${./setup-etc.pl} ${etc}/etc
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
source $stdenv/setup
|
||||
|
||||
mkdir -p $out/etc
|
||||
|
||||
set -f
|
||||
sources_=($sources)
|
||||
targets_=($targets)
|
||||
modes_=($modes)
|
||||
set +f
|
||||
|
||||
for ((i = 0; i < ${#targets_[@]}; i++)); do
|
||||
source="${sources_[$i]}"
|
||||
target="${targets_[$i]}"
|
||||
|
||||
if [[ "$source" =~ '*' ]]; then
|
||||
|
||||
# If the source name contains '*', perform globbing.
|
||||
mkdir -p $out/etc/$target
|
||||
for fn in $source; do
|
||||
ln -s "$fn" $out/etc/$target/
|
||||
done
|
||||
|
||||
else
|
||||
|
||||
mkdir -p $out/etc/$(dirname $target)
|
||||
if ! [ -e $out/etc/$target ]; then
|
||||
ln -s $source $out/etc/$target
|
||||
else
|
||||
echo "duplicate entry $target -> $source"
|
||||
if test "$(readlink $out/etc/$target)" != "$source"; then
|
||||
echo "mismatched duplicate entry $(readlink $out/etc/$target) <-> $source"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "${modes_[$i]}" != symlink; then
|
||||
echo "${modes_[$i]}" > $out/etc/$target.mode
|
||||
fi
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use strict;
|
||||
use File::Find;
|
||||
use File::Copy;
|
||||
use File::Path;
|
||||
use File::Basename;
|
||||
|
||||
my $etc = $ARGV[0] or die;
|
||||
my $static = "/etc/static";
|
||||
|
||||
sub atomicSymlink {
|
||||
my ($source, $target) = @_;
|
||||
my $tmp = "$target.tmp";
|
||||
unlink $tmp;
|
||||
symlink $source, $tmp or return 1;
|
||||
rename $tmp, $target or return 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
# Atomically update /etc/static to point at the etc files of the
|
||||
# current configuration.
|
||||
atomicSymlink $etc, $static or die;
|
||||
|
||||
|
||||
# Remove dangling symlinks that point to /etc/static. These are
|
||||
# configuration files that existed in a previous configuration but not
|
||||
# in the current one. For efficiency, don't look under /etc/nixos
|
||||
# (where all the NixOS sources live).
|
||||
sub cleanup {
|
||||
if ($File::Find::name eq "/etc/nixos") {
|
||||
$File::Find::prune = 1;
|
||||
return;
|
||||
}
|
||||
if (-l $_) {
|
||||
my $target = readlink $_;
|
||||
if (substr($target, 0, length $static) eq $static) {
|
||||
my $x = "/etc/static/" . substr($File::Find::name, length "/etc/");
|
||||
unless (-l $x) {
|
||||
print STDERR "removing obsolete symlink ‘$File::Find::name’...\n";
|
||||
unlink "$_";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
find(\&cleanup, "/etc");
|
||||
|
||||
|
||||
# For every file in the etc tree, create a corresponding symlink in
|
||||
# /etc to /etc/static. The indirection through /etc/static is to make
|
||||
# switching to a new configuration somewhat more atomic.
|
||||
sub link {
|
||||
my $fn = substr $File::Find::name, length($etc) + 1 or next;
|
||||
my $target = "/etc/$fn";
|
||||
File::Path::make_path(dirname $target);
|
||||
if (-e "$_.mode") {
|
||||
open MODE, "<$_.mode";
|
||||
my $mode = <MODE>; chomp $mode;
|
||||
close MODE;
|
||||
copy "$static/$fn", "$target.tmp" or warn;
|
||||
chmod oct($mode), "$target.tmp" or warn;
|
||||
rename "$target.tmp", $target or warn;
|
||||
} elsif (-l "$_") {
|
||||
atomicSymlink "$static/$fn", $target or warn;
|
||||
}
|
||||
}
|
||||
|
||||
find(\&link, $etc);
|
||||
@@ -0,0 +1,286 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
with import ../boot/systemd-unit-options.nix { inherit config pkgs; };
|
||||
|
||||
let
|
||||
|
||||
userExists = u:
|
||||
(u == "") || any (uu: uu.name == u) (attrValues config.users.extraUsers);
|
||||
|
||||
groupExists = g:
|
||||
(g == "") || any (gg: gg.name == g) (attrValues config.users.extraGroups);
|
||||
|
||||
makeJobScript = name: content: "${pkgs.writeScriptBin name content}/bin/${name}";
|
||||
|
||||
# From a job description, generate an systemd unit file.
|
||||
makeUnit = job:
|
||||
|
||||
let
|
||||
hasMain = job.script != "" || job.exec != "";
|
||||
|
||||
env = job.environment;
|
||||
|
||||
preStartScript = makeJobScript "${job.name}-pre-start"
|
||||
''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${job.preStart}
|
||||
'';
|
||||
|
||||
startScript = makeJobScript "${job.name}-start"
|
||||
''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${if job.script != "" then job.script else ''
|
||||
exec ${job.exec}
|
||||
''}
|
||||
'';
|
||||
|
||||
postStartScript = makeJobScript "${job.name}-post-start"
|
||||
''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${job.postStart}
|
||||
'';
|
||||
|
||||
preStopScript = makeJobScript "${job.name}-pre-stop"
|
||||
''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${job.preStop}
|
||||
'';
|
||||
|
||||
postStopScript = makeJobScript "${job.name}-post-stop"
|
||||
''
|
||||
#! ${pkgs.stdenv.shell} -e
|
||||
${job.postStop}
|
||||
'';
|
||||
in {
|
||||
|
||||
inherit (job) description requires before partOf environment path restartIfChanged unitConfig;
|
||||
|
||||
after =
|
||||
(if job.startOn == "stopped udevtrigger" then [ "systemd-udev-settle.service" ] else
|
||||
if job.startOn == "started udev" then [ "systemd-udev.service" ] else
|
||||
if job.startOn == "started network-interfaces" then [ "network-interfaces.target" ] else
|
||||
if job.startOn == "started networking" then [ "network.target" ] else
|
||||
if job.startOn == "ip-up" then [] else
|
||||
if job.startOn == "" || job.startOn == "startup" then [] else
|
||||
builtins.trace "Warning: job ‘${job.name}’ has unknown startOn value ‘${job.startOn}’." []
|
||||
) ++ job.after;
|
||||
|
||||
wants =
|
||||
(if job.startOn == "stopped udevtrigger" then [ "systemd-udev-settle.service" ] else []
|
||||
) ++ job.wants;
|
||||
|
||||
wantedBy =
|
||||
(if job.startOn == "" then [] else
|
||||
if job.startOn == "ip-up" then [ "ip-up.target" ] else
|
||||
[ "multi-user.target" ]) ++ job.wantedBy;
|
||||
|
||||
serviceConfig =
|
||||
job.serviceConfig
|
||||
// optionalAttrs (job.preStart != "" && (job.script != "" || job.exec != ""))
|
||||
{ ExecStartPre = preStartScript; }
|
||||
// optionalAttrs (job.preStart != "" && job.script == "" && job.exec == "")
|
||||
{ ExecStart = preStartScript; }
|
||||
// optionalAttrs (job.script != "" || job.exec != "")
|
||||
{ ExecStart = startScript; }
|
||||
// optionalAttrs (job.postStart != "")
|
||||
{ ExecStartPost = postStartScript; }
|
||||
// optionalAttrs (job.preStop != "")
|
||||
{ ExecStop = preStopScript; }
|
||||
// optionalAttrs (job.postStop != "")
|
||||
{ ExecStopPost = postStopScript; }
|
||||
// (if job.script == "" && job.exec == "" then { Type = "oneshot"; RemainAfterExit = true; } else
|
||||
if job.daemonType == "fork" || job.daemonType == "daemon" then { Type = "forking"; GuessMainPID = true; } else
|
||||
if job.daemonType == "none" then { } else
|
||||
throw "invalid daemon type `${job.daemonType}'")
|
||||
// optionalAttrs (!job.task && job.respawn)
|
||||
{ Restart = "always"; }
|
||||
// optionalAttrs job.task
|
||||
{ Type = "oneshot"; RemainAfterExit = false; };
|
||||
};
|
||||
|
||||
|
||||
jobOptions = serviceOptions // {
|
||||
|
||||
name = mkOption {
|
||||
# !!! The type should ensure that this could be a filename.
|
||||
type = types.string;
|
||||
example = "sshd";
|
||||
description = ''
|
||||
Name of the Upstart job.
|
||||
'';
|
||||
};
|
||||
|
||||
startOn = mkOption {
|
||||
# !!! Re-enable this once we're on Upstart >= 0.6.
|
||||
#type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
The Upstart event that triggers this job to be started.
|
||||
If empty, the job will not start automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
stopOn = mkOption {
|
||||
type = types.string;
|
||||
default = "starting shutdown";
|
||||
description = ''
|
||||
The Upstart event that triggers this job to be stopped.
|
||||
'';
|
||||
};
|
||||
|
||||
postStart = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed after the job is started (i.e. after
|
||||
the job's main process is started), but before the job is
|
||||
considered “running”.
|
||||
'';
|
||||
};
|
||||
|
||||
preStop = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed before the job is stopped
|
||||
(i.e. before Upstart kills the job's main process). This can
|
||||
be used to cleanly shut down a daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
postStop = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Shell commands executed after the job has stopped
|
||||
(i.e. after the job's main process has terminated).
|
||||
'';
|
||||
};
|
||||
|
||||
exec = mkOption {
|
||||
type = types.string;
|
||||
default = "";
|
||||
description = ''
|
||||
Command to start the job's main process. If empty, the
|
||||
job has no main process, but can still have pre/post-start
|
||||
and pre/post-stop scripts, and is considered “running”
|
||||
until it is stopped.
|
||||
'';
|
||||
};
|
||||
|
||||
respawn = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to restart the job automatically if its process
|
||||
ends unexpectedly.
|
||||
'';
|
||||
};
|
||||
|
||||
task = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether this job is a task rather than a service. Tasks
|
||||
are executed only once, while services are restarted when
|
||||
they exit.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonType = mkOption {
|
||||
type = types.string;
|
||||
default = "none";
|
||||
description = ''
|
||||
Determines how Upstart detects when a daemon should be
|
||||
considered “running”. The value <literal>none</literal> means
|
||||
that the daemon is considered ready immediately. The value
|
||||
<literal>fork</literal> means that the daemon will fork once.
|
||||
The value <literal>daemon</literal> means that the daemon will
|
||||
fork twice. The value <literal>stop</literal> means that the
|
||||
daemon will raise the SIGSTOP signal to indicate readiness.
|
||||
'';
|
||||
};
|
||||
|
||||
setuid = mkOption {
|
||||
type = types.string;
|
||||
check = userExists;
|
||||
default = "";
|
||||
description = ''
|
||||
Run the daemon as a different user.
|
||||
'';
|
||||
};
|
||||
|
||||
setgid = mkOption {
|
||||
type = types.string;
|
||||
check = groupExists;
|
||||
default = "";
|
||||
description = ''
|
||||
Run the daemon as a different group.
|
||||
'';
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
default = [];
|
||||
description = ''
|
||||
Packages added to the job's <envar>PATH</envar> environment variable.
|
||||
Both the <filename>bin</filename> and <filename>sbin</filename>
|
||||
subdirectories of each package are added.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
upstartJob = { name, config, ... }: {
|
||||
|
||||
options = {
|
||||
|
||||
unit = mkOption {
|
||||
default = makeUnit config;
|
||||
description = "Generated definition of the systemd unit corresponding to this job.";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = {
|
||||
|
||||
# The default name is the name extracted from the attribute path.
|
||||
name = mkDefaultValue name;
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
jobs = mkOption {
|
||||
default = {};
|
||||
description = ''
|
||||
This option defines the system jobs started and managed by the
|
||||
Upstart daemon.
|
||||
'';
|
||||
type = types.loaOf types.optionSet;
|
||||
options = [ jobOptions upstartJob ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = {
|
||||
|
||||
systemd.services =
|
||||
flip mapAttrs' config.jobs (name: job:
|
||||
nameValuePair job.name job.unit);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user