nixpkgs/modules/system/upstart/upstart.nix

369 lines
9.3 KiB
Nix

{ config, pkgs, ... }:
with pkgs.lib;
let
upstart = pkgs.upstart;
userExists = u:
(u == "") || any (uu: uu.name == u) (attrValues config.users.extraUsers);
groupExists = g:
(g == "") || any (gg: gg.name == g) (attrValues config.users.extraGroups);
# From a job description, generate an systemd unit file.
makeUnit = job:
let
hasMain = job.script != "" || job.exec != "";
env = config.system.upstartEnvironment // job.environment;
preStartScript = pkgs.writeScript "${job.name}-pre-start.sh"
''
#! ${pkgs.stdenv.shell} -e
${job.preStart}
'';
startScript = pkgs.writeScript "${job.name}-start.sh"
''
#! ${pkgs.stdenv.shell} -e
${if job.script != "" then job.script else ''
exec ${job.exec}
''}
'';
postStartScript = pkgs.writeScript "${job.name}-post-start.sh"
''
#! ${pkgs.stdenv.shell} -e
${job.postStart}
'';
preStopScript = pkgs.writeScript "${job.name}-pre-stop.sh"
''
#! ${pkgs.stdenv.shell} -e
${job.preStop}
'';
postStopScript = pkgs.writeScript "${job.name}-post-stop.sh"
''
#! ${pkgs.stdenv.shell} -e
${job.postStop}
'';
in {
inherit (job) description path environment;
after =
if job.startOn == "stopped udevtrigger" then [ "systemd-udev-settle.service" ] else
if job.startOn == "started udev" then [ "systemd-udev.service" ] else
[];
wantedBy = if job.startOn == "" then [ ] else [ "multi-user.target" ];
serviceConfig =
''
${optionalString (job.preStart != "" && (job.script != "" || job.exec != "")) ''
ExecStartPre=${preStartScript}
''}
${optionalString (job.preStart != "" && job.script == "" && job.exec == "") ''
ExecStart=${preStartScript}
''}
${optionalString (job.script != "" || job.exec != "") ''
ExecStart=${startScript}
''}
${optionalString (job.postStart != "") ''
ExecStartPost=${postStartScript}
''}
${optionalString (job.preStop != "") ''
ExecStop=${preStopScript}
''}
${optionalString (job.postStop != "") ''
ExecStopPost=${postStopScript}
''}
${if job.script == "" && job.exec == "" then "Type=oneshot\nRemainAfterExit=true" else
if job.daemonType == "fork" then "Type=forking\nGuessMainPID=true" else
if job.daemonType == "none" then "" else
throw "invalid daemon type `${job.daemonType}'"}
${optionalString (!job.task && job.respawn) "Restart=always"}
'';
};
jobOptions = {
name = mkOption {
# !!! The type should ensure that this could be a filename.
type = types.string;
example = "sshd";
description = ''
Name of the Upstart job.
'';
};
description = mkOption {
type = types.string;
default = "";
description = ''
A short description of this job.
'';
};
startOn = mkOption {
# !!! Re-enable this once we're on Upstart >= 0.6.
#type = types.string;
default = "";
description = ''
The Upstart event that triggers this job to be started.
If empty, the job will not start automatically.
'';
};
stopOn = mkOption {
type = types.string;
default = "starting shutdown";
description = ''
The Upstart event that triggers this job to be stopped.
'';
};
preStart = mkOption {
type = types.string;
default = "";
description = ''
Shell commands executed before the job is started
(i.e. before the job's main process is started).
'';
};
postStart = mkOption {
type = types.string;
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.string;
default = "";
description = ''
Shell commands executed before the job is stopped
(i.e. before Upstart kills the job's main process). This can
be used to cleanly shut down a daemon.
'';
};
postStop = mkOption {
type = types.string;
default = "";
description = ''
Shell commands executed after the job has stopped
(i.e. after the job's main process has terminated).
'';
};
exec = mkOption {
type = types.string;
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.
'';
};
script = mkOption {
type = types.string;
default = "";
description = ''
Shell commands executed as the job's main process. Can be
specified instead of the <varname>exec</varname> attribute.
'';
};
respawn = mkOption {
type = types.bool;
default = true;
description = ''
Whether to restart the job automatically if its process
ends unexpectedly.
'';
};
restartIfChanged = mkOption {
type = types.bool;
default = true;
description = ''
Whether the job should be restarted if it has changed after a
NixOS configuration switch.
'';
};
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.
'';
};
environment = mkOption {
type = types.attrs;
default = {};
example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
description = ''
Environment variables passed to the job's processes.
'';
};
daemonType = mkOption {
type = types.string;
default = "none";
description = ''
Determines how Upstart 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.string;
check = userExists;
default = "";
description = ''
Run the daemon as a different user.
'';
};
setgid = mkOption {
type = types.string;
check = groupExists;
default = "";
description = ''
Run the daemon as a different group.
'';
};
extraConfig = mkOption {
type = types.string;
default = "";
example = "limit nofile 4096 4096";
description = ''
Additional Upstart stanzas not otherwise supported.
'';
};
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.
'';
};
console = mkOption {
default = "";
example = "console";
description = ''
If set to <literal>output</literal>, job output is written to
the console. If it's <literal>owner</literal>, additionally
the job becomes owner of the console. It it's empty (the
default), output is written to
<filename>/var/log/upstart/<replaceable>jobname</replaceable></filename>
'';
};
};
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 = mkDefaultValue name;
# Default path for Upstart jobs. Should be quite minimal.
path =
[ pkgs.coreutils
pkgs.findutils
pkgs.gnugrep
pkgs.gnused
upstart
];
};
};
in
{
###### interface
options = {
jobs = mkOption {
default = {};
description = ''
This option defines the system jobs started and managed by the
Upstart daemon.
'';
type = types.loaOf types.optionSet;
options = [ jobOptions upstartJob ];
};
system.upstartEnvironment = mkOption {
type = types.attrs;
default = {};
example = { TZ = "CET"; };
description = ''
Environment variables passed to <emphasis>all</emphasis> Upstart jobs.
'';
};
};
###### implementation
config = {
system.build.upstart = upstart;
boot.systemd.services =
flip mapAttrs' config.jobs (name: job:
nameValuePair "${job.name}.service" job.unit);
};
}