nixpkgs/modules/system/boot/systemd.nix

481 lines
13 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ config, pkgs, ... }:
with pkgs.lib;
with import ./systemd-unit-options.nix { inherit config pkgs; };
let
cfg = config.boot.systemd;
systemd = pkgs.systemd;
makeUnit = name: unit:
pkgs.runCommand "unit" { inherit (unit) text; }
(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"
"rescue.target"
"network.target"
"nss-lookup.target"
"nss-user-lookup.target"
"syslog.target"
"time-sync.target"
#"cryptsetup.target"
"sigpwr.target"
# 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"
"runlevel0.target"
"runlevel1.target"
"runlevel2.target"
"runlevel3.target"
"runlevel4.target"
"runlevel5.target"
"runlevel6.target"
# Random seed.
"systemd-random-seed-load.service"
"systemd-random-seed-save.service"
# Utmp maintenance.
"systemd-update-utmp-runlevel.service"
"systemd-update-utmp-shutdown.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"
"systemd-hibernate.service"
"systemd-suspend.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"
# Password entry.
"systemd-ask-password-console.path"
"systemd-ask-password-console.service"
"systemd-ask-password-wall.path"
"systemd-ask-password-wall.service"
];
upstreamWants =
[ "basic.target.wants"
"sysinit.target.wants"
"sockets.target.wants"
"local-fs.target.wants"
"multi-user.target.wants"
"shutdown.target.wants"
];
rescueService =
''
[Unit]
Description=Rescue Shell
DefaultDependencies=no
Conflicts=shutdown.target
After=sysinit.target
Before=shutdown.target
[Service]
Environment=HOME=/root
WorkingDirectory=/root
ExecStartPre=-${pkgs.coreutils}/bin/echo 'Welcome to rescue mode. Use "systemctl default" or ^D to enter default mode.'
#ExecStart=-/sbin/sulogin
ExecStart=-${pkgs.bashInteractive}/bin/bash --login
ExecStopPost=-${systemd}/bin/systemctl --fail --no-block default
Type=idle
StandardInput=tty-force
StandardOutput=inherit
StandardError=inherit
KillMode=process
# Bash ignores SIGTERM, so we send SIGHUP instead, to ensure that bash
# terminates cleanly.
KillSignal=SIGHUP
'';
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;
"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
];
};
};
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 enable;
text =
''
[Unit]
${attrsToSection def.unitConfig}
'';
};
serviceToUnit = name: def:
{ inherit (def) wantedBy enable;
text =
''
[Unit]
${attrsToSection def.unitConfig}
[Service]
Environment=PATH=${def.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.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}
''}
''}
${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 enable;
text =
''
[Unit]
${attrsToSection def.unitConfig}
[Socket]
${attrsToSection def.socketConfig}
'';
};
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)}
ln -s ${cfg.defaultUnit} $out/default.target
#ln -s ../getty@tty1.service $out/multi-user.target.wants/
ln -s ../local-fs.target ../remote-fs.target ../network.target ../swap.target $out/multi-user.target.wants/
''; # */
in
{
###### interface
options = {
boot.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.
'';
};
wantedBy = mkOption {
default = [];
types = types.listOf types.string;
description = "Units that want (i.e. depend on) this unit.";
};
};
};
boot.systemd.packages = mkOption {
default = [];
type = types.listOf types.package;
description = "Packages providing systemd units.";
};
boot.systemd.targets = mkOption {
default = {};
type = types.attrsOf types.optionSet;
options = [ unitOptions unitConfig ];
description = "Definition of systemd target units.";
};
boot.systemd.services = mkOption {
default = {};
type = types.attrsOf types.optionSet;
options = [ serviceOptions unitConfig serviceConfig ];
description = "Definition of systemd service units.";
};
boot.systemd.sockets = mkOption {
default = {};
type = types.attrsOf types.optionSet;
options = [ socketOptions unitConfig ];
description = "Definition of systemd socket units.";
};
boot.systemd.defaultUnit = mkOption {
default = "multi-user.target";
type = types.uniq types.string;
description = "Default unit started when the system boots.";
};
boot.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.";
};
};
###### implementation
config = {
system.build.systemd = systemd;
system.build.units = units;
environment.systemPackages = [ systemd ];
environment.etc =
[ { source = units;
target = "systemd/system";
}
{ source = pkgs.writeText "systemd.conf"
''
[Manager]
'';
target = "systemd/system.conf";
}
{ source = pkgs.writeText "journald.conf"
''
[Journal]
${optionalString (config.services.journald.console != "") ''
ForwardToConsole=yes
TTYPath=${config.services.journald.console}
''}
'';
target = "systemd/journald.conf";
}
];
system.activationScripts.systemd =
''
mkdir -p /var/lib/udev -m 0755
# 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
'';
# Target for charon send-keys to hook into.
boot.systemd.targets.keys =
{ description = "Security Keys";
};
boot.systemd.units =
{ "rescue.service".text = rescueService; }
// 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;
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";
};
};
}