{ 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 <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); }; }