How I manage my dotfiles using GNU Stow
Tags: posts, bash, dotfiles
Published: 2019-06-19 17:02:33

---
title: How I manage my dotfiles using GNU Stow
published: true
tags: Posts,bash,dotfiles
canonical_url: https://writingco.de/blog/how-i-manage-my-dotfiles-using-gnu-stow/
---
Originally published at [writingco.de](https://writingco.de). This is a cross-post from my content blog. I will be publishing new content every week or so, and you can sign up to my [newsletter](https://writingco.de/newsletter) if you'd like to receive my articles and other info tidbits directly to your inbox!

Managing your dotfiles is a fairly common thing for developers. But more often than not for many, things “just work”. If duct tape really was a developer tool, it would surely be found here. But it should not be hard to handle your dotfiles like a pro. In this article I will show you how. how to manage your dotfiles like a pro.

Developers don’t often have to deal with managing dotfiles on windows. So it might be only items such as ssh, orchestration tools like docker, and language specific things like environments in python, build targets for Rust, or setting up Go. However these tools can still be automated. Stow has other advanced usages and way to run it. On windows, you can use a tool called `dploy` mentioned later.

## GNU Stow

First thing is first. Install the stow binary on your platform of choice.

### OSX 

with `brew` installed, you can simply run:

```
brew install stow
```

Boom you’re done! pretty simple.

### Debian/Ubuntu (and derivatives)

Easy as well.

```
sudo apt install stow
```

### Arch

Just as simple

```
sudo pacman -S stow
```

## Creating your Dotfiles

Now let’s create a folder to get started. You can manage your dotfiles repo anywhere. I keep it along side my other code in `~/code/dotfiles`. So from here on, I will just refer to it as `$DOT`. If you want to follow along, just run this in your terminal:

```
export DOT=$HOME/code/dotfiles
```

Just replace the path to where your dotfiles are located. A common location I see others use is `$HOME/.dotfiles`. Now when you paste code in a bash compliant shell it will still work. While in this directory, lets first create some directories and files

```
mkdir -p __setup git termite/.confg bin/bin bash
touch setup.sh
```

this will create a few directories in your dotfiles.

- `__setup`: used for one time setups, such as package installation on different platforms.
- `git`: this will be a place we can save out global git configurations like `.gitconfig`
- `termite/.config`: this will be where we store a configuration for a terminal named termite. I am using termite here to help show how the folders are structured and why.
- `bin/bin`: is where you will save command line binaries made in bash so they are available system wide. this directory will be added to your `$PATH`
- `bash`: where we will save our bash configuration.

## Git

This is simple, we want to manage and keep track of our Git configuration in our dotfiles. So we will move our global `.gitconfig` file into this directory

```
mv ~/.gitconfig $DOT/git
```

Now we have `$HOME/code/dotfiles/git/.gitconfig`. We need to use `stow` to install it. Lets do that. From inside the $DOT directory, run this:

```
➜ stow -v -R -t ~ git
LINK: .gitattributes => code/dotfiles/git/.gitattributes
LINK: .gitconfig => code/dotfiles/git/.gitconfig
```

`-v` just means verbose so we can see what it is doing. `-R` tells stow to purge old links first making sure to clean up old references. `-t ~` is the target, or where this stow should be installed to. finally, we specify the directory `git` for what directory we are installing.

```
➜ ls -la | grep .git
lrwxrwxrwx 32 shawn 17 Jun 23:05 .gitattributes -> code/dotfiles/git/.gitattributes
lrwxrwxrwx 28 shawn 17 Jun 23:05 .gitconfig -> code/dotfiles/git/.gitconfig
```

As we can see above, it placed the files back into the home directory and symlinked them. Note that this does not work on windows but will work inside the `WSL` environment and does quite well.

## Automate the Dotfiles

So, we know we are going to need to do this more. And if you have to do it more than once you should automate it right? Of course. Automation is the key to managing repetitive tasks so we will use a bash script to install our dotfiles folders.

![https://imgs.xkcd.com/comics/automation.png](https://imgs.xkcd.com/comics/automation.png)<figcaption>courtesy xkcd <a href="https://xkcd.com/1319/">https://xkcd.com/1319/</a></figcaption>

Open up `setup.sh` and place in it the following contents:

```
#!/usr/bin/env bash

# make sure we have pulled in and updated any submodules
git submodule init
git submodule update

# what directories should be installable by all users including the root user
base=(
    bash
)

# folders that should, or only need to be installed for a local user
useronly=(
    git
)

# run the stow command for the passed in directory ($2) in location $1
stowit() {
    usr=$1
    app=$2
    # -v verbose
    # -R recursive
    # -t target
    stow -v -R -t ${usr} ${app}
}

echo ""
echo "Stowing apps for user: ${whoami}"

# install apps available to local users and root
for app in ${base[@]}; do
    stowit "${HOME}" $app 
done

# install only user space folders
for app in ${useronly[@]}; do
    if [[! "$(whoami)" = *"root"*]]; then
        stowit "${HOME}" $app 
    fi
done

echo ""
echo "##### ALL DONE"
```

In the code above, we will install the `git` directory for only the local user as root doesn’t need that. However `bash` which we will do next, can be used for both local users and root. We then create a bash function named `stowit` to run the actual stow command with our required arguments. If we were to call this when we installed `git`, it would be called as

```
stowit ~ git
```

which you can see in the loops just after that when I call `stowit`. It’s rather simple really. The first loop is to install folders for any user, and the second has a check to install for any user _unless_ it is the `root` user. So lets setup the bash directory.

```
mv ~/.bashrc $DOT/bash
mv ~/.bash_profile $DOT/bash
mv ~/.profile $DOT/bash
```

OSX may not have all of these, Windows for sure does not. If you don’t have them, then you can ignore them.

### Run the setup

The `bash` files are now being managed in your dotfiles repo. So now lets try running our setup file.

```
➜ chmod +x setup.sh
 ➜ ./setup.sh
Stowing apps for user:
LINK: .profile => code/dotfiles/bash/.profile
LINK: .bashrc => code/dotfiles/bash/.bashrc
LINK: .bash_profile => code/dotfiles/bash/.bash_profile
UNLINK: .gitattributes
UNLINK: .gitconfig
LINK: .gitattributes => code/dotfiles/git/.gitattributes (reverts previous action)
LINK: .gitconfig => code/dotfiles/git/.gitconfig (reverts previous action)

##### ALL DONE
```

You can see that `stow` is pretty smart about linking our files and folders. It linked our new bash files. But when we ran stow again it went through our previously linked `git` files, re re-linked them. You can actually configure how that handles those situations with different flags. `stow` will also abort stowing folders when it finds new files that have not been stowed before and will tell you what files so you can fix them. If we were to run

```
sudo ./setup.sh
```

then only the `bash` files would be setup in the roots home directory. This is nice because often times when we have to change to the root user we lose all the cool setups we have done for our user.

## The bin directory

We have two last files in our setup. `bin` will be easier so lets do that. inside the `$DOT/bin/bin` folder we can place any binary files we want to keep around across systems. Just make sure they are cross platform. This is why I like using bash, and Go binaries since you don’t need to worry about dependencies. Python is the same but it can depend on what version of `python` is available. As much as I love python for things, bash is best here unless there’s some magic tools in python to do what we need. Like processing CSV files or manipulating data.

In the `$DOT/bin/bin` folder, lets create a file named `$` … YES! Just a dollar sign. You may be asking why, but you will see. Update it’s contents to be:

```
#!/bin/zsh

# Ever pasted "$ somecommand" into the terminal and gotten this error?
# -bash: $: command not found

# Begone, silly errors! Lazy copy + paste forever!! ETCETERA!!!!

echo 'Quit pasting in commands from the internet, you lazy bum.'
"$@"
```

Then, in `$HOME/.bashrc` simply add this line:

```
export PATH="$HOME/bin/$:$PATH"
```

Run `setup.sh` again to link the files. Once linked we need to update our bash terminal environment. You can either restart your terminal, or run

```
source ~/.bashrc
```

to do the trick. You should be able to type:

```
➜ which $
/home/shawn/bin/$
```

If so, then it works. Now say you are copying and pasting a command from stack overflow. And you accidentally paste in the `$` from their terminal. Which usually denotes where their prompt starts. Normally, you would get some kind of `command not found` error for the `$` command. But now, if you take the command `$ echo "I work now"` and paste it into your terminal, you get:

```
$ echo "I work now"
Quit pasting in commands from the internet, you lazy bum.
I work now
```

Now when you copy a command from a code sample and also copy the `$` character, it will continue to run the command but will also give a message (if you wish) about making sure not to copy the `$`. Making it not break in many cases. Now, in the `$HOME/bin` directory, you can place any commands you want available system wide.

## Termite

Now lets do termite. I mostly have this here to help you understand how it copies directories over. So if you don’t use termite, just read along.

The program `termite` keeps it’s configuration file not in `$HOME`, but in `$HOME/.config/termite`. Or, more specifically, `$XDG_CONFIG_HOME/termite` as `$XDG_CONFIG_HOME` usually defaults to `$HOME/.config` anyways.

When we run `stow -R -t ~ termite` it takes the source directory, in this case `$DOT/termite` and maps it’s contents to the target directory, which is `~` aka `$HOME`. For me that is `/home/shawn`. On OSX it would be `/Users/shawn`. Inside the `termite` directory is a .config folder. Since `$HOME/.config` is already there, we must go one level deeper.

![https://cdn-images-1.medium.com/max/1600/1*cwR_ezx0jliDvVUV6yno5g.jpeg](https://cdn-images-1.medium.com/max/1600/1*cwR_ezx0jliDvVUV6yno5g.jpeg)

Now it will compare `$DOT/termite/.config/termite` with `$HOME/.config/termite` and see that that it doesn’t exist yet in `$HOME/.config/termite` and will essentially run

```
ln -s $HOME/code/dotfiles/termite/.config/termite $HOME/config/termite
```

And when we run `setup.sh` again, we can see this is true

```
➜ pwd
/home/shawn/.config

 ➜ ls -la | grep termite
lrwxrwxrwx 40 shawn 14 Jun 9:37 termite -> ../code/dotfiles/termite/.config/termite
```

## Conclusion

I have showed you how to manage your dotfiles like a pro. It is pretty simple. I haven’t yet talked much about the `_setup` directory we made. You can keep other setup files for your system that should only be run once. I use `_setup/osx.sh` for example to install `homebrew` and homebrew packages, and setup other system settings. I also have `_setup/arch.sh` for installing my packages using `pacman` or `trizen`.

From here, you can see how this simple setup can make it much easier to manage your dotfiles for linux and osx. A few extra steps needed to get them to work in windows. You could use something like [https://github.com/arecarn/dploy](https://github.com/arecarn/dploy) which is a python port of `stow` but needs to have python installed on your system (please use python 3.6+). It knows how to handle things like environment variable between bash (`$VAR`) vs windows (`%VAR%`) as well as using a compatible system link. You may not use `termite` on windows, but some are using tools like [Hyper Term](https://hyper.is/) which despite being built on electron, is a pretty good cross platform terminal.

You should easily now be able to take what you have for your configs like vim/neovim, tmux, or anything else and just place them into your dotfiles. If you want to use a submodule for something like tmux, just do

```
git submodule add [tmux-repo] tmux/
```

Then when running the `setup.sh` script it will make sure the code is local and link everything accordingly.

<section id="uagb-section-3afa700c-612a-4359-968a-5666675ee6e4"><div></div>
<div>
<div id="uagb-infobox-acd9b002-ac5a-4314-b2b0-0bb6b1ed0260"><div><div><div>
<div><h3>Looking for an Expert Developer?</h3></div>
<div>
<p>Just send me a message!</p>
<div><div><a href="https://writingco.de/contact/" target="_self" rel="noopener noreferrer"><span><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 496 512"><path d="M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 96c48.6 0 88 39.4 88 88s-39.4 88-88 88-88-39.4-88-88 39.4-88 88-88zm0 344c-58.7 0-111.3-26.6-146.5-68.2 18.8-35.4 55.6-59.8 98.5-59.8 2.4 0 4.8.4 7.1 1.1 13 4.2 26.6 6.9 40.9 6.9 14.3 0 28-2.7 40.9-6.9 2.3-.7 4.7-1.1 7.1-1.1 42.9 0 79.7 24.4 98.5 59.8C359.3 421.4 306.7 448 248 448z"></path></svg></span><span><span>CONTACT ME</span></span></a></div></div>
</div>
</div></div></div></div>
</div></section>

The post [How to manage your dotfiles like a pro with stow?](https://writingco.de/blog/how-i-manage-my-dotfiles-using-gnu-stow/) appeared first on my blog over at [Writing Co.de](https://writingco.de).