Setting environment variables on login in OS X
The problem
I require the use of several shell commands in Sublime Text 3. Some used by plugins (requiring for example coffee
on the path), others by the build system.
As such, I require a proper PATH
so I don’t have to install stuff in the system directories.
For example, I use a custom build system made using Python to handle compilation and propagation of various websites I make. That custom script is placed in a virtualenv.
I install coffeescript through npm
, so it gets installed as /usr/local/share/npm/bin/coffee
.
And so on.
Common solutions
The most common solution involves editing /etc/launchd.conf
and placing a setenv there. This is bad in my case because:
- I don’t want root processes to have
/usr/local/bin
in their environment, because under homebrew, that space is writable by users; - I don’t want root nor other users to load stuff from a specific user’s virtual environment;
- I want to be able to have a clean alternate user with which I can sign in in case something is messed up.
Thus I do not want to put that in /etc/launchd.conf
.
A better solution
The approach presented in this stackoverflow comment is almost what I needed.
Almost, because while this does work for Finder, Spotlight and the Dock, I actually launch stuff using Alfred, and somehow Alfred seems to be launched before the environment is set.
Or maybe the “Login Items” in the User preferences pane aren’t affected by the env change? I don’t really care why, only that if affects me.
And so launching Sublime Text 3 using Alfred means not having the proper PATH, which is the point of all this.
A working solution
So this solution is a variant from the above stackoverflow post.
Create a launchd file:
cat > ~/Library/LaunchAgents/local.launch-at-login.conf.plist <<'EOF'
<?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>local.launch-at-login.conf</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>~/.launch_at_login.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
Create the referenced script:
cat > ~/.launch_at_login.sh <<'EOF'
#!/bin/sh
# This is executed at login through ~/Library/LaunchAgents/local.launchd.conf.plist
launchctl setenv PATH ~/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:~/.virtualenvs/pypy/bin
pid=$(pgrep Alfred)
if [ $? -ne 0 ]; then
open -a 'Alfred 2.app'
fi
EOF
Then remove Alfred from the user’s login items, in the “Users & Groups” preference pane.
Logout, then log back in, and verify it’s working by starting Sublime Text from Alfred, then opening the console, and typing something like:
>>> import os
>>> os.environ['PATH']
'/Users/serge/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/Users/serge/.virtualenvs/pypy/bin'
Side effects
The same approach could also be used to serialize startup items, which might help attaining faster login times (at least on non-ssd drives!). Simply add a delay between items, or use a slightly more sophisticated script that waits for the previous program to have started before launching the next:
#!/bin/sh
# This is executed at login through ~/Library/LaunchAgents/local.launchd.conf.plist
launchctl setenv PATH ~/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:~/.virtualenvs/pypy/bin
function condlaunch {
current_pid=$(pgrep -af "$1")
if [ $? -ne 0 ]; then
if [ -n "$2" ]; then
sleep $2
fi
open -a "$1"
fi
}
condlaunch 'Alfred 2.app'
condlaunch 'Mail.app' 1
condlaunch 'Airmail.app' 3
condlaunch 'iTerm.app' 3
condlaunch 'Things.app' 3