Conquer your dotfiles with VCSH and MR
Are you tired of wasting time setting up your dotfiles every time you change laptop? Here I will describe a simple method based on vcsh and later show how you can make it more customizable and modular, thanks to myrepos.
To manage my dotfiles I use a bare git repository, instead of symlinks, and vcsh, a small utility that makes the whole process a lot easier. On a new machine, I just need to:
$ vcsh clone https://github.com/tyrion/dotfiles dotfiles
And I am done.
Recently I switched to a more advanced and modular setup, combining vcsh with myrepos. I keep separate branches for different applications and I can run custom scripts on checkout/update. On a new machine it looks like this:
- Get the myrepos configuration
$ vcsh clone keybase://private/tyrion/home mr
- List available application configurations
$ cd .config/mr $ ls available.d/ bash.vcsh i3.vcsh mr.vcsh nvim.vcsh pyenv.vcsh
- Activate the ones I want on this machine
$ ln -s ../available.d/nvim.vcsh config.d/
- Get everything in the right place
$ mr checkout
Many programs on GNU/Linux used to store their configuration inside our
directory using one or more dotfiles (i.e. files starting with a dot, like
Nowadays those files usually do not start with a dot and reside in
~/.config, but the name stuck.
Some people spend a great deal of effort in configuring and customizing their dotfiles (Have a look at reddit for some inspiration). Therefore, when switching to a new machine, or reinstalling our OS, it is very important to be able to restore all those configuration files easily.
Here I will list the most common approaches to the management of dotfiles. I am going to assume that the best way to deal with the versioning of dotfiles is to use git.
$HOME as a git repository
The first solution that might come to our mind is to make our entire
a git repository. This may seem a simple and good solution, although we have
to be careful with sub-repositories and most importantly it is not going to
be straightforward to clone our repository on a new machine (since the home
folder probably already exists).
Separate repository plus symlinks
Since making our home folder a git repository seems to have some drawbacks,
one reasonable way to fix the previous approach might be to put the git
repository with our dotfiles somewhere else (for example,
) and then symlink what we need inside our
This in practice, is a good approach. The only downside is that on a new machine we need to manually symlink everything in the right place and it might be a bit tedious.
For this reason, when using this method it is probably a good idea to use one of the many tools available. For example see: Using GNU Stow to manage your dotfiles.
Bare git repositories
If you do not like dealing with symlinks or having to use yet an other tool to manage your dotfiles, this method is for you.
A bare git repository allows us to keep the working tree and the
.git directory in different places.
$ DOT_DIR=~/.config/dotfiles $ git init --bare $DOT_DIR $ alias dot="`which git` --git-dir=$DOT_DIR --work-tree=$HOME"
Then we can use the
dot alias to manage our dotfiles:
$ dot add .bashrc $ dot commit -m "Add .bashrc" $ dot push
This is very similar to our first method, except that the
.git folder is not
placed directly inside
$HOME but resides in
~/.config/dotfiles. This means
that (A) we avoid all possible problems with nested repositories and (B)
we do not need symlinks!
Unfortunately, like in our first method, it is not straightforward to clone our repository when the home folder already exists. Moreover, whenever I need to setup a new machine, I always forget some of the git commands and flags required to use this method.
VCSH to the rescue
Using a bash alias like shown above, proves that we can manage our dotfiles in a satisfying way, only with git. However, I prefer using one of the many available tools that wrap git. In particular vcsh has the following advantages:
- Easier CLI. no need to remember complex git commands or define aliases on a new machine.
- Better UX. no problems to clone our dotfiles when the
$HOMEfolder already exists or is non-empty.
- Seeamless support for multiple repositories. no need to define one alias for each repository.
vcsh is licensed under GPLv2 or later and is available on most GNU/Linux distributions and OSX.
To get started with vcsh, you can create a new git repository for your dotfiles:
$ vcsh init dotfiles
Now you can run git commands, either using
vcsh <repo> <git cmd> or by
first activating your repository and then using git normally:
$ vcsh enter dotfiles (master) $ git add .bashrc .vimrc (master) $ git commit -m "initial files" (master) $ exit $
To get your dotfiles on a new machine, you can use
$ vcsh clone https://github.com/tyrion/dotfiles dotfiles
It is also worth noting that vcsh is capable of handling multiple repositories. This means you could separate the dotfiles of different applications. For example:
$ vcsh init bash $ vcsh bash add ~/.bashrc ~/.profile $ vcsh init vim $ vcsh vim add ~/.vimrc ~/.vim
For more information look at the README and try
Advanced setup with myrepos
If you are a true power user and you are still not completely satisfied by the above methods, here I will present a more advanced setup based on myrepos.
The method presented in this section is not necessarily the best for everyone. Mainly because it is more complex to setup than plain vcsh (even though you need to do this setup only once). Therefore, I encourage you to evaluate first what are you needs and then choose the most appropriate method.
The reasons that personally convinced me to adopt myrepos are the following:
- I can write small executable scripts that run on checkout/update.
- I can easily checkout some repositories in a different location (i.e. pyenv
NOTE: myrepos has other features, like the ability to manage almost any kind of version control system (e.g. svn, bzr), which I did not mention here, because they are not relevant for my use case. I invite you to check out the documentation.
To manage our repositories with myrepos, we need to edit
Here is an example:
[src/mr] # We can use almost any version control system checkout = git clone git://git.kitenet.net/mr [books/LFS] # Really, also SVN ... # It was not easy to find a working SVN repo checkout = svn co svn://svn.linuxfromscratch.org/LFS/trunk/BOOK/ LFS
Each section represents a repository and the header specifies its location on
our machine, relative to the
If we place the above
.mrconfig inside any directory and run
myrepos will fetch the repositories for us.
$ cd ~/test && ls .mrconfig $ mr checkout $ tree -L 2 . ├── books │ └── LFS └── src └── mr 4 directories, 0 files
If we have many repositories, we can use the
--jobs option, to make the
checkouts run in parallel.
Here is a more realistic example of
.mrconfig, showcasing the
- environment variables in the header,
- advanced checkout with vcsh,
[$HOME/.config/vcsh/repo.d/nvim.git] checkout = vcsh clone https://github.com/example/nvim nvim post_update = set -e curl -fLo ~/.local/share/nvim/site/autoload/plug.vim \ --create-dirs https://raw.githubusercontent.com/\ junegunn/vim-plug/master/plug.vim pip install -U pip pip install -U pynvim black mypy jedi [$HOME/.config/vcsh/repo.d/pyenv.git] order = 30 checkout = VCSH_BASE="$HOME/.local/share/pyenv" vcsh clone \ 'https://github.com/pyenv/pyenv.git' 'pyenv' [$HOME/.local/share/pyenv/plugins/pyenv-virtualenv] order = 30 checkout = git clone https://github.com/pyenv/pyenv-virtualenv.git $MR_REPO
Putting it all together
In this last section, I will show how to put together everything we have learned so far, by illustrating my current setup.
The goal is to create a single git repository, with different branches containing the dotfiles for different applications. For extra security, I recommend to use the encrypted git repositories provided by Keybase. They are especially useful if you plan to store sensitive informations, like passwords or ssh keys.
As a first step we should clone the mr-template repository, which I prepared to make things easier. In case you plan to use Github, I suggest you to fork it and use your fork instead.
$ git clone https://github.com/tyrion/mr-template.git $ cd mr-template $ git branch * master mr
As you can see the repository contains two branches. The master branch contains
only the README and can be used to store any file we do not want to be checked
out automatically in our
mr branch is the most important and contains the configuration for myrepos and the repository definitions for all the other apps.
All the other branches will be used to store the dotfiles
for our applications.
Let us examine the contents of the
$ git checkout mr $ tree -aI .git . ├── .config │ └── mr │ ├── available.d │ │ └── mr.vcsh │ └── config.d │ └── mr.vcsh -> ../available.d/mr.vcsh └── .mrconfig 4 directories, 3 files
The contents of this branch can be summarized as follows:
.mrconfig: the entrypoint configuration file for mr,
.config/mr/available.d/: repository definitions for our applications,
.config/mr/config.d/: repository definitions active in the current system. Contains symlinks to
.mrconfig is used only to include everything inside
content should be:
[DEFAULT] include = cat ~/.config/mr/config.d/*
Therefore, to add the dotfiles for a specific application to our system, we have to enable
its repository definition (placed inside
available.d/) by creating a symlink
This mechanism allows us to only activate the dotfiles for the applications that we need on the current machine, while keeping all the repository definitions inside the same git repository.
mr.vcsh is symlinked inside
config.d/ and thus is enabled
by default. Let us check its contents:
[$HOME/.config/vcsh/repo.d/mr.git] order = 0 # NOTE: put here the correct url for your repository checkout = vcsh clone -b mr https://github.com/tyrion/mr-template.git mr
Remember to edit the url to point to your dotfiles repository and commit it.
As an example, let us now add a simple
.bashrc to our repository.
The first thing to do is to add the repository definition. Place the following
[$HOME/.config/vcsh/repo.d/bash.git] order = 10 # NOTE: put the correct url for your repository checkout = vcsh clone -b bash https://github.com/tyrion/mr-template.git bash
This instructs vcsh to checkout the
bash branch of our repository. Let us
create this branch, since it does not exist, and add our dotfiles to it:
$ git checkout -b bash init Switched to a new branch 'bash' $ echo "echo 'Hello o/'" > .bashrc $ git add .bashrc $ git commit -m "Add dummy .bashrc"
You should also create a symbolic link inside
.config.d/ if you want your
bash branch to be enabled by default.
NOTE: It is very important to create your branches from the
to ensure they will start from a clean/empty state.
That’s it. Assuming you pushed your branches to your remote repository, you are now ready to check out your dotfiles on any machine:
$ # NOTE: always put your repo $ vcsh clone -b mr https://github.com/tyrion/mr-template.git mr
Enable/disable the branches that you want and run
$ cd .config/mr $ ln -s ../available.d/bash.vcsh config.d/ $ mr checkout
All your dotfiles should be in the right place. Congratulations 🎉