Cross-Platform Dotfiles

How I manage configs across different computers and operating systems.

From my previous post, I'm still thoroughly enjoying dotbot for managing my dotfiles and bootstrapping new computers. It is lightweight, has no external dependencies and requires no installation.

My dotfiles repo only supported macOS. In my day-to-day, I'm using Windows, Linux and macOS for personal and work purposes. I did a fresh install of Windows on my PC and decided now would be the time to support Windows and Linux as well.

uname

The uname command stands for Unix Name. It's a useful command that outputs details about the current machine like its operating system. I'll be using uname extensively to differentiate between my operating systems. It's the special sauce holding everything together.

Wikipedia has a compatibility list for uname. The main operating systems are supported like Android, Debian, Fedora, macOS, Ubuntu and Windows. This will be okay for me as:

  • the operating systems are known (Windows, macOS, Ubuntu) and not likely to change
  • the computers are known (personal, work, server) and not likely to change

dotbot

dotbot's config has two main functions: linking files and running scripts. It's possible to split each function for each operating system.

For symlinks, use the if parameter to link different files. In this example, I'm linking a different .gitlocal file based on the output of uname -s.

- link:
    ~/.gitconfig: git/gitconfig

- link:
    ~/.gitlocal:
      path: git/linux
      if: '[ "$(uname -s)" = "Linux" ]'

- link:
    ~/.gitlocal:
      path: git/mac
      if: '[ "$(uname -s)" = "Darwin" ]'

It's also possible to use the defaults key inbetween each link step to not repeat yourself:

# link all the Linux stuff
- defaults:
    link:
      if: '[ "$(uname -s)" = "Linux" ]'
- link:
    ~/.gitlocal:  git/linux
    ~/.config/sublime-text-3: subl/linux
    ~/.bashrc: bash/bashrc

# change defaults and link all the Mac stuff
- defaults:
    link:
      if: '[ "$(uname -s)" = "Darwin" ]'
- link:
    ~/.gitlocal:  git/mac
    ~/Library/Application Support/Sublime Text 3: subl/mac
    ~/.zshrc: zsh/zshrc

Scripts

For scripts, a | block scalar can be used on the command parameter to run a multiline script. Once again, I'm using uname to determine the operating system and if the script should execute.

- shell:
  - description: apt-get install
    command: |
      if [ "$(uname -s)" = "Linux" ]; then
        sudo apt-get update
        sudo apt-get install -y $(cat apt/packages.txt)
      fi
    stdout: true
    stdin: true

gitconfig

My gitconfig file differs between operating systems because of the GPG signing keys, credential store and diff tool. Newer versions of git support an include and includeIf section to pull in config directives from another source. For example, I'm linking to .gitlocal file which is different on each Linux and Mac:

[user]
  name = Calvin Bui
  email = 3604363+calvinbui@users.noreply.github.com

[include]
  path = .gitlocal

Linux specific gitconfig options:

[user]
  signingkey = MyLinuxSigningKey

Mac-specific gitconfig, which uses a different credential store:

[user]
  signingkey = MyMacSigningKey

[credential]
  helper = osxkeychain

ZSH

I use ZSH with oh-my-zsh. It has a custom directory which is loads any .zsh files when the terminal is initialised. This is the recommended method of adding paths or custom functions instead of appending them onto ~/.zshrc. Using the uname method as before, I can selectively include different paths and functions based on the operating system:

# ~/.oh-my-zsh/custom/paths.zsh

if [ "$(uname -s)" = "Darwin" ]; then
  do_mac_stuff() {
    echo "Hi From Mac"
  }

  # homebrew path
  export PATH="/usr/local/sbin:${PATH}"

elif [ "$(uname -s)" = "Linux" ]; then
  do_linux_stuff() {
    echo "Hi from Linux"
  }

  # Python on Linux
  export PATH="${HOME}/.local/bin:${PATH}"
fi

Chocolatey

Chocolatey is a package manager for Windows. I've used it many years ago and it has gotten a lot better since then for me to recommend it. Chocolately contained almost all of the applications I commonly installed. The only applications I had to install manually were LAV Filters Megamix and drivers for my printer.

After installing everything, I used choco-package-list-backup, a tool by bcurran3 to backup the list of currently installed packages including their installation parameters. The standard Chocolately backup does not backup installation parameters as they can be sensitive. To do this, I set the <SaveArguments> value to true inside of the XML config:

<?xml version="1.0"?>
<Settings>
  <Preferences>
    <SaveArguments>true</SaveArguments>
  </Preferences>
</Settings>

Future Plans

  • Continually sync changes (but safely to not include secrets) so the repo always remains up to date
  • Do more Windows-specific configuration
  • Add tests to validate everything works (CI for all OSes?)