Allow upstream systemd units to be extended

If you define a unit, and either systemd or a package in
systemd.packages already provides that unit, then we now generate a
file /etc/systemd/system/<unit>.d/overrides.conf. This makes it
possible to use upstream units, while allowing them to be customised
from the NixOS configuration. For instance, the module nix-daemon.nix
now uses the units provided by the Nix package. And all unit
definitions that duplicated upstream systemd units are finally gone.

This makes the baseUnit option unnecessary, so I've removed it.
This commit is contained in:
Eelco Dolstra 2014-04-17 18:52:31 +02:00
parent 8dcf76480c
commit 179acfb664
9 changed files with 72 additions and 97 deletions

View file

@ -45,19 +45,8 @@ in
) config.boot.kernel.sysctl);
systemd.services.systemd-sysctl =
{ description = "Apply Kernel Variables";
before = [ "sysinit.target" "shutdown.target" ];
wantedBy = [ "sysinit.target" "multi-user.target" ];
{ wantedBy = [ "multi-user.target" ];
restartTriggers = [ config.environment.etc."sysctl.d/nixos.conf".source ];
unitConfig = {
DefaultDependencies = false; # needed to prevent a cycle
ConditionPathIsReadWrite = "/proc/sys/"; # prevent systemd-sysctl in containers
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${config.systemd.package}/lib/systemd/systemd-sysctl";
};
};
# Enable hardlink and symlink restrictions. See

View file

@ -244,8 +244,7 @@ in
'';
systemd.services.systemd-udevd =
{ baseUnit = "${config.systemd.package}/example/systemd/system/systemd-udevd.service";
environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
{ environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
};
};

View file

@ -275,28 +275,16 @@ in
) cfg.buildMachines;
};
systemd.sockets."nix-daemon" =
{ description = "Nix Daemon Socket";
wantedBy = [ "sockets.target" ];
before = [ "multi-user.target" ];
unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket/";
socketConfig.ListenStream = "/nix/var/nix/daemon-socket/socket";
};
systemd.packages = [ nix ];
systemd.services."nix-daemon" =
{ description = "Nix Daemon";
path = [ nix pkgs.openssl pkgs.utillinux pkgs.openssh ]
systemd.services.nix-daemon =
{ path = [ nix pkgs.openssl pkgs.utillinux pkgs.openssh ]
++ optionals cfg.distributedBuilds [ pkgs.gzip ];
environment = cfg.envVars // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-bundle.crt"; };
unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket/";
serviceConfig =
{ ExecStart = "@${nix}/bin/nix-daemon nix-daemon --daemon";
KillMode = "process";
Nice = cfg.daemonNiceLevel;
{ Nice = cfg.daemonNiceLevel;
IOSchedulingPriority = cfg.daemonIONiceLevel;
LimitNOFILE = 4096;
};

View file

@ -49,22 +49,19 @@ with lib;
config = {
systemd.services."getty@" =
{ baseUnit = "${config.systemd.package}/example/systemd/system/getty@.service";
serviceConfig.ExecStart = "@${pkgs.utillinux}/sbin/agetty agetty --noclear --login-program ${pkgs.shadow}/bin/login --keep-baud %I 115200,38400,9600 $TERM";
{ serviceConfig.ExecStart = "@${pkgs.utillinux}/sbin/agetty agetty --noclear --login-program ${pkgs.shadow}/bin/login --keep-baud %I 115200,38400,9600 $TERM";
restartIfChanged = false;
};
systemd.services."serial-getty@" =
{ baseUnit = "${config.systemd.package}/example/systemd/system/serial-getty@.service";
serviceConfig.ExecStart =
{ serviceConfig.ExecStart =
let speeds = concatStringsSep "," (map toString config.services.mingetty.serialSpeed);
in "@${pkgs.utillinux}/sbin/agetty agetty --login-program ${pkgs.shadow}/bin/login %I ${speeds} $TERM";
restartIfChanged = false;
};
systemd.services."container-getty@" =
{ baseUnit = "${config.systemd.package}/example/systemd/system/container-getty@.service";
unitConfig.ConditionPathExists = "/dev/pts/%I"; # Work around being respawned when "machinectl login" exits.
{ unitConfig.ConditionPathExists = "/dev/pts/%I"; # Work around being respawned when "machinectl login" exits.
serviceConfig.ExecStart = "@${pkgs.utillinux}/sbin/agetty agetty --noclear --login-program ${pkgs.shadow}/bin/login --keep-baud pts/%I 115200,38400,9600 $TERM";
restartIfChanged = false;
};

View file

@ -115,6 +115,14 @@ sub boolIsTrue {
return $s eq "yes" || $s eq "true";
}
# As a fingerprint for determining whether a unit has changed, we use
# its absolute path. If it has an override file, we append *its*
# absolute path as well.
sub fingerprintUnit {
my ($s) = @_;
return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : "");
}
# Stop all services that no longer exist or have changed in the new
# configuration.
my (@unitsToStop, @unitsToSkip);
@ -166,7 +174,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
}
}
elsif (abs_path($prevUnitFile) ne abs_path($newUnitFile)) {
elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($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$/) {

View file

@ -147,12 +147,6 @@ in
config = mkIf (!config.boot.isContainer) {
systemd.services.kmod-static-nodes =
{ wantedBy = [ "sysinit.target" ];
baseUnit = "${config.systemd.package}/example/systemd/system/kmod-static-nodes.service";
environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
};
system.build = { inherit kernel; };
system.modulesTree = [ kernel ] ++ config.boot.extraModulePackages;
@ -224,37 +218,26 @@ in
# 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" ];
conflicts = [ "shutdown.target" ];
unitConfig =
{ DefaultDependencies = false;
ConditionCapability = "CAP_SYS_MODULE";
};
{ wantedBy = [ "multi-user.target" ];
restartTriggers = [ kernelModulesConf ];
environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
serviceConfig =
{ Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${config.systemd.package}/lib/systemd/systemd-modules-load";
# Ignore failed module loads. Typically some of the
{ # 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 ];
};
systemd.services.kmod-static-nodes =
{ wantedBy = [ "sysinit.target" ];
environment.MODULE_DIR = "/run/booted-system/kernel-modules/lib/modules";
};

View file

@ -41,12 +41,6 @@ in rec {
'';
};
baseUnit = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to an upstream unit file on which the NixOS unit configuration will be based.";
};
description = mkOption {
default = "";
type = types.str;

View file

@ -31,7 +31,6 @@ let
"sockets.target"
"graphical.target"
"multi-user.target"
"getty.target"
"network.target"
"network-online.target"
"nss-lookup.target"
@ -50,10 +49,17 @@ let
# Udev.
"systemd-udevd-control.socket"
"systemd-udevd-kernel.socket"
#"systemd-udevd.service"
"systemd-udevd.service"
"systemd-udev-settle.service"
"systemd-udev-trigger.service"
# Consoles.
"getty.target"
"getty@.service"
"serial-getty@.service"
"container-getty@.service"
"systemd-vconsole-setup.service"
# Hardware (started by udev when a relevant device is plugged in).
"sound.target"
"bluetooth.target"
@ -80,7 +86,8 @@ let
"systemd-initctl.service"
# Kernel module loading.
#"systemd-modules-load.service"
"systemd-modules-load.service"
"kmod-static-nodes.service"
# Filesystems.
"systemd-fsck@.service"
@ -146,6 +153,9 @@ let
"systemd-tmpfiles-clean.timer"
"systemd-tmpfiles-setup.service"
"systemd-tmpfiles-setup-dev.service"
# Misc.
"systemd-sysctl.service"
]
++ optionals cfg.enableEmergencyMode [
@ -198,7 +208,7 @@ let
serviceConfig = { name, config, ... }: {
config = mkMerge
[ (mkIf (config.baseUnit == null) { # Default path for systemd services. Should be quite minimal.
[ { # Default path for systemd services. Should be quite minimal.
path =
[ pkgs.coreutils
pkgs.findutils
@ -207,7 +217,7 @@ let
systemd
];
environment.PATH = config.path;
})
}
(mkIf (config.preStart != "")
{ serviceConfig.ExecStartPre = makeJobScript "${name}-pre-start" ''
#! ${pkgs.stdenv.shell} -e
@ -275,10 +285,7 @@ let
(if isList value then value else [value]))
as));
commonUnitText = def:
optionalString (def.baseUnit != null) ''
.include ${def.baseUnit}
'' + ''
commonUnitText = def: ''
[Unit]
${attrsToSection def.unitConfig}
'';
@ -358,6 +365,8 @@ let
units = pkgs.runCommand "units" { preferLocalBuild = true; }
''
mkdir -p $out
# Copy the upstream systemd units we're interested in.
for i in ${toString upstreamUnits}; do
fn=${systemd}/example/systemd/system/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
@ -368,6 +377,8 @@ let
fi
done
# Copy .wants links, but only those that point to units that
# we're interested in.
for i in ${toString upstreamWants}; do
fn=${systemd}/example/systemd/system/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
@ -376,18 +387,35 @@ let
for i in $fn/*; do
y=$x/$(basename $i)
cp -pd $i $y
if ! [ -e $y ]; then rm -v $y; fi
if ! [ -e $y ]; then rm $y; fi
done
done
for i in ${toString (mapAttrsToList (n: v: v.unit) cfg.units)}; do
ln -fs $i/* $out/
done
# Symlink all units provided listed in systemd.packages.
for i in ${toString cfg.packages}; do
ln -s $i/etc/systemd/system/* $out/
ln -s $i/etc/systemd/system/* $i/lib/systemd/system/* $out/
done
# Symlink all units defined by systemd.units. If these are also
# provided by systemd or systemd.packages, then add them as
# <unit-name>.d/overrides.conf, which makes them extend the
# upstream unit.
for i in ${toString (mapAttrsToList (n: v: v.unit) cfg.units)}; do
fn=$(basename $i/*)
if [ -e $out/$fn ]; then
if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
ln -sfn /dev/null $out/$fn
else
mkdir $out/$fn.d
ln -s $i/$fn $out/$fn.d/overrides.conf
fi
else
ln -fs $i/$fn $out/
fi
done
# Created .wants and .requires symlinks from the wantedBy and
# requiredBy options.
${concatStrings (mapAttrsToList (name: unit:
concatMapStrings (name2: ''
mkdir -p $out/'${name2}.wants'
@ -400,6 +428,7 @@ let
ln -sfn '../${name}' $out/'${name2}.requires'/
'') unit.requiredBy) cfg.units)}
# Stupid misc. symlinks.
ln -s ${cfg.defaultUnit} $out/default.target
ln -s rescue.target $out/kbrequest.target
@ -411,7 +440,7 @@ let
../nss-user-lookup.target ../swap.target $out/multi-user.target.wants/
${ optionalString config.services.journald.enableHttpGateway ''
ln -s ../systemd-journal-gatewayd.service $out/multi-user-target.wants/
ln -s ../systemd-journal-gatewayd.service $out/multi-user-target.wants/
''}
''; # */

View file

@ -52,19 +52,7 @@ in
# /dev/tty0 to prevent putting the X server in non-raw mode, and
# it has a restart trigger.
systemd.services."systemd-vconsole-setup" =
{ description = "Setup Virtual Console";
wantedBy = [ "sysinit.target" "multi-user.target" ];
before = [ "sysinit.target" "shutdown.target" ];
conflicts = [ "shutdown.target" ];
unitConfig =
{ DefaultDependencies = "no";
ConditionPathExists = "/dev/tty1";
};
serviceConfig =
{ Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${config.systemd.package}/lib/systemd/systemd-vconsole-setup /dev/tty1";
};
{ wantedBy = [ "multi-user.target" ];
restartTriggers = [ vconsoleConf ];
};