OS X: LaunchDaemon not running: Service could not initialize
I used Apple's seemingly straightforward docs to create a LaunchDaemon to run a Node.js script I wrote.
Here's the plist
file. It's basically exactly a copy-paste from Apple's docs, set to run every 300 seconds:
<?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.wintr.eodemail</string>
<key>ProgramArguments</key>
<array>
<string>~/projects/eod_email/eod</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>StandardOutPath</key>
<string>/var/log/eod-email.log</string>
<key>StandardErrorPath</key>
<string>/var/log/eod-email.log</string>
<key>Debug</key>
<true/>
</dict>
</plist>
Here's the error I get in /var/log/system.log
:
Jul 22 10:55:52 Nick-Cox com.apple.xpc.launchd[1] (com.wintr.eodemail[7097]): Service could not initialize: 14E46: xpcproxy + 13421 [1402][7D917364-B96E-3F93-B923-A89B5BF5736D]: 0x2
What I've done:
- It has the same permissions as the rest of the files in /Library/LaunchDaemons (
-rw-r--r--
, owned by root) - I read the docs for xpc, but that didn't help much.
- I made sure that the Node.js script was adequately permissive (777), and runnable from the command line (it is).
- Tried the absolute path to the file (
/Users/nickcox/projects/eod_email/eod
) and made sure I ranlaunchctl unload (daemonname)
andlaunchctl load (daemon name)
This seems much more complicated than cron, which is apparently deprecated, according to those Apple docs. What do I need to do to get this script to run on a schedule?
Getting started with launchctl can definitely be a frustrating experience. I found a lot of articles explaining what you should do but few little downloadable samples. Here is a simple LaunchDaemon that will hopefully be a good starting point. You can just download the files here if you don't feel like copying and pasting.
Note: you need to replace MY_USER_NAME with your username. The plist needs to find your script.
// at ~/Desktop/testdaemon/com.wintr.eodemail.plist
<?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.wintr.eodemail</string>
<key>Program</key>
<string>/Users/MY_USER_NAME/Desktop/testdaemon/testdaemon.sh</string>
<key>StandardErrorPath</key>
<string>/var/log/eod-email.log</string>
<key>StandardOutPath</key>
<string>/var/log/eod-email.log</string>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>15</integer>
</dict>
</plist>
This is a simple daemon script that will append the datetime to a file on your desktop. Note: since the script is run as root, tilde (~) won't be the home directory you expect.
// at ~/Desktop/testdaemon/testdaemon.sh
#!/bin/sh
home="/Users/MYUSERNAME" ## note -- this will be run as root, ~ is not your normal user
now=$(date "+%Y-%m-%d %H.%M.%S")
echo $now >> "$home/Desktop/TestFile.txt"
Finally, I always write a little shell script to install the LaunchDaemons since it's easy to make a mistake. Since launchctl runs your script as root it requires that script's permissions not be writeable by others, since that would essentially give them root privileges.
// ~/Desktop/testdaemon/install.sh
#!/bin/sh -e
plist_path="com.wintr.eodemail.plist"
plist_filename=$(basename "$plist_path")
install_path="/Library/LaunchDaemons/$plist_filename"
echo "installing launchctl plist: $plist_path --> $install_path"
sudo cp -f "$plist_path" "$install_path"
sudo chown root "$install_path"
sudo chmod 644 "$install_path"
sudo launchctl unload "$install_path"
sudo launchctl load "$install_path"
echo "to check if it's running, run this command: sudo launchctl list | grep wintr"
echo "to uninstall, run this command: sudo launchctl unload \"$install_path\""