In the last few years, ruby has taken the world by storm. With different projects dependent on different versions of Ruby, many Rails, Jekyll, and Sinatra programmers have installed RVM to manage their ruby versions in OS X. I am one of them.
While RVM is awesome, it can make some previously simple tasks difficult. One such example is creating a
launchd daemon that invokes a ruby script. If your gems are installed with RVM, you will need to use RVM to write your daemon. That's where it gets complicated.
This tutorial teaches you how to schedule a ruby task on OS X using
launchd. You can schedule any ruby file to be run. It will be executed using the RVM ruby version of your choice.
The basic gist of this tutorial
You have a ruby file you want to schedule to run periodically with
launchd. To accomplish this, we will:
- Set set up an RVM alias. The RVM alias will let you run your ruby file "inside of" the RVM environment... instead of OS X's pre-installed ruby
- Create a 1-line bash script to run your ruby file using the RVM alias
- Register that bash script with launchd using a plist file
Your ruby file will now run in the background of your Mac at the specified interval. Ready to get started?
Step 1: Create your ruby file
This probably goes without saying, but you will need the ruby file you want to schedule! If you have not already, create the file. Note that your ruby file can use any RVM-installed gems. Just ensure the file runs from the command prompt using your RVM ruby...
which ruby > /Users/you/.rvm/rubies/ruby-1.9.3-p194/bin/ruby
And you're set:
...should return no errors, and work as expected.
Step 2: Create an RVM alias
Next, you will need to create what is called an RVM alias in order to run your ruby file inside of the launchd daemon. (Creating an alias is somewhat documented here.)
You need an RVM alias because launchd, cron, and other process schedulers operate in discrete bash shell environments. Simply calling
ruby from inside your launchd or cron script will not work; that will invoke the non-RVM ruby that OS X shipped with. Instead, you need an RVM alias, which will run your file through RVM's ruby, from inside launchd.
To create the alias, first get your current RVM version of ruby by running:
It will return something like this:
/Users/you/.rvm/rubies/ruby-1.9.3-p194/bin/ruby. We want that part in the middle that reads:
ruby-1.9.3-p194. That is your current ruby version string. Your ruby version may be different.
(N.B. You do not have to use the current RVM ruby version; that's just for the sake of simplicity in this tutorial. You may use any RVM-available ruby.)
Now that we know our current RVM version of ruby, we can create the actual RVM alias. Run:
rvm alias create my_app ruby-1.9.3-p194@my_app
Notice that we typed in the ruby version from the previous step here. Also, replace
my_app with the name of your app, so you can identify the reason this alias was created in the future. For example, if you are making a ruby daemon that empties your OS X trash every day,
my_app could be
Great. Now your RVM alias is set up and ready to use.
Step 3: Test run your ruby code using the RVM alias from the commandline
Usually, if you wanted to run a ruby file from a bash script, you'd just write:
But to run your ruby file with RVM, you will need to write:
Here, you are using the RVM-installed ruby to execute your file. Test this command now, and ensure it works before proceeding. (Your file should run and exit properly.)
Next, because $rvm_path cannot be expanded inside of launchd, you’ll need to substitute it with the absolute path. Just run:
echo $rvm_path > /Users/jerzy/.rvm/
and use the output of that command instead. So now your bash code should read something like this:
If that’s successfully running your ruby file, you are ready to go.
But before we move on to step 4, there's one last thing. If you have any arguments to your ruby file, make sure you add them in now. For example:
/Users/jerzy/.rvm/wrappers/my_app/ruby myfile.rb -a somearg
Step 4: Create a bash script to run your ruby code using the RVM alias
Next you need to create a bash file that calls your ruby file. It’s going to be exactly what we typed above, just in a bash file. This is the bash file that
launchd will invoke.
Create an empty bash file called
my_daemon.sh in the directory
/usr/local/bin. Inside the file, simply paste in what we typed a few seconds ago at the end of step 3:
/Users/jerzy/.rvm/wrappers/my_app/ruby myfile.rb -a somearg
And save it. To be sure that your script works, try running:
Your ruby code should run in all its glory. (If not, stop and figure out what is wrong!)
Step 5: Create a .plist file that tells launchd where your bash script is, and how often to run it
Now you’re ready to create your launchd plist file. This is the file that tells launchd what you want to run, how you want to run it, and when you want to run it. Here’s a template that should work for you:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.yourdomain.projectname</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/my_daemon.sh</string> </array> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>RunAtLoad</key> <true/> <key>StartInterval</key> <integer>21600</integer> </dict> </plist>
I will not get in to all of these settings; the settings have been optimized for this tutorial use case. The only parameter you need to configure is
StartInterval, which is the number of seconds between each run of your script. In the code above, you'll see I have it set to 21,600 seconds -- 6 hours.
Save this plist file to:
Step 6: Register your .plist file
You can now register your plist file with launchd. At the commandline, type:
launchctl load ~/Library/LaunchAgents/com.yourdomain.projectname.plist
Congratulations! Your daemon is now running!
If you want to manually invoke your script through launchd (without waiting the interval specified), just type:
launchctl start com.yourdomain.projectname
... and your script will be run. This is great for debugging, as it mirrors how your code will be run by launchd. You can also unregister the plist with launchd like this:
launchctl unload ~/Library/LaunchAgents/com.yourdomain.projectname.plist
After you unregister, the daemon will no longer run at the specified interval. You can re-register it, of course, by running the first command above (
If things aren’t working, there’s a few suggestions.
First of all, if it makes sense for your script, set the interval to 20 seconds. This will allow you to see what is happening without waiting 6 hours every time it runs.
Second, double check your permissions. They should look like this:
-rwxr--r-- 1 yourosxusername staff 548 Oct 29 15:58 com.yourdomain.projectname.plist -rwxr-xr-x 1 yourosxusername admin 114 Oct 29 16:31 my_daemon.sh -rwxr-xr-x 1 yourosxusername admin 2733 Oct 29 14:42 myfile.rb