Managing file links

When it comes to dotfiles, there’s many ways to manage them. If you really care about them, I’d argue that you should use a version system, personally, I use git. But when you have you config files in a different folder from home, using symlinks is almost required.

There are other ways to use git and not needing to rely on symlinking, but I prefer using symlinks, call me old fashioned, but it just feels simpler to me.

OK, we want our dotfiles to be versioned and symlinked, how can we do that? I’ll describe three that I’ve used, for the purposes of this example, we have three files we want to symlink from our git repo:

dotfiles/
├── .bashrc
└── .vimrc

The bash way

You can link each file on a script manually like this:

#!/bin/bash

ln -s $REPO_DIR/.bashrc ~
ln -s $REPO_DIR/.vimrc ~

Then, you run the script and it should create symlinks for each of the files. You can easily guess that one of the issues of this way of symlinking it that, any time you want to add another dotfile to your repo, you need to add the line to this script, otherwise, it’s not going to be linked.

Another, arguably better way of doing this is using a script to go through all the files in the directory and link them all, like so:

#!/bin/bash

for file in \.*; do
    if [[ ${file} == ".." || ${file} == "." ]]; then
        continue;
    fi
    if [[ -f "${HOME}/${file}" ]]; then
        if [[ `diff -q ${file} ${HOME}/${file}` ]]; then
            mv "${file}" "${file}.`date +%Y`"
        fi
    fi
    ln -s "${file}" "${HOME}/${file}"
done

This script also renames the current files in the home directory to backup files suffixed with the current date. You will then have to go and ensure you have the right files in $HOME.

The ansible way

Ansible is a tool to provision machines with anything they need, making it perfect for this usecase (even though it is more commonly used for remote machines).

There’s a “module” (think, commands) that allows us to symlink files, I’m going to skip the usual manually symlinking each file and show you how to do so with a with_items (think, a loop) so that it’s easier to add filesnames to it.

---
- hosts: localhost
  vars:
     username: lurst
     home_dir: /home/{{ username }}
     source_dir: $REPO_DIR
  tasks:
   - name: Link dotfiles
     file: src={{ source_dir }}/{{ item.name }} dest={{ home_dir }}/{{ item.name }} state=link
     with_items:
        - { name: '.bashrc' }
        - { name: '.vimrc }

Now, if this is in a file called playbook.yml, you’ll need to run:

$ ansible-playbook playbook.yml

And ansible will symlink the files to your home directory, and will let you know if there is already a file with that name in the target directory. You’ll have to manually remove/rename each of them. Since ansible is idempotent, you can run it over and over again until you’ve symlinked all the files correctly.

The stow way

This is the final (as of the day I am writing this post) form of how I symlink my files. Stow is a “symlink farmer manager”, meaning that it’s perfect for our usecase of wanting to symlink a bunch of files.

Stow is very simple, you point a source directory and give it a target, and stow will symlink all the files from the source, into the target. That’s it. It will also tell you if any of the files is already there.

In order to “stow” our dotfiles in our home we cd into the directory above (..) dotfiles and execute:

$ stow dotfiles

I has now created symlinks from dotfiles to our $HOME, which is the default target, if you’d like to link the files to a different directory, use the flag --target:

$ stow --target /tmp dotfiles

Now every time that you add a file to the stow directory, you can re-run the command above to create the symlinks for the new files, if you want to remove the symlinks, use the flag -D.

$ stow -D --target /tmp dotfile

You can see how I use stow for my dotfiles in my setup repo, note the .stowrc file which configures the --target flag automatically.