Simple Per-User Bash Profile Configuration

While creating Ansible scripts to automate the configuration of servers, I frequently stumble across an issue where I need to setup a user's profile on the server that has a custom path.

The issue that arises is that the .bash_profile file is a single file where any number of PATH exports can be provided. Any step in a provisioning tool like Ansible should be aware that this only adds state. I have seen other DevOps workers use modules like lineinfile, which I abhor because you can never be certain that the module will work. You will always ask yourself, "did the PATH on line 5 get set correctly, or did someone change one character (or perhaps add a comment) that now breaks the lineinfile module task?"

When I write scripts I adhere to the "tell, don't ask" principle 1. By using lineinfile, you are asking the system if the line exists and, if it is missing, add the line. I much prefer telling the system, "here is your configuration file, just load it." If you ask a system what state it is in, you cannot be certain that it will be in the state later. If you simply tell it what state it should be in, that state shouldn't change (state changes can be mitigated by isolating it to a very small component).

To remove state we need a way to specify single-purpose .bash_profile files on a per-user basis. My solution to this is to create a .bash_profile.d directory that will contain these files, then have the .bash_profile file only contain the code to load each file found in the configuration subdirectory.

# .bash_profile
BASH_PROFILE_CONF_DIR="$HOME/.bash_profile.d"

if [ ! -d $BASH_PROFILE_CONF_DIR ]; then
  # create the configuration directory
  mkdir $BASH_PROFILE_CONF_DIR
  chmod 700 $BASH_PROFILE_CONF_DIR

  # add a dummy file so the `ls` in the loop doesn't err out
  touch $BASH_PROFILE_CONF_DIR/default
fi

for profile_file in `ls .bash_profile.d/*`; do
  source $profile_file
done

Add this script into the user's .bash_profile 2 and then re-login (or simply source ~/.bash_profile) to execute the script. It will first check for the existence of the configuration directory and create it if it is missing. Then it loops through any files in the subdirectory and loads them into the environment.

Note that a dummy configuration file default is stored in the configuration directory. This is to prevent any errors from occuring when the script runs the ls command.

Now when writing Ansible scripts, all you need to do is add a file into the .bash_profile.d directory and you are done. Once Ansible moves to the next task, a new SSH connection will be spawned and the profile will be updated with the new configuration.

Footnotes

  1. "Tell, Don't Ask" is a programming concept, but it works equally well when building systems. 

  2. This is only on the .bash_profile so if you want it on the .bashrc you will need to add it there as well.