{ config, pkgs, ... }: with pkgs.lib; let upstart = pkgs.upstart; # Path for Upstart jobs. Should be quite minimal. upstartPath = [ pkgs.coreutils pkgs.findutils pkgs.gnugrep pkgs.gnused upstart ]; # From a job description, generate an Upstart job file. makeJob = job: let hasMain = job.script != "" || job.exec != ""; env = config.system.upstartEnvironment // job.environment; jobText = let log = "/var/log/upstart/${job.name}"; in '' # Upstart job `${job.name}'. This is a generated file. Do not edit. description "${job.description}" ${if isList job.startOn then "start on ${concatStringsSep " or " job.startOn}" else if job.startOn != "" then "start on ${job.startOn}" else "" } ${optionalString (job.stopOn != "") "stop on ${job.stopOn}"} env PATH=${makeSearchPath "bin" (job.path ++ upstartPath)}:${makeSearchPath "sbin" (job.path ++ upstartPath)} ${concatMapStrings (n: "env ${n}=\"${getAttr n env}\"\n") (attrNames env)} ${optionalString (job.preStart != "") '' pre-start script exec >> ${log} 2>&1 ${job.preStart} end script ''} ${if job.script != "" && job.exec != "" then abort "Job ${job.name} has both a `script' and `exec' attribute." else if job.script != "" then '' script exec >> ${log} 2>&1 ${job.script} end script '' else if job.exec != "" then '' script exec >> ${log} 2>&1 exec ${job.exec} end script '' else "" } ${optionalString (job.postStart != "") '' post-start script exec >> ${log} 2>&1 ${job.postStart} end script ''} ${optionalString job.task "task"} ${optionalString (!job.task && job.respawn) "respawn"} ${ # preStop is run only if there is exec or script. # (upstart 0.6.5, job.c:562) optionalString (job.preStop != "") (assert hasMain; '' pre-stop script exec >> ${log} 2>&1 ${job.preStop} end script '')} ${optionalString (job.postStop != "") '' post-stop script exec >> ${log} 2>&1 ${job.postStop} end script ''} ${optionalString (!job.task) ( if job.daemonType == "fork" then "expect fork" else if job.daemonType == "daemon" then "expect daemon" else if job.daemonType == "stop" then "expect stop" else if job.daemonType == "none" then "" else throw "invalid daemon type `${job.daemonType}'" )} ${job.extraConfig} ''; in pkgs.runCommand ("upstart-" + job.name + ".conf") { inherit (job) buildHook; inherit jobText; } '' eval "$buildHook" echo "$jobText" > $out ''; jobOptions = { name = mkOption { # !!! The type should ensure that this could be a filename. type = types.string; example = "sshd"; description = '' Name of the Upstart job. ''; }; buildHook = mkOption { type = types.string; default = "true"; description = '' Command run while building the Upstart job. Can be used to perform simple regression tests (e.g., the Apache Upstart job uses it to check the syntax of the generated <filename>httpd.conf</filename>. ''; }; description = mkOption { type = types.string; default = "(no description given)"; 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. ''; }; 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. ''; }; 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. ''; }; }; upstartJob = {name, config, ...}: { options = { jobDrv = mkOption { default = makeJob config; type = types.uniq types.package; description = '' Derivation that builds the Upstart job file. The default value is generated from other options. ''; }; }; config = { # The default name is the name extracted from the attribute path. name = mkDefaultValue ( replaceChars ["<" ">" "*"] ["_" "_" "_name_"] name ); }; }; in { ###### interface options = { jobs = mkOption { default = {}; description = '' This option defines the system jobs started and managed by the Upstart daemon. ''; type = types.attrsOf types.optionSet; options = [ jobOptions upstartJob ]; }; tests.upstartJobs = mkOption { internal = true; default = {}; description = '' Make it easier to build individual Upstart jobs. (e.g., <command>nix-build /etc/nixos/nixos -A tests.upstartJobs.xserver</command>). ''; }; 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; environment.etc = flip map (attrValues config.jobs) (job: { source = job.jobDrv; target = "init/${job.name}.conf"; } ); # Upstart can listen on the system bus, allowing normal users to # do status queries. services.dbus.packages = [ upstart ]; }; }