at v192 8.6 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4with import ../boot/systemd-unit-options.nix { inherit config lib; }; 5 6let 7 8 userExists = u: 9 (u == "") || any (uu: uu.name == u) (attrValues config.users.extraUsers); 10 11 groupExists = g: 12 (g == "") || any (gg: gg.name == g) (attrValues config.users.extraGroups); 13 14 makeJobScript = name: content: "${pkgs.writeScriptBin name content}/bin/${name}"; 15 16 # From a job description, generate an systemd unit file. 17 makeUnit = job: 18 19 let 20 hasMain = job.script != "" || job.exec != ""; 21 22 env = job.environment; 23 24 preStartScript = makeJobScript "${job.name}-pre-start" 25 '' 26 #! ${pkgs.stdenv.shell} -e 27 ${job.preStart} 28 ''; 29 30 startScript = makeJobScript "${job.name}-start" 31 '' 32 #! ${pkgs.stdenv.shell} -e 33 ${if job.script != "" then job.script else '' 34 exec ${job.exec} 35 ''} 36 ''; 37 38 postStartScript = makeJobScript "${job.name}-post-start" 39 '' 40 #! ${pkgs.stdenv.shell} -e 41 ${job.postStart} 42 ''; 43 44 preStopScript = makeJobScript "${job.name}-pre-stop" 45 '' 46 #! ${pkgs.stdenv.shell} -e 47 ${job.preStop} 48 ''; 49 50 postStopScript = makeJobScript "${job.name}-post-stop" 51 '' 52 #! ${pkgs.stdenv.shell} -e 53 ${job.postStop} 54 ''; 55 in { 56 57 inherit (job) description requires before partOf environment path restartIfChanged unitConfig; 58 59 after = 60 (if job.startOn == "stopped udevtrigger" then [ "systemd-udev-settle.service" ] else 61 if job.startOn == "started udev" then [ "systemd-udev.service" ] else 62 if job.startOn == "started network-interfaces" then [ "network-interfaces.target" ] else 63 if job.startOn == "started networking" then [ "network.target" ] else 64 if job.startOn == "ip-up" then [] else 65 if job.startOn == "" || job.startOn == "startup" then [] else 66 builtins.trace "Warning: job ${job.name} has unknown startOn value ${job.startOn}." [] 67 ) ++ job.after; 68 69 wants = 70 (if job.startOn == "stopped udevtrigger" then [ "systemd-udev-settle.service" ] else [] 71 ) ++ job.wants; 72 73 wantedBy = 74 (if job.startOn == "" then [] else 75 if job.startOn == "ip-up" then [ "ip-up.target" ] else 76 [ "multi-user.target" ]) ++ job.wantedBy; 77 78 serviceConfig = 79 job.serviceConfig 80 // optionalAttrs (job.preStart != "" && (job.script != "" || job.exec != "")) 81 { ExecStartPre = preStartScript; } 82 // optionalAttrs (job.preStart != "" && job.script == "" && job.exec == "") 83 { ExecStart = preStartScript; } 84 // optionalAttrs (job.script != "" || job.exec != "") 85 { ExecStart = startScript; } 86 // optionalAttrs (job.postStart != "") 87 { ExecStartPost = postStartScript; } 88 // optionalAttrs (job.preStop != "") 89 { ExecStop = preStopScript; } 90 // optionalAttrs (job.postStop != "") 91 { ExecStopPost = postStopScript; } 92 // (if job.script == "" && job.exec == "" then { Type = "oneshot"; RemainAfterExit = true; } else 93 if job.daemonType == "fork" || job.daemonType == "daemon" then { Type = "forking"; GuessMainPID = true; } else 94 if job.daemonType == "none" then { } else 95 throw "invalid daemon type `${job.daemonType}'") 96 // optionalAttrs (!job.task && !(job.script == "" && job.exec == "") && job.respawn) 97 { Restart = "always"; } 98 // optionalAttrs job.task 99 { Type = "oneshot"; RemainAfterExit = false; }; 100 }; 101 102 103 jobOptions = serviceOptions // { 104 105 name = mkOption { 106 # !!! The type should ensure that this could be a filename. 107 type = types.str; 108 example = "sshd"; 109 description = '' 110 Name of the job, mapped to the systemd unit 111 <literal><replaceable>name</replaceable>.service</literal>. 112 ''; 113 }; 114 115 startOn = mkOption { 116 #type = types.str; 117 default = ""; 118 description = '' 119 The Upstart event that triggers this job to be started. Some 120 are mapped to systemd dependencies; otherwise you will get a 121 warning. If empty, the job will not start automatically. 122 ''; 123 }; 124 125 stopOn = mkOption { 126 type = types.str; 127 default = "starting shutdown"; 128 description = '' 129 Ignored; this was the Upstart event that triggers this job to be stopped. 130 ''; 131 }; 132 133 postStart = mkOption { 134 type = types.lines; 135 default = ""; 136 description = '' 137 Shell commands executed after the job is started (i.e. after 138 the job's main process is started), but before the job is 139 considered running. 140 ''; 141 }; 142 143 preStop = mkOption { 144 type = types.lines; 145 default = ""; 146 description = '' 147 Shell commands executed before the job is stopped 148 (i.e. before systemd kills the job's main process). This can 149 be used to cleanly shut down a daemon. 150 ''; 151 }; 152 153 postStop = mkOption { 154 type = types.lines; 155 default = ""; 156 description = '' 157 Shell commands executed after the job has stopped 158 (i.e. after the job's main process has terminated). 159 ''; 160 }; 161 162 exec = mkOption { 163 type = types.str; 164 default = ""; 165 description = '' 166 Command to start the job's main process. If empty, the 167 job has no main process, but can still have pre/post-start 168 and pre/post-stop scripts, and is considered running 169 until it is stopped. 170 ''; 171 }; 172 173 respawn = mkOption { 174 type = types.bool; 175 default = true; 176 description = '' 177 Whether to restart the job automatically if its process 178 ends unexpectedly. 179 ''; 180 }; 181 182 task = mkOption { 183 type = types.bool; 184 default = false; 185 description = '' 186 Whether this job is a task rather than a service. Tasks 187 are executed only once, while services are restarted when 188 they exit. 189 ''; 190 }; 191 192 daemonType = mkOption { 193 type = types.str; 194 default = "none"; 195 description = '' 196 Determines how systemd detects when a daemon should be 197 considered running. The value <literal>none</literal> means 198 that the daemon is considered ready immediately. The value 199 <literal>fork</literal> means that the daemon will fork once. 200 The value <literal>daemon</literal> means that the daemon will 201 fork twice. The value <literal>stop</literal> means that the 202 daemon will raise the SIGSTOP signal to indicate readiness. 203 ''; 204 }; 205 206 setuid = mkOption { 207 type = types.addCheck types.str userExists; 208 default = ""; 209 description = '' 210 Run the daemon as a different user. 211 ''; 212 }; 213 214 setgid = mkOption { 215 type = types.addCheck types.str groupExists; 216 default = ""; 217 description = '' 218 Run the daemon as a different group. 219 ''; 220 }; 221 222 path = mkOption { 223 default = []; 224 description = '' 225 Packages added to the job's <envar>PATH</envar> environment variable. 226 Both the <filename>bin</filename> and <filename>sbin</filename> 227 subdirectories of each package are added. 228 ''; 229 }; 230 231 }; 232 233 234 upstartJob = { name, config, ... }: { 235 236 options = { 237 238 unit = mkOption { 239 default = makeUnit config; 240 description = "Generated definition of the systemd unit corresponding to this job."; 241 }; 242 243 }; 244 245 config = { 246 247 # The default name is the name extracted from the attribute path. 248 name = mkDefault name; 249 250 }; 251 252 }; 253 254in 255 256{ 257 258 ###### interface 259 260 options = { 261 262 jobs = mkOption { 263 default = {}; 264 description = '' 265 This option is a legacy method to define system services, 266 dating from the era where NixOS used Upstart instead of 267 systemd. You should use <option>systemd.services</option> 268 instead. Services defined using <option>jobs</option> are 269 mapped automatically to <option>systemd.services</option>, but 270 may not work perfectly; in particular, most 271 <option>startOn</option> conditions are not supported. 272 ''; 273 type = types.loaOf types.optionSet; 274 options = [ jobOptions upstartJob ]; 275 }; 276 277 }; 278 279 280 ###### implementation 281 282 config = { 283 284 systemd.services = 285 flip mapAttrs' config.jobs (name: job: 286 nameValuePair job.name job.unit); 287 288 }; 289 290}