{ config, lib, pkgs, ... }:
with lib;
with import ../boot/systemd-unit-options.nix { inherit config lib; };
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.script == "" && job.exec == "") && 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.str;
example = "sshd";
description = ''
Name of the job, mapped to the systemd unit
name.service.
'';
};
startOn = mkOption {
#type = types.str;
default = "";
description = ''
The Upstart event that triggers this job to be started. Some
are mapped to systemd dependencies; otherwise you will get a
warning. If empty, the job will not start automatically.
'';
};
stopOn = mkOption {
type = types.str;
default = "starting shutdown";
description = ''
Ignored; this was the Upstart event that triggers this job to be stopped.
'';
};
postStart = mkOption {
type = types.lines;
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.lines;
default = "";
description = ''
Shell commands executed before the job is stopped
(i.e. before systemd kills the job's main process). This can
be used to cleanly shut down a daemon.
'';
};
postStop = mkOption {
type = types.lines;
default = "";
description = ''
Shell commands executed after the job has stopped
(i.e. after the job's main process has terminated).
'';
};
exec = mkOption {
type = types.str;
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.str;
default = "none";
description = ''
Determines how systemd detects when a daemon should be
considered “running”. The value none means
that the daemon is considered ready immediately. The value
fork means that the daemon will fork once.
The value daemon means that the daemon will
fork twice. The value stop means that the
daemon will raise the SIGSTOP signal to indicate readiness.
'';
};
setuid = mkOption {
type = types.addCheck types.str userExists;
default = "";
description = ''
Run the daemon as a different user.
'';
};
setgid = mkOption {
type = types.addCheck types.str groupExists;
default = "";
description = ''
Run the daemon as a different group.
'';
};
path = mkOption {
default = [];
description = ''
Packages added to the job's PATH environment variable.
Both the bin and sbin
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 = mkDefault name;
};
};
in
{
###### interface
options = {
jobs = mkOption {
default = {};
description = ''
This option is a legacy method to define system services,
dating from the era where NixOS used Upstart instead of
systemd. You should use
instead. Services defined using are
mapped automatically to , but
may not work perfectly; in particular, most
conditions are not supported.
'';
type = types.loaOf types.optionSet;
options = [ jobOptions upstartJob ];
};
};
###### implementation
config = {
systemd.services =
flip mapAttrs' config.jobs (name: job:
nameValuePair job.name job.unit);
};
}