services on a Mac

Say there’s a program that needs to run on your Mac all the time forever and ever. This post describes how to set that up. The example here sets up the script that runs elasticsearch.*

On a Mac, services are controlled by launchd, which is configured using launchctl. This example uses launchctl to set up a service that starts as soon as it’s configured, starts up automatically at system startup, and gets restarted every time the job dies.

1) Create a configuration (plist) file for it. Name the file like: com.yourorganization.whateverpackage.jobtitle.plist. In the example, mine is org.elasticsearch.test.plist


<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
http://www.apple.com/DTDs/PropertyList-1.0.dtd”&gt;

   
        Label
        org.elasticsearch.test
        ProgramArguments
       
            /Volumes/Projects/elasticsearch/bin/elasticsearch
            -f
       
        WorkingDirectory
        /Volumes/Projects/elasticsearch
        StandardErrorPath
        logs/service-err.log
        StandardOutPath
        logs/service-out.log
        OnDemand
       
        KeepAlive
       
        RunAtLoad
       
   

Some important bits explained:
org.elasticsearch.test – the Label is the job’s name. After the job is loaded into launchctl, this is how you can refer to it for start, stop, list.
/Volumes/Projects/elasticsearch/bin/elasticsearch – the ProgramArguments contains the name of the program you want to run, and then any arguments to pass to it. (there’s an alternative to put the program or script name in a separate place, but this is easy. It keeps it right next to its arguments.)
-f – this argument is specific to elasticsearch, but the meaning of it is important. Your program or script should not fork and then exit. This means it should run everything in the foreground and never exit. launchd has a job to keep your script running, so your script needs to keep running.
/Volumes/Projects/elasticsearch –  launchd will cd to the WorkingDirectory before running your program. Your script should not cd.

2) load your plist configuration:

  • copy your plist file to /Library/LaunchDaemons
  • Load it: launchctl load /Library/LaunchDaemons/org.elasticsearch.test.plist (your plist filename)
    • note that whatever user you are when you run this launchctl load will, by default, be thu user the job runs as. If you don’t like that, you can configure the UserName in your plist (man launchd.plist for more info). If you sudo launchctl load, your job will run as root.
  • Check it: launchctl list org.elasticsearch.test (your job label)
    • if you just do launchctl list, it’ll list all the jobs set up by your user. sudo launchctl list to see everything. Grep for yours.
    • listing the specific job gives you some status information. Look for a PID – that means your script is running.
    • if you see no PID and a LastExitStatus of 256, then your job might be forking and exiting. Don’t do that.
  • Now ps -ef | grep for your program, and see that it’s running.
    • Kill it. See whether it gets started again with no action on your part. It should.
    • Check your program’s logs.
  • If you need your program to NOT run constantly, you’ll need to unload it.
    • launchctl unload /Library/LaunchDaemons/org.elasticsearch.test.plist

Great! That’s the whole thing. For way more options, check the man pages. Also, here’s a poorly organized but nonetheless useful reference page.
* there is a service wrapper for elasticsearch, but it didn’t work for us. It doesn’t set the job up to run continually.