Dotfiles
Abstract
I work across any number of permanent or ephemeral Linux and Unix-based systems, but system configurations accumulate over decades. A repository-based system heavily utilizing userspace configuration, FreeDesktop conventions, and modular design, allows for a single source of truth for personal configuration.
Background and Acknowledgements
Not since college have I been in a position where “my computer” or even “my development machine” is could be considerd a single system. I am nevertheless the kind of person who likes to configure a system to my exact needs, with all the preferred tools for an job.
Sometimes I would need to work on my personal (Linux) machine, or my work Macbook, or one of several WSL environments on my work laptop, or the WSL enviornment on my gaming PC, or on my home server, or my virtual website servers, or servers I host for work, or any number of local VMs, across Debian, Ubuntu, CentOS, RHEL, Oracle Linux, etc…
The effect was that I ould either configure nothing and use bash defaults and vi for everything…or copy scripts and configs everywhere. For years I maintained a script package in a central location I would rsync to any system to get up and running ASAP on any given system, many of which were ephemeral.
Then, when I was forced to use MacOS for the first time and sought advice, I came across first a repo-based solution in the form of ptb/mac-setup from Peter T Bosse II, then mathiasbynens/dotfiles from Mathias Bynens, and my eyes were opened to the possibilities of hosting this through a hosted repository for distributed fetch and contribution. It has evolved a lot in the intervening 7 years, but these were my foundational examples.
Philosophy
- Everything should be functional in userspace; all software but the classic basics (e.g. sh, perl, GNU coreutils) should be optional
- FreeDesktop (XDG) conventions should be preferred wherever possible
- When a new tool, toolkit, or utility is added, it should ideally be possible to add as a standalone file, and have it take immediate effect (distributed contribution)
- Simple conventions on filename should determine which scripts/snippets/components are used ; no complicated script to pick and choose
- Conditional behavior (such as hooking in completions or appending the PATH) should be conditional based on the tools currently present
- Where possible, existing local config should supercede or merge into distributed config (let VCS help restore what was clobbered or changed)
- Changes to configurations – both explicit and implicit – should be visible in the repository view, for selective commit and push (achieved through symlinking)
XDG Conventions
The repo assumes XDG conventions – even on system which do not honor it.
One of the clever parts of the system is that every piece of configuration also ihas its default avalues, which allows for these conventions to be stablished and enforced even when unconfigured.
For example, a defaults assignment and conditional export, like the following bash statement, either honors or establishes the convention.
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-"$HOME/.config"}
export XDG_CONFIG_HOME
Symlinks
Most of the repository is just composed of my personal preferences and needs, but the parts worth sharing here are the little tricks used to capture and track then. One such is the use of symlinks.
- The repo contains a .config dir which is symlinked to “$XDG_CONFIG_HOME”. Any app which respects the convention will then implicitly add its files to the repo
- The repo has a
.rc/dir which contains all of the main scripts (more on this next), symlinked - Files such as
.profileare similarly symlinked to bootstrap the whole thing - Any file which already exists is copied moved into the repo before symlinking to avoid data loss. The integrator (always me!) can evaluate the git diff to merge or clobber any such files as necessary.
Conditional Import
In this scheme, files for different purposes are all contributed independently. Some examples:
- .rc/rc001_freedesktop.sh
- .rc/rc100_android+linux.sh
- .rc/rc100_android+macos.sh
- .rc/rc200_vim.sh
- .rc/rc600_docker+debian+wsl.sh
- .rc/rc800_utility+zsh.sh
- .rc/rc800_utility+macos.sh
What to understand about this convention:
- Scripts are run in natural order (so rc001_* always runs before rc100_*)
- Scripts with
+fooare conditionally run only in a “foo” environment. - Scripts are presumbed to be POSIX sh compatible, unless a tag constrains this
Putting these together, some examples:
- rc100_vim.sh runs on a all systems
- rc800_utility+zsh.sh only runs when zsh is the active shell
- rc600_clipboard+debian+wsl.sh only activates when running a Debian image within in a WSL environment (a very specific and weird cirsumstance for Docker environments, which did come up)
- rc200_vim.sh will run on all systems, but can implicitly depends on the XDG configs found in rc001_freedesktop.sh (which runs earlier due to natural ordering)
This approach keeps the initialization complexity in the shared and rerarely-moving config files, while allowing any number of generic or specific files to be added at random when new utility functions, alises, exports, or other configs arise.
Repo
https://github.com/gsprdev/dotfiles/tree/main
See the latest here. It’s a moving target.