One of the minor annoyances of working in Unix-land is the variety of ways to set user environment variables: .zshenv, .bash_profile, .profile, .xinitrc, and so forth. Depending on where you set them, your variables may be visible in interactive shells, login shells, X applications, or some strange mixture. Even worse is that some, but not all, environment variables are set when interpreting your crontab, so that has yet another set of rules you have to remember.

I’ve been gradually working on setting up my home directory so that it contains everything (barring system packages) that I need to work, so that migrating to a new computer is just a matter of installing packages and then copying my $HOME. This only works if I have a consistent way to set environment variables, so that they are visible to every program that runs under my account, regardless of how I logon. And I think I’ve finally got it.

This process requires changing a couple of system-wide settings, but only in special circumstances:

  • If you care about having environment variables enabled when you login via TTY or SSH, make the following change to /etc/pam.d/login: find the line

     session       required   pam_env.so readenv=1 envfile=/etc/default/locale
    

    and change it to read

     session       required   pam_env.so readenv=1 user_readenv=1 envfile=/etc/default/locale
    

    (In the default config, pam_env is loaded only when you login via your display manager: MDM on Mint, GDM on Gnome, etc.)

  • If you want to set LD_LIBRARY_PATH then you have to make a change to the Xsession settings: add a file 90preserve_ld_library_path to /etc/X11/Xsession.d/ containing

     STARTUP="/usr/bin/env LD_LIBRARY_PATH=${LD_LIBRARY_PATH} ${STARTUP}"
    

    This is only necessary because X normally starts ssh-agent which clears `LDLIBRARYPATH for security reasons. Another option is to disable loading of ssh-agent entirely.

  • If you want your environment variables to apply to your crontab (and you do), edit /etc/pam.d/cron and find the line

     session       required   pam_env.so envfile=/etc/default/locale
    

    with

     session       required   pam_env.so user_readenv=1 envfile=/etc/default/locale
    

After making that change(s), create a file ~/.pam_environment and add your variables to it like this:

# HOME is not defined when this file is executed, and BOOST_ROOT needs 
# to be set somewhere, so...
HOME       DEFAULT=/home/@{PAM_USER}
BOOST_ROOT DEFAULT=${HOME}/bin/boost

# Setup path
PATH DEFAULT=${HOME}/bin:${HOME}/bin/bin:${PATH}
PATH DEFAULT=${HOME}/bin/rakudo:${PATH}
PATH DEFAULT=/opt/texlive/2016/bin/x86_64-linux:${PATH}
PATH DEFAULT=/opt/teyjus:${PATH}
PATH DEFAULT=/opt/bin:${PATH}
PATH DEFAULT=${HOME}/.cabal/bin:${PATH}
PATH DEFAULT=${HOME}/.local/bin:${PATH}
PATH DEFAULT=/opt/cabal/1.20/bin:/opt/ghc/7.8.4/bin:${PATH}
PATH DEFAULT=${HOME}/bin/tmsu-x86_64-0.6.1/bin:${PATH}
PATH DEFAULT=${HOME}/bin/boost:${PATH}

# Python paths
PYTHONPATH DEFAULT=${HOME}/.local/lib/python/site-packages:${PYTHONPATH}
PYTHONPATH DEFAULT=${HOME}/.local/lib/python2.7/site-packages:${PYTHONPATH}

# Manpaths
MANPATH DEFAULT=/opt/texlive/2014/texmf/doc/man:${MANPATH}
MANPATH DEFAULT=${HOME}/docs/man:${MANPATH}
MANPATH DEFAULT=${HOME}/bin/man:${MANPATH}

# Infopaths
INFOPATH DEFAULT=/opt/texlive/2016/texmf/doc/info:${INFOPATH}

# Library/link paths
LD_LIBRARY_PATH DEFAULT=/usr/local/lib:${BOOST_ROOT}/stage/lib:${HOME}/lib:${HOME}/lib/sfml:${LD_LIBRARY_PATH}
LIBRARY_PATH    DEFAULT=${HOME}/lib:${HOME}/lib/sfml:${BOOST_ROOT}/stage/lib:${LIBRARY_PATH}

# Include paths
CPLUS_INCLUDE_PATH DEFAULT=${HOME}/include:${BOOST_ROOT}:${CPLUS_INCLUDE_PATH}

# TeX paths
TEXINPUTS DEFAULT=.:${HOME}/.texmf/tex/:
BIBINPUTS DEFAULT=.:${HOME}/.texmf/tex/:

This is my actual .pam_environment. Setting environment variables that refer to other, existing variables (e.g., extending $PATH) uses a fancy syntax:

NAME DEFAULT=...

Within the value, you can use ${...} to expand environment variables, and use @{...} to expand PAM_ITEMs (as I did to get the user’s login name, because $USER isn’t set yet).

After that, it’s basically a matter of setting things the way you want. Note that if you aren’t using any expansions, you can write just

NAME=value...

With this setup, all of my environment variables are available to any process that runs under my account, including cron jobs! No more fighting with my editor to get it to use my $PATH when it runs my build tools, and no more hard-coded paths in crontab, etc.