summary refs log blame commit diff
path: root/nixos/modules/system/upstart/upstart.nix
blob: 5c0461304072d3fc2a1feb4b6848d82a5689b7eb (plain) (tree)
1
2
3
4
5
6
7
                           
 

                                                                     
 
   
 




                                                                              
 

                                                                                   

                                                          

       
                                                   
 
                            
 
                                                            
          



                                    
                                                     






                                                       
                                                              




                                    
                                                          




                                    
                                                            



                                    
        
 
                                                                                                    

             

                                                                                            
                                                                                                 
                                                                              

                                                                      

                                                                                                           
 
              
                                                                                               
                       
 
                


                                                               
 
                     


                                                                                     

                                                                                   








                                                                                                         
                                                                                                                          

                                                            
                                                                                            


                                                         

      
 
                                  
 

                                                                 
                       

                       

                                                                   

         
 
                        
                        

                      


                                                                     



                       
                       
                                    
                      
                                                                                 


         
                          
                         





                                                                    


                        
                         


                                                         
                                                                     

                                              

      
                         
                         







                                                           
                       



                                                                  
                                                                  



                            


















                                                                 
                           
                       

                       
                                                              








                                                                          
                       
                                                 






                                           
                                                  





                                            
                     
                   

                                                                             
                                                                       



                                                 
    
 
 
                                       
 
               
 
                       
                                  
                                                                                            
        
 


              
 
                                                                       
                            
 
      
 
    

  
 


                  
 

             
                     

                      






                                                                      
         
                                         
                                          
      
 

    

                       
 

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

  };

}