January 22nd 2018


In the past, I already attempted to manage some of my configuration files, and failed twice:

  • I made it a public github repository, and added too much - creating an unmanageable mess of sometimes constantly changing files, with the few really useful ones strewn in. I also forgot that some files revealed information about SSH ports and the like. Ouch. Quickly deleted all traces of it, hopefully.

  • Tried a homebrew synching mechanism with symlinks, some of them to folders that contained not only the interesting config files but also caches and the like. I quickly lost sight of what is where and the process of adding files (moving the file to the backup location, creating a symlink for it) and removing files (tracing symlinks, copying back the original files...) was too complex.

So what do I actually need/want?

Structural Requirements

  • I want to share dotfiles across multiple installs of varying types/distros
  • Centralized? Probably yes. Server/client model, but:
  • It is possible to both push & pull (to use git vocabulary), meaning: changes on all machines can be incorporated back into the central repository, and local repositories can be updated.
  • I do not want a public repository, but I want to access it remotely.
  • Minimal (preferably none) impact on the local machines' existing file/folder structures, meaning: no symlinks please.

This still leaves me with the most important question:

What are dotfiles?

Configuration files that took a while to set up, don't change regularly, don't depend on software or hardware versions, and are less pain to copy over and edit, than to recreate them from scratch.
Also scripts (my complete ~/bin) and other pieces of valued code.
More often than not, these files aid personal habits, like keyboard shortcuts or custom menus.

There can't be a finite definition, and that's why it's so important that

  • the impact on the system's file/folder structure is minimal (no symlinks!)
  • it is easy to add/remove files

Implementation: Store Dotfiles in a Bare Git Repository

Most of my requirements and musings seem to be supported by the scheme described in this article - at least theoretically. Let's see how I can put it into practice.

git init --bare $HOME/.dotfiles
alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles config --local status.showUntrackedFiles no

and don't forget to:

echo "alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'" >> $HOME/.bashrc

Now start adding some files:

dotfiles status
dotfiles add .vimrc
dotfiles add .bashrc
# etc...
dotfiles commit -m "Initial commit"

Please take your time to read the original article; I did not fully document the process here.

The Additional Step: Creating a Central Remote Repository

But I cannot push these changes anywhere yet.

Unlike the writer of the article, I don't want to put an ssh daemon on my main machine, instead I want to have the dotfile repository on my server, which is a different machine than those that are going to benefit from the shared dotfiles. How do I get it there? I have some experience with both github and local git repositories, but this is new. Do I need to set up a git server? It seems no, I can do it all over SSH, which is already working on all ends. This actually aids my wish for remote but private access.

So I already have everything I need to remotely access a git repository.

Nevertheless, the official git guide recommends setting up a separate git user. Done. Setting up (the git user's) SSH keys is a piece of cake by now. For those who don't think so, this is the ultimate set of instructions. Now initialize a bare repository:

git@localhost:~$ mkdir dotfiles && cd dotfiles
git@localhost:~/dotfiles$ git init
Initialized empty Git repository in /home/git/dotfiles/.git/

But how to get the original repository on my home machine onto the server, so it becomes push- and pullable? After some trial and error, I ended up using the exact method described in this answer:

  • locally clone --bare the repo (i used the previous created alias for that, so the command was dotfiles clone --bare ~/.dotfiles /temp/path/to/dotfiles)
  • scp the local clone to the server, right where you want to pull pull from

From there onwards, I clone the repo to another machine much like described in the next step of the article.

It Works

Back on the originating machine I can finally start pushing out changes:

dotfiles push origin master

Then to the remote machine:

dotfiles pull

But I can also push changes from some other machine back into the repository, and then incorporate them into all my other machines.
In other words: say I have an improvement to one of my scripts while working on my laptop. I push the improvement out via my dotfiles repo. Later I can pull it into my desktop computer.

Various caveats

It's more like a big fat warning sign:
If you're not professionally familiar with git, you will experience pitfalls, due to the special nature of this git repository.

I had to

  • remove files that will always be different on different machines
  • take care to remove them only from the git repo, and not physically from the filesystem
  • rewrite some scripts to cater differently to different machines (making use of $(hostname))
  • make sure I always immediately push out changes, and pull them back in on another machine where before I make any changes there (I will have to learn about branches I guess)

After some trial and error (and one major f*ck-up where I almost had to start from scratch) I think I have it working to my satisfaction.

Here's just one problem I managed to solve:
How to delete a file from a Git repository, but not other users' working copies

So, I always need to be aware of which files are part of my dotfiles repo.
This implies less is more.

I am also considering to replace the previously described aliases with a bash function that only allows for a limited subset of git commands, as a security precaution.

What About System Files

What about files that do not reside under my $HOME?
If I try to add them to the repo I get:

dotfiles add /etc/default/grub
fatal: /etc/default/grub: '/etc/default/grub' is outside repository

Makes sense.

Simple solution: A weekly backup script I'm already using can be expanded a little to zip up the whole /etc folder - excluding my adblocking hosts file it's just a few MBs:

date="$(date +%Y%m%d%H%M)"
backup="/home/username/.local/share/etc.7z" # 7z will fail if this doesn't end in 7z because of mhe=on
mv "$backup" "$backup.$date"
7z a -mhe=on -x'!etc/hosts' -p"$(cat /root/etc.zip.pwd)" "$backup" /etc && rm "$backup.$date"

Since I am backing up all of this anyway with borg, this can be a little sloppy.
BTW, /root/etc.zip.pwd is only readable by root.

Let's try again:

dotfiles add etc.7z
# commit, push etc.

No errors. Done!


Sep 9th, 2017