Start a process in screen when OS X user logs on

Not as easy as I first thought since OS X prevents launchd launchers from starting a daemon (such as screen). But it's doable, just follow my notes and you should be good to go!

For me, this turned out to be a threeway headache (sudo permissions, cron with screen and not least launchd). I don't fancy the idea of storing my root password in my script so I first experimented with permissions (chown) to no avail. As a eureka moment (more facepalm..) i stumbled upon the magic sudoers file.

$ sudo visudo

Add the following text to the bottom of the file, replace "username" with your own.

#/etc/sudoers
...

username ALL=(ALL) NOPASSWD: /path/to/script

Save and close.You should now be able to execute your choosen script without the need to enter your root password. Of course, be careful so that just the intended script and nothing else gets the elevated powers.

When the intended script now have the right permissions, let's create the start script that launchd will invoke at login. The cron-job is set to run 1min ahead in time.

#/path/to/wrapper/script

#!/bin/bash
nofCrontabs=`crontab -l | grep -c screenTag`

# If there already exists cronjobs matching screenTag, remove them
if [[ $nofCrontabs -gt 0 ]] 
then
    `crontab -l | sed '/screenTag/d'` | crontab -
fi

# Get current unix timestamp
curdate=`date "+%s"`

# Add 60 seconds
execTime=$(($curdate + 60))

# And parse needed time-variables with gawk
min=`echo $execTime | gawk '{print strftime("%M", $0)}'`
hour=`echo $execTime | gawk '{print strftime("%H", $0)}'`
dayOfMonth=`echo $execTime | gawk '{print strftime("%d", $0)}'`
month=`echo $execTime | gawk '{print strftime("%m", $0)}'`

# Add the cronjob
line="$min $hour $dayOfMonth $month * screen -S screenTag -d -m -- sh -c 
      'sudo /path/to/script'"
(crontab -l; echo "$line" ) | crontab -

Adjust screenTag and the script path to your environment to make it work. Since I use gawk (and it's not part of a standard OSX installation), make sure bash finds it. Either adjust your $PATH variable or simply replace gawk with the complete path (ie paste the results from "which gawk").

Finally, create the plist-file that OS X reads at login. Place it in "~/Library/LaunchAgents" if you want it to be run at login.

#~/Library/LaunchAgents/com.example.name.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.example.name</string>
	<key>ProgramArguments</key>
	<array>
	    <string>/path/to/wrapper/script</string>
	</array>
	<key>WatchPaths</key>
	<array>	
	    <string>/Folder/to/watch</string>
	</array>
	<key>LaunchOnlyOnce</key>
	<true/>
    </dict>
</plist>

In my case, I want to launch the script after a specific program already has started. For me, it is sufficient to look at the folder where that program has it's log files. At startup, it writes to a file in the folder and that triggers OS X to launch my wrapper-script which in turn creates a cronjob set to go off the next minute.
After launchd has started the script, it is never run again. Launchd takes care of the clean up.

A useful tool to troubleshoot is launchctl. Use the "list" option to list all processes. Grep the output to your liking if you want to narrow down the output.