Originally published at 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 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.
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.
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 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 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.
Looking for an Expert Developer?
Just send me a message!
The post How to manage your dotfiles like a pro with stow? appeared first on my blog over at Writing Co.de.
Top comments (5)
Thanks a lot for this article. I love your setup script. It was the setup I was looking for.
I had one issue running the script on line 38. It was a syntax issue with "[[!". I replaced it with "[[ !" instead (just a space between brackets and !).
[[! --> [[ !
I've should read the comments more often as I spent a quite sometime to come to the same conclusion.
Great post, but one minor correction. The -R option to stow is not recursive, but restow. From the manpage:
-R
--restow
Restow packages (first unstow, then stow again). This is useful for pruning obsolete symlinks from the target tree after updating the software in a package.
Ooh thanks. Fixed.
thanks for this Shawn!