Version controlled configs

:: linux, tricks

Disclaimer: I typed this is a hurry and haven’t proof-read it, or tried running any of the code (except the script at the end).

So a while ago I decided to use git to track all my dot-files and other assorted configuration stuff that each of my linux systems need. I’m going to try to outline how I did this:

First, I have some files that exist on all my machines. These I’m going to call common files. Things like muttrc, irssi config scripts, etc.  These are also mostly configuration I don’t mind making public.

Second, I have some stuff that are common, but need to be private. I call these private files.

Lastly, I have files that are specific to a machine, but I still want tracked. Things like .xinitrc and .profile are in here.

First thing we did is create some directories:

    mkdir -p .config.git/common
    mkdir -p .config.git/private

So, all the common configuration stuff will go in ../common, the ssh/gpg/other private things will go in ../private, and the machine specific stuff will just go in .config.git.

Next we create some repos.

    git init .config.git/common
    git init .config.git/private
    git init .config.git

Excellent. Be careful with this next step, you’re about to not have any configuration.

    cd $HOME
    mv .bashrc
    mv *rc .config.git/common
    mv .ssh .config.git/private
    mv .gnupg .config.git/private
    mv .profile .config.git/
    mv .config.git/common/.xinitrc .config.git/

Now we want to link all the configurations together using submodules. Git allows you to add one repository as a kind of dependency of another. This is called a submodule. You do this as follows:

    cd .config.git
    git submodule add $HOME/.config.git/common
    git submodule add $HOME/.config.git/private

Alternatively, if you want to push these to your server first:

    cd $HOME/.config.git/common
    git add *
    git commit -a -m "Init"
    ssh user@myserver.com git init --bare ~/repos/config.common
    git remote add origin myserver.com:~/repos/config.common
    git push origin master
    cd $HOME/.config.git/private
    git add *
    git commit -a -m "init"

    ssh user@myserver.com git init --bare ~/repos/config.private
    git remote add origin myserver.com:~/repos/config.private
    git push origin master
    cd $HOME/.config.git/
    git add .profile
    git add .xinitrc
    git submodule add user@myserver.com:~/repos/config.common
    git submodule add user@myserver.com:~/repos/config.private
    git commit -a -m "init" ssh user@myserver.com
    git init --bare ~/repos/config.machine-name
    git remote add origin myserver.com:~/repos/config.machine-name
    git push origin master

Now, you need to link everything back into your home so you can actually use your version controlled configs. I use a script to do this:

    #/bin/bash
    # A script for creating symlinks to all the dot files stored in the
    # config.git repo.

    CONFIG_PATHS=${CONFIG_PATHS:-"~/.config.git/common/ ~/.config.git/private ~/.config.git/"}
    EXCLUDE=${EXCLUDE:-"$1 bin/lndot.sh common/? private/? .config.git/? .git"}

    # Make sure to exclude ., .., and any other files listed in $EXCLUDE
    FIND_CMD="find $CONFIG_PATHS -maxdepth 1 -iname \"*\" "
    for file in $EXCLUDE
    do
      FIND_CMD=$FIND_CMD"| egrep -v \"$file\$\" "
    done
    echo $FIND_CMD
    # Ensure we're in home, to make proper symlinks
    pushd ~/ >/dev/null
    for i in $(eval $FIND_CMD)
    do
      ln -f -s $i ~/
    done
    popd >/dev/null

Now, you have your configuration files version controlled, linked properly, and easily shareable! To add a new machine to this setup, all you need to do is:

    git init .config.git
    cd .config.git
    git submodule add user@server.com:~/repos/config.common
    git submodule add user@server.com:~/repos/config.private
    ssh user@myserver.com git init --bare ~/repos/config.machine-name2
    mv ~/.xinitrc .
    mv ~/.profile .
    git add .xinitrc
    git add .profile
    ... etc

One important thing to note: whenever you change a file from a submodule, you should perform a commit/push of the parent module. Otherwise, when cloning the parent module, it will pull an older revision of the submodule.

The final piece of this setup, for me, was learning how to use bash configuration files so I could have both common and machine specific files coexist, and have settings that exist for all shells, or only interactive/login shells. I might make another post about this later.