Using stow to manage dotfiles on multiple systems
As a long time Linux user (almost 25 years now), there are a few tasks which have always been in the back of my mind but which I’ve always neglected. One of them is proper backups. I will come back later to that hopefully (I know). The other is a proper way to manage my dotfiles. I copy my dotfiles between my systems, try to move them to a new system and usually create a new version, trying to invent the wheel all over again every time. I’ve checked GNU stow before, but it did not really click in my mind, or maybe I wasn’t paying attention.
From the examples I saw, I thought the correct way to set up a stow directory was to put configs for different applications in different folders, and than use stow to link to it from your home folder. Like a folder for bash-config, another for bspwm, another for another program, etc. However this did not really made sense too much in my mind because I don’t use the same configuration even if it is the same app on different machines.
After reading for a while, I understand that this is not necessarily the only way to use stow. What I want to do is to have all config files from different “systems”, not different applications, in the same git repository and use stow to manage them.
I guess my use case is a bit different than usual. However I believe I have come to a sensible setup at the end. Here is how to understand the logic behind stow and how I set it up.
Understanding the structure stow expects
Stow expects a directory with subdirectories for each of the configuration groups. I will call this main stow directory “stow repository”. In the repo, each subfolder has config files or folders or whatever else you want to keep, and when you run stow subfolder command, it creates links to the files/folders in the subfolder, and places the links to the parent folder of the stow repo.
- You want to manage the dotfiles in your home directory, so the stow repository should be in your home directory.
- You need subfolders as configuration groups in the stow repo, which has files and folders that you want to manage in them.
1~$ pwd
2/home/user
3~$ ls -l
4drwxr-xr-x 3 user user 4096 Aug 29 10:50 stow_repo
5~$ cd stow_repo/
6~/stow_repo$ ls -l
7drwxr-xr-x 2 user user 4096 Aug 29 10:50 config_dir
8~/stow_repo$ cd config_dir/
9~/stow_repo/config_dir$ ls -l
10-rw-r--r-- 1 user user 0 Aug 29 10:50 file_1
11-rw-r--r-- 1 user user 0 Aug 29 10:50 file_2
12~/stow_repo/config_dir$ cd ~/stow_repo/
13~/stow_repo$ stow config_dir/
14~/stow_repo$ cd
15~$ ls -l
16lrwxrwxrwx 1 user user 27 Aug 29 10:51 file_1 -> stow_repo/config_dir/file_1
17lrwxrwxrwx 1 user user 27 Aug 29 10:51 file_2 -> stow_repo/config_dir/file_2
18drwxr-xr-x 3 user user 4096 Aug 29 10:50 stow_repo
19~$
Of course, these assumptions work for our use case but stow allows you to manage file linking in much more flexible ways. It is better explained in the links below [2]. However we don’t need to worry about these at the moment just to keep things simple.
My idea for structure
In my case, I want ~/.dotfiles to be my main stow repository with subfolders for each of my machines/systems:
1~$ cd .dotfiles/
2~/.dotfiles$ ls -l
3drwxr-xr-x 2 user user 4096 Aug 29 10:55 ceres-debian
4drwxr-xr-x 2 user user 4096 Aug 29 10:55 common-configs
5drwxr-xr-x 2 user user 4096 Aug 29 10:55 jupiter-arch
6drwxr-xr-x 2 user user 4096 Aug 29 10:56 macbook-air
7drwxr-xr-x 2 user user 4096 Aug 29 10:55 netbsd
8drwxr-xr-x 2 user user 4096 Aug 29 10:56 pluto-ubuntu
9drwxr-xr-x 2 user user 4096 Aug 29 10:56 scripts
10drwxr-xr-x 2 user user 4096 Aug 29 10:56 wallpapers
11~/.dotfiles$
Each system gets their own dotfiles within them:
1~/.dotfiles$ cd netbsd/
2~/.dotfiles/netbsd$ ls -la
3drwxr-xr-x 2 user user 4096 Aug 29 10:58 .
4drwxr-xr-x 9 user user 4096 Aug 29 10:56 ..
5-rw-r--r-- 1 user user 0 Aug 29 10:58 .Xresources
6-rw-r--r-- 1 user user 0 Aug 29 10:58 .mkshrc
7-rw-r--r-- 1 user user 0 Aug 29 10:58 .vimrc
8-rw-r--r-- 1 user user 0 Aug 29 10:58 .xsession
9~/.dotfiles/netbsd$
And it does not have to be only files, it can be whole folders:
1~/.dotfiles$ cd jupiter-arch/
2~/.dotfiles/jupiter-arch$ ls -la
3-rw-r--r-- 1 user user 0 Aug 29 11:00 .bashrc
4drwxr-xr-x 5 user user 4096 Aug 29 11:00 .config
5~/.dotfiles/jupiter-arch$ cd .config/
6~/.dotfiles/jupiter-arch/.config$ ls -l
7drwxr-xr-x 2 user user 4096 Aug 29 11:00 elinks
8drwxr-xr-x 2 user user 4096 Aug 29 11:00 nvim
9drwxr-xr-x 2 user user 4096 Aug 29 11:00 tmux
10~/.dotfiles/jupiter-arch/.config$
So, on a fresh NetBSD installation all I need to do is to install git and stow, clone the repository and stow what I need:
1~$ su -
2root@host:~$ pkgin in git stow
3root@host:~$ exit
4
5~$ git clone https://gitlab.com/fcn/dotfiles.git ~/.dotfiles
6~$ cd .dotfiles/
7~/.dotfiles$ stow netbsd
Whatever files that are in netbsd folder is now in my home directory.
Using a common-configs folder to manage common files
For configs that I want to share between different systems, I can place them in common-configs folder, and I can link them in the specific system I want to use this shared config.
1~/.dotfiles$ ls
2ceres-debian common-configs jupiter-arch macbook-air netbsd pluto-ubuntu scripts wallpapers
3~/.dotfiles$ cd ceres-debian/
4~/.dotfiles/ceres-debian$ ln -s ../common-configs/common-file .
5~/.dotfiles/ceres-debian$ ls -l
6lrwxrwxrwx 1 user user 29 Aug 29 12:14 common-file -> ../common-configs/common-file
7~/.dotfiles/ceres-debian$ cd ..
8~/.dotfiles$ stow ceres-debian/
9~/.dotfiles$ cd ..
10~$ ls -l
11lrwxrwxrwx 1 user user 34 Aug 29 12:14 common-file -> .dotfiles/ceres-debian/common-file
12~$
My actual setup from zero
I will start with a private repo in Gitlab to manage all files and to track changes in the future. I will go to my Gitlab projects page, New project → Create blank project and create a new project called dotfiles. This will give us a git repo with a single file so we can directly clone to our computer.
1~$ git clone git@gitlab.com:fcn/dotfiles.git .dotfiles
2Cloning into '.dotfiles'...
3remote: Enumerating objects: 3, done.
4remote: Counting objects: 100% (3/3), done.
5remote: Compressing objects: 100% (2/2), done.
6remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
7Receiving objects: 100% (3/3), done.
8~$ cd .dotfiles
9~/.dotfiles$ ls
10README.md
11~/.dotfiles$
We have now our stow repository where we will place our config files which we will be able to track using git. We can just delete the README.md file or put some information and a link to this page in it for future reference.
I will then create folders for each system. I will start with the dotfiles on the Macbook and install stow using brew on MacOS.
1~/.dotfiles$ mkdir macbook-air
2~/.dotfiles$ cp ~/.zshrc macbook-air/
3~/.dotfiles$ mkdir macbook-air/.config
4~/.dotfiles$ cp -R ~/.config/{iterm2,starship.toml,zsh} macbook-air/.config/
5~/.dotfiles$ git add .
6~/.dotfiles$ git commit -m "added Macbook Air .zsh iterm2 and starship configs"
7~/.dotfiles$ git push
A problem we can face at this stage, is that sometimes there are other git repositories in our config folders. If we add a git-tracked folder in another repository, git will give us an error message and tell us that the main repo will not include these folders’ contents when cloned. In my case it is the zsh-autosuggestions repository in ~/.config/zsh/ folder, and I have decided not to include this ~/.config/zsh/ directory in my stow repo and add a note in my README.md to install starship separately (I believe this is how it was installed in the first place).
Other things in my ~/.dotfiles directory
I keep my scripts and other resources I want to make available on my multiple systems such as a small selection of wallpapers or my customs scripts. I keep them in the same repository but not stow them at all. They are available directly in ~/.dotfiles and ~/.dotfiles/scripts is already added in my $PATH on systems that I want to use these scripts.