Like a lot of people, I manage all of my dotfiles in a GitHub Repo. The main reason that I do this is so that I can have a consistent environment, no matter where I log in. Over the years, my dotfiles repository has grown as I have added more functionality. In this post, I’ll share some things I do and explain why I do them that way.

Repository Layout

The layout of my repository is pretty simple. It contains a bin directory that contains some shell scripts that I have written, a bootstrap.sh file that is used to rsync the repo to my home directory, and the various dotfiles that I like to keep in source control. You can store any dotfile that doesn’t contain a secret in your repo. In addition to my .zshrc file, I store dotfiles for git, vim, aws, tmux, ssh (the config file), and gnupg. I also have a directory called .functions, where I store a bunch of various functions.

Functions Directory

The .functions directory gives me a place to store a number of different functions that I use on a regular basis without making my .zshrc file huge. I can create multiple files to group like commands together. For example, I have one that just has aws specific functions, one that has git functions, and so on. I can then source all the files at once with from my .zshrc with the following block:


# Source the functions directory
if [ -d ~/.functions ]; then
    for F in ~/.functions/*; do
        source $F
    done
fi

Working Across Mac and Linux

Since I work in both Mac and Linux, I’ve made sure my dotfiles work on both platforms. To do that, I use a simple case statement in my .zshrc file to detect the OS and load things that are specific to the operating system.


case "$OSTYPE" in
  darwin*)
    export SYSTEM_ICON=""
    ;;
  *)
    export SYSTEM_ICON="🐧"
    ;;
esac

I also have a macos file in my .functions directory for additional functions that I want to have available on my Mac.

Bootstraping

The bootstrap.sh file uses rsync to sync the files in the directory with the home directory. I always have it pull a fresh copy from GitHub in case there were changes made since the last time I ran it. It also sources the .zshrc file when it completes so that the current shell picks up any changes. By default the script prompts if you want to overwrite the files, but that can be overridden with the -f switch so that it can be run in automation.


#!/usr/bin/env zsh

#cd "$(dirname "${BASH_SOURCE}")";

cd $(dirname ${(%):-%x})

git pull origin master;

function doIt() {
    echo "Updating"
    rsync --exclude ".git/" \
        --exclude ".DS_Store" \
        --exclude ".osx" \
        --exclude ".idea/" \
        --exclude "bootstrap.sh" \
        --exclude "README.md" \
        --exclude "LICENSE" \
        --exclude ".gitignore" \
        -avh --no-perms . ~;
    chmod 700 ~/.ssh
    chmod 700 ~/.gnupg
    chmod 600 ~/.ssh/authorized_keys
    source ~/.zshrc;
}

if [[ "$1" == "--force" || "$1" == "-f" ]]; then
    doIt;
else
    if read -q '? This may overwrite existing files in your home directory. Are you sure? (y/n) ?'; then
      echo "\n"
      doIt; 
    fi
fi;
unset doIt;

Now I can quickly set up a new environment by cloning the repo and running the bootstrap.sh command.


git clone git@github.com:austincloudguru/dotfiles
cd dotfiles
./bootstrap.sh -f

Whenever I make a change to my current setup and check it in to my GitHub repo, I can easily update any other environment by running a git pull command and re-running bootstrap.sh on that host.