Move all of NixOS to nixos/ in preparation of the repository merge

This commit is contained in:
Eelco Dolstra
2013-10-10 13:28:20 +02:00
parent 6070bc016b
commit 5c1f8cbc70
481 changed files with 0 additions and 0 deletions
@@ -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;
};
}
+304
View File
@@ -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;
};
}
+21
View File
@@ -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")
];
};
};
}
+49
View File
@@ -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;
};
}
+176
View File
@@ -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 ];
};
}
+112
View File
@@ -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);
}
+27
View File
@@ -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";
}
+374
View File
@@ -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 "<<< NixOS Stage 1 >>>"
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
+343
View File
@@ -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")
];
};
}
+173
View File
@@ -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
+100
View File
@@ -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.
'';
};
};
}
+678
View File
@@ -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";
};
};
};
}
+117
View File
@@ -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
'';
};
}
+42
View File
@@ -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
+68
View File
@@ -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);
+286
View File
@@ -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);
};
}