Sharing .dotfiles cross-platform with a shell script
Tags: linux, dotfiles, git, bash
Published: 2018-05-29 23:56:37

---
title: Sharing .dotfiles cross-platform with a shell script
published: true
description: This article demonstrates the issue of sharing Linux home folder dot configuration files and provides a solution to it with the help of Git and a shell script.
tags: linux,dotfiles,git,bash
---

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:

1. 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)
1. 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.
1. 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:

```sh
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:

```sh
#!/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`:

```sh
# 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:

```sh
cp -rv --backup=numbered .dotfiles.dist/* ~

```

In case you're interested, you can see my dotfiles installer script [here](https://github.com/amcsi/dotfiles/blob/master/bin/install_dotfiles.sh).