Throughout my career I have been in many Linux environments. I had the one at my first workplace where I would have my own .vimfiles
settings set. Then at home when I installed Ubuntu for the first time, I would have a .bashrc
file for storing some aliases. Then on my Windows on my Linux-like cygwin environment I had to copy that same .bashrc
over, and maybe I created a .screenrc
to configure GNU screen. Then on my personal DigitalOcean server I had another Linux environment, and I had to copy my configuration again. Then I installed Linux on a new computer and needed to find and copy my aliases again... and so on and so on.
I have probably had to manually copy parts of local Linux configuration over and over again twenty times or so before I realized that this just has to be automated in some way so that I wouldn't have to suffer so much getting all my configuration ready any time I have to deal with a new Linux environment. In this article, I'm going to show you what I did to solve this problem of mine.
Why do I opt for shell scripts in the title? That's so to minimize the amount of dependencies needed to be installed in order to get my .dotfiles
up and running on any server.
Versioning dotfiles with Git
First of all, what best way to save the state of several files (.bashrc
, .gitignore_global
, bin/
) for keeping them in sync than by using version control?
There is a thing one might think as issues when considering to version files in your home directory...
The dotfiles you would like to version is a very small subset of files/directories in your home directory
That's true. If you started versioning your home directory, all the other unrelated files/directories would show up as unversioned, muddying up your git status
output all the time.
To solve this, I just created a .gitignore
file in my home directory with this in it: *
. Basically to ignore everything.
It's not a problem at all. Whenever I want to add new file, I can just do git add -f <file>
to bypass the ignores.
Cloning .dotfiles in new environments
Now that we have our dotfiles versioned in Git, we'll need to be able to clone it into new environments to make use of them.
On any new Linux environment ideally we would like to clone our dotfiles into our home directory. Unfortunately in every Linux environment your home directory is not empty, and Git doesn't give you a way to clone into an existing directory.
So we'll need to clone into a new directory. We could call it dotfiles
, and it could be within our home directory.
Now after that we would need to copy the files over from within the dotfiles
directory to the home directory. There are some issues with that again though that would be good to address:
- It's usually non-trivial for non-Linux-gurus recursively copy files from within a directory including files starting with a dot (most dotfiles we would like to version)
- Our new Linux environment might come with some
.bashrc
file already there that we might not want to just blindly override it with ours. Maybe this was actually an older Linux environment of ours which has an old.bashrc
of ours with some interesting stuff in it. - Besides, worrying about all this also just wastes time. It would be good to get all our dotfiles ready ASAP on any Linux environment such that it would never feel like a chore.
Why not automate all this then?
Using an installer as a bash script
I'm not a big fan of bash scripts, but their portability is pretty good, so I opted for making one of those.
Where would the bash script go? Well it would be versioned within our dotfiles of course. I myself put it in bin/install_dotfiles.sh
.
The answer to the problem of how to include files starting with a dot when copying files from *
is by setting:
shopt -s dotglob
You now know what you need... but you might forget it. So just leave this in your bash script so you wouldn't have to remember!
The other thing is what to do to avoid overriding your existing files.
If you didn't know, there's an argument you can pass to cp
which keeps numbered backups for any files that would be overridden by a copy. It's --backup=numbered
.
So the initial install script would copy all the files over with backups including the .git
folder and .gitignore
file to make sure your home folder is versioned but ignores all files.
The bash script would basically contain:
#!/bin/bash
shopt -s dotglob
cd ~/dotfiles
cp -rv --backup=numbered * ~
And that's it, we have a portable way to sync dotfiles across Linux environments with a simple initial installer bash script you would have to run only once per new environment.
Addition for Macs
Unfortunately one more dependency is needed for this to work on a Mac.
To make cp
on macOS work the same way as in Linux (for --backup=numbered
), you'll need to make sure to install coreutils (e.g. with brew install coreutils
), then make sure the GNU cp
command is used by using this code after setopt
:
# Wrapper for cp to work in macOS.
cp() {
if [ -x "$(command -v gcp)" ]; then
command=gcp
else
command=cp
fi
command $command "$@"
}
Last extension: dist files
You might have some local configuration files you wouldn't want to version, but would like to have a default "dist" version to be installed initially on new Linux environments. For that, you could have a folder called, say, .dotfiles.dist
with all such files. Then at the end of your script file after copying the files, you could then do:
cp -rv --backup=numbered .dotfiles.dist/* ~
In case you're interested, you can see my dotfiles installer script here.
Top comments (5)
Don't copy, symlink! That way you don't have to reinstall every time you tweak something. I use dotbot to keep my dotfiles up to date.
Apologies for the confusion. I opt for versioning your home folder directly with Git. As such, no copying necessary.
The bash script in the article is only for the initial install per environment that would copy all the files including the
.git
folder over to the home directory.I have amended the article to be more clear about this.
Was going to reply the same. I wrote my own bash script to symlink in files to the home directory. You tweak the files and can commit them without having to copy everything over again.
Should look into dotbot, I don’t think it existed when I first started to version control my dot files.
Yup, dotbot is what I use as well. I have shell script that installs a ton of packages I typically use and also kicks off a run of dotbot. It works pretty well on both Mac and Linux and doesn't result in having to do a bunch of work around having my home directory being a repo.
I'd say use stow