What follows is a brief description of the techniques I currently use to
organize my personal configuration files, often called "dot files." In general,
most of your configurations are stored in a folder in your home directory
called .config
. This directory can be changed, but in my opinion it's a
sensible default.
In theory, every program you configure in Linux/UNIX will store its config files
in .config
, making it very easy to migrate your configs to new computers or
track that folder with a version control system like git
. Tracking your
configurations with git
allows you to easily clone the repository onto all
your computers and keep your configs synchronized across them. You can browse
your git history to look at any old versions of your configs and use branches to
maintain different versions of some configs. For example, my desktop doesn't
need a battery indicator, but my laptop does.
Unfortunately, some popular programs don't support this standard either because
of incompetence, ignorance, or the program is just older than the XDG
standard and can't be updated due to legacy reasons. This
includes vim
(but not neovim), git
itself, and most
shells. Additionally, you might track more than just configurations such as an
icon pack or a
few fonts which would instead be (correctly) stored under
.local/share
.
Symlinks
The solution is to use symbolic links (or symlinks for short). They are one of
the two types of links on UNIX and function similarly to "shortcuts" on Windows.
A symlink is a regular text file that contains the name of another file or
directory and has a file system flag of l
. This is what indicates to
programs that it is a symlink rather than a regular file. The standard way to
create one is with ln -s
.
$ touch /tmp/a
$ ln -s /tmp/a /tmp/b
$ echo "hello world" > /tmp/a
$ cat /tmp/b
hello world
In this example /tmp/b
is a symlink that points to /tmp/a
.
Whenever a program attempts to open a symlink it will instead seamlessly open
the file that the symlink is pointing towards. Using this, we can store our
configuration files anywhere we like and create symlinks in the "expected"
places that point to our real files. Personally, I organize all my configs into
a folder called dots
with each program having its own subdirectory -- like below.
$ tree -a ~/dots
dots
├── beets
│ └── .config
│ └── beets
│ └── config.yaml
├── dunst
│ └── .config
│ └── dunst
│ └── dunstrc
├── git
│ └── .gitconfig
├── i3
│ ├── .config
│ │ └── i3
│ │ └── config
│ └── .xinitrc
├── user-dirs
│ └── .config
│ ├── user-dirs.dirs
│ └── user-dirs.locale
└── zsh
├── .local
│ └── share
│ └── zsh
│ └── plugins
│ └── kota-prompt
│ ├── .git
│ ├── kota-prompt.plugin.zsh
│ ├── kota-prompt.zsh
│ ├── LICENSE
│ └── README.md
└── .zshrc
Most have their own .config
folder. Everything is arranged so that the
contents of these folders can be symlinked to my home directory. The file at
/home/kota/.config/i3/config
is actually a symlink that points to
/home/kota/dots/i3/.config/i3/config
. It's a little bit confusing, but this
allows me to easily track of all of my dot files with git
. A little bit of
tooling makes it quite easy to manage.
stow
stow [ options ] package ...
stow is a fairly popular and old unix tool that is used to "manage farms of symbolic links," which is exactly what we're dealing with. It was originally created to help with manual software installs (and excels at that), but it also happens to be an amazing tool for managing your dot files.
stow deals with "packages," which are directories arranged like the ones in
my dots
folder from above (zsh, user-dirs, i3 are all stow
"packages"). It
has options for "installing," "removing," or "updating" these packages.
Installing a package simply means creating a bunch of symlinks in a certain
location that point to the corresponding files in the package. I think the
easiest way to explain this is with an example.
This is how I install my i3 configs on a new computer.
cd ~/dots
stow -t /home/kota i3
The -t
option tells stow
which folder all the symlinks are relative towards,
or rather, where to actually install the package. Basically, if running cp -r i3/* /home/kota/
would copy all the files to the correct location, then
/home/kota
is the correct directory for the -t
option.
stow
defaults to creating symlinks, but if one or more of the directories exists
it works around them. For example, if .config/i3
doesn't exist stow
will make a
symlink for that folder that points to the one in your i3 package. However, if
that folder does exist, stow
will create a symlink for .config/i3/config
instead.
Git
I'm going to assume you know the basics of git
(add, commit, push, pull, clone),
but if you haven't used it much or need a refresher take a look at the first few
chapters of the git book.
There are 2 distinct "issues" you might run into with your new git
tracked,
stow
managed, dots folder. I addressed the first earlier. What happens
if you want to have slightly differing configs between your computers? You
might want to use a different colorscheme on one computer, or not show a
battery indicator on your desktop. Whatever the case, there are 3 ways of
handling it.
The most obvious is using git
branches. This approach is pretty clean and easy
to understand, but there is one annoying downside: if you update a config on one
branch, most of the time you'll want to update it on all your branches. So, this
approach involves a lot of remembering to merge and keeping all your branches up
to date. If you have like 6+ computers this quickly becomes a huge pain in the
ass, especially if they use almost the exact same configs for everything.
The second approach, which is even worse than the first (but requires slightly
less git
knowledge), is to have multiple repos, one for each computer. I did
this for a while, but it has all the downsides of the first approach with more
manual merges and none of the upsides.
The last approach, which I use currently, is having one repo and one branch, but
different packages for the differing software. For example, if you have a sway
config for your laptop and a different one for your desktop, you would have two
folders sway-laptop
and sway-desktop
. You still need to remember to copy
over changes that you want to make to both configs, but seeing them both
physically in the directory makes it easier to remember. Plus, for all the
software that doesn't have differing configs, you only store one copy.
git submodule
The second distinct problem that comes up is how to handle "plugins" inside your
configs. Specifically, I'm thinking of vim
and zsh
plugins, but most other
software (except browsers) works similarly.
For zsh I made a little prompt plugin. To
use it, you simply clone that repo to ~/.local/share/zsh/plugins/
or something
similar and add a line like this to your zsh config source /home/kota/.local/share/zsh/plugins/kota-prompt/kota-prompt.zsh
However, now that you've added that line to your zsh config, you need to
re-install that plugin whenever you setup a new computer. You could "solve" this
by adding .local/share/zsh
to your stow
package and tracking the whole thing
with git
. That works well enough for small packages, but for bigger ones it
really bloats your repo and now you need to manually update your plugins.
Let's be honest you're not gonna remember to do that.
git
actually comes to the rescue here with a lovely command called git submodule
. This command allows you to specify other repositories as
dependencies for your repository. For example, git submodule add https://github.com/tpope/vim-surround neovim/.config/nvim/pack/plugins/start/vim-surround
is the command I ran from
inside my ~/dots
repo to install Tim Popes lovely vim-surround
plugin. It will clone that repository
into the location you specified, and now you can use the other git submodule
commands to update or remove your plugins. You also need to make a commit that
adds the submodule -- similar to when you update a .gitignore
. The git book
covers that command in detail in chapter
7.11.
One thing to note is that now, when you clone your dots onto a new computer, you
need to use the --recursive
argument with git clone
. For me, the command is
git clone --recursive https://git.sr.ht/~kota/dots
. When you want to update
your plugins you can run git submodule update --remote --merge
.