29027fd1e1
Using pkgs.lib on the spine of module evaluation is problematic because the pkgs argument depends on the result of module evaluation. To prevent an infinite recursion, pkgs and some of the modules are evaluated twice, which is inefficient. Using ‘with lib’ prevents this problem.
291 lines
8.3 KiB
Nix
291 lines
8.3 KiB
Nix
{ 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.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
|
||
<literal><replaceable>name</replaceable>.service</literal>.
|
||
'';
|
||
};
|
||
|
||
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 <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.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 <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 = 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 <option>systemd.services</option>
|
||
instead. Services defined using <option>jobs</option> are
|
||
mapped automatically to <option>systemd.services</option>, but
|
||
may not work perfectly; in particular, most
|
||
<option>startOn</option> 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);
|
||
|
||
};
|
||
|
||
}
|