Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    Dotfiles: An Environment That Follows Me

    Four years and 168 commits of dotfiles: GNU Stow mirroring $HOME, a Linux-to-macOS migration, a graveyard of Neovim configs, scripts that encode my workflow, and keyboard firmware as config too.

    Growingtended planted 4 min read
    On this page

    The first commit in my dotfiles repo is from August 2021 and is called “Current Dotfiles”. The most recent is from October 2025. In between are 168 commits that tell the story of how I like to work: which window manager I was obsessed with, which Neovim distribution I was trying that month, which keyboard I’d just flashed.

    I keep dotfiles for one practical reason: a new machine should feel like mine within an hour, not within a month. New laptop, new job, a fresh WSL install on a Windows box — I want to clone one repo and have my shell, editor, terminal multiplexer and scripts exactly where my fingers expect them.

    The model: a folder that mirrors $HOME

    I use GNU Stow. Each top-level folder in the repo is a “package”, and inside it the files sit at the path they should have relative to your home directory:

    .dotfiles/
    ├── zsh/.zshrc                 → ~/.zshrc
    ├── tmux/.tmux.conf            → ~/.tmux.conf
    ├── nvim/.config/nvim/…        → ~/.config/nvim/…
    ├── bin/bin/…                  → ~/bin/…
    └── wezterm/.wezterm.lua       → ~/.wezterm.lua

    stow zsh creates symlinks from $HOME into the repo. Edit ~/.zshrc and you’re editing the file in git. There’s no copy step to forget and no sync script to drift.

    The install script is tiny. It was inspired by ThePrimeagen’s approach: unstow and restow each package listed in an environment variable.

    #!/usr/bin/env zsh
    pushd $DOTFILES
    for folder in $(echo $STOW_FOLDERS | sed "s/,/ /g")
    do
        echo "stow $folder"
        stow -D $folder
        stow $folder
    done
    popd

    A per-OS wrapper picks the defaults, so a minimal Linux box gets just the essentials (lightly simplified):

    #!/usr/bin/env zsh
    if [ -z "$STOW_FOLDERS" ]; then
        STOW_FOLDERS="bin,nvim,tmux,zsh"
    fi
    if [ -z "$DOTFILES" ]; then
        DOTFILES=$HOME/.dotfiles
    fi
    STOW_FOLDERS=$STOW_FOLDERS DOTFILES=$DOTFILES $DOTFILES/install

    Choosing packages per machine is the quiet superpower. A headless server gets four folders; my desktop gets twenty.

    From Linux tiling to macOS tiling

    The repo is a fossil record. The Linux era (2021–23) is all there: i3 and xmonad as window managers, polybar for the status bar, picom for compositing, rofi as the launcher, dunst for notifications, eww and conky widgets. Several have a _laptop twin (polybar_laptop, picom_laptop, dunst_laptop), because a 14-inch screen and a big monitor want different settings, and a separate Stow package was the simplest way to switch.

    Then the macOS era: sketchybar replaced polybar in 2023, WezTerm arrived in 2024, and AeroSpace (a tiling window manager for macOS) landed in October 2025. Different tools, same idea. Every machine I use tiles windows and is driven from the keyboard, which is the setup I describe in Typing Speed and a Keyboard-First Setup.

    The Neovim graveyard

    Here’s the honest bit. My repo contains nvim, nvim_old, nvim_laptop, lvim, kickstart_nvim, NvChad and lazyvim: seven Neovim configurations, added between 2021 and 2024.

    I could tidy that up, and I probably should delete a few. But I’ve come to like it as a record of experimentation. LunarVim taught me what a batteries-included distribution feels like. Kickstart taught me to understand every line of my config. LazyVim showed how much a well-designed plugin manager changes things. Each experiment left something behind in the config I actually use. Because they’re separate Stow packages, trying a new one never put my working setup at risk.

    Scripts that encode a workflow

    The bin package is where habits become tools. A few I use constantly:

    • git-clone-bare-for-worktrees clones a repo as a bare .bare directory with a .git file pointing at it, so every branch can live as a sibling worktree.
    • create-git-worktree fuzzy-finds a project and a branch with fzf, adds the worktree, and installs dependencies in the background, with a desktop notification when they’re done.
    • tmux-git-session lists every worktree across my projects, and creates or switches to a tmux session named after the branch.
    • tmux-windowizer opens (or reuses) a named tmux window and sends a command to it.

    Together they make a branch feel like a desk you sit down at rather than a state you check out. The full workflow is in Git Worktrees: A Desk per Branch. The point here is that the workflow is versioned: when I improve it, every machine gets the improvement.

    Keyboard firmware is config too

    The repo also holds a QMK keymap for my Corne (crkbd) split keyboard and tools for an Anne Pro 2. Flashing a board is one command from the README:

    qmk flash -kb crkbd -km <keymap> -e BOOTLOADER=atmel-dfu

    If the editor config belongs in git, so does the layout of the keys I type it with.

    Keeping secrets out

    There’s a secrets/ folder, and the only thing in it that git tracks is a .gitignore. Tokens, work credentials and SSH keys never go in the repo. It’s the same lesson I took from cleaning tokens out of a repo’s history at work (Removing Secrets from Git History): it’s far easier never to commit a secret than to remove one.

    What’s still manual

    The README still has a checklist of things to install by hand: a list of yay commands from my Arch days, brew installs for the Mac, a reminder to untick “Automatically rearrange Spaces” in macOS settings. Stow handles config; it doesn’t install software or flip OS settings.

    That gap is what pushed me towards Provisioning My Laptop with Ansible. And it’s also fine. A setup that’s 80% automated and 100% understood beats one that’s fully automated and fragile.

    Progressive overload for your environment

    The thing I value most isn’t any single config. It’s the habit of small, continuous improvements: notice a friction, write a script or a keybinding, commit it, and every future machine benefits. One more rep each session. Over four years that adds up to an environment that follows me from job to job and lets me be productive on day one — Progressive Overload and Incremental Migration applied to my own desk.

    • #dotfiles
    • #gnu-stow
    • #neovim
    • #tmux
    • #developer-experience
    • #infrastructure-as-code

    ↪ Links to (5)