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.
TL;DR
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
Keep reading if you want to know the details, or skip to the sauce or directly check the final result on Github!
Contents
The problem
Many programs on GNU/Linux used to store their configuration inside our $HOME
directory using one or more dotfiles (i.e. files starting with a dot, like .vimrc
).
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.
Possible approaches
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 $HOME
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, ~/.config/dotfiles
) and then symlink what we need inside our $HOME
.
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
$HOME
folder 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
:
$ 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 man vcsh
.
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.
myrepos is a tool to manage multiple version control repositories, which works very well with vcsh, licensed under GPLv2 or later and available on most GNU/Linux distributions and OSX.
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
goes inside
~/.local/share
and not~
).
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.
Quickstart
To manage our repositories with myrepos, we need to edit .mrconfig
files.
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 .mrconfig
file.
If we place the above .mrconfig
inside any directory and run mr checkout
,
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.
Realistic example
Here is a more realistic example of .mrconfig
, showcasing the
support for:
- environment variables in the header,
- advanced checkout with vcsh,
- and
post_update
hooks.
[$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
To learn more about myrepos you can consult its website and
read the manual, either online or with man mr
. In the git repo you
can also find an example .mrconfig
.
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 $HOME
.
The 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 mr
branch:
$ 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 toavailable.d/
.
The .mrconfig
is used only to include everything inside config.d/
. Its
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
inside config.d/
.
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.
Notice that 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
inside .config/mr/available.d/bash.vcsh
:
[$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 init
tag,
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 mr checkout
:
$ cd .config/mr
$ ln -s ../available.d/bash.vcsh config.d/
$ mr checkout
All your dotfiles should be in the right place. Congratulations 🎉