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