c52fd85990
This makes the system journal readable by users in the systemd-journal, wheel and adm groups. It also allows users to read their own journals. Note that this doesn't change the permissions of existing journals.
610 lines
17 KiB
Nix
610 lines
17 KiB
Nix
{ 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"
|
||
"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"
|
||
"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"
|
||
|
||
# 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;
|
||
"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;
|
||
};
|
||
};
|
||
};
|
||
|
||
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}
|
||
'';
|
||
};
|
||
|
||
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.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 /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);
|
||
|
||
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;
|
||
|
||
};
|
||
}
|