Architecture¶
How the dotfiles repository is organized and why.
External tools (installed from GitHub, not in this repo):
toolbox: Go app viago install github.com/datapointchris/toolboxsesh: Go app viago install github.com/joshmedeski/sesh/v2theme,font: Bash tools cloned to~/.local/share/
Symlink System¶
Two-layer approach: common base + platform overlay.
How it works:
- Links
configs/common/configs to$HOME - Overlays platform-specific files (auto-detected: macos, wsl, arch, or generic linux)
- Links apps from
apps/{platform}/to~/.local/bin/ - Links shell source files from
shell/{platform}/to~/.local/shell/
Common commands (the dotfiles CLI works from any directory; task is equivalent
but only from inside the repo — see Management Interface):
dotfiles link # Deploy all symlinks
dotfiles relink # Complete refresh (remove and recreate)
dotfiles update # Update everything (or a subset: --mine, --no-system)
dotfiles doctor # Health check: symlinks + package-manifest drift
dotfiles symlinks check # Verify symlinks are correct
dotfiles symlinks show # Show all symlinks
Example results:
configs/common/.config/zsh/.zshrc→~/.config/zsh/.zshrcconfigs/macos/.gitconfig→~/.gitconfig(overrides common)apps/common/menu→~/.local/bin/menu
Package Management¶
System Packages: Homebrew (macOS), apt (Ubuntu/WSL, and generic Debian/Ubuntu LXCs), pacman (Arch)
Language runtimes: managed per language via install/packages.yml — version managers where useful (uv, rustup, go) or system packages otherwise (Node.js)
Why separate: Version managers provide cross-platform consistency and project-specific versions without system conflicts.
Machine Manifests¶
Installation is driven by machine manifests in install/manifests/. Each manifest defines exactly what gets installed. Every installed tool is declared as a name in a list; the name must resolve to a catalog entry in install/packages.yml. packages verify enforces this bidirectionally (every name → an entry, every entry → a name that references it or a warning).
# install/manifests/archlinux-personal-workstation.yml
machine: archlinux-personal-workstation
platform: archlinux
function_groups: [core, git, python, aws, docker, network, reference, node, fzf]
alias_groups: [core]
system_packages: workstation # or `core` for a minimal server (linux-lxc-server)
go_tools: [task, cheat, terraform-docs, ...]
github_releases: [fzf, neovim, lazygit, yazi, tree-sitter, tenv, ...]
custom_installers: [bats, awscli, claude-code, terraform-ls, ...]
cargo_packages: [bat, fd-find, eza, zoxide, ...]
npm_globals: [typescript-language-server, prettier, ...]
uv_tools: [ruff, mypy, basedpyright, ...]
git_uv_tools: [refcheck, indy, ...]
# ... etc
Runtime installation is derived from list presence, not from explicit booleans. A non-empty go_tools: list triggers the Go runtime install; a non-empty npm_globals: list triggers nvm + Node; uv_tools: or git_uv_tools: triggers uv. The deprecated go: true / rust: true / nvm: true / uv: true / tenv: true gates were removed in the Phase 1.6 cleanup — packages verify flags any manifest that still sets them.
Run installation with: bash install.sh --machine archlinux-personal-workstation
Shell Source Files¶
Shell functions and aliases live in shell/ organized by platform, deployed via symlinks — no build step required.
- Cross-platform:
shell/common/functions.shandshell/common/aliases.sh→~/.local/shell/ - Platform-specific:
shell/{platform}/{platform}.sh(macos, arch, wsl, linux) →~/.local/shell/{platform}.sh .zshrcsources them explicitly using the$PLATFORMenv var:source "$SHELL_DIR/$PLATFORM.sh"
Windows Git Bash is the only exception — it needs a concatenated combined.sh for startup performance, generated by install/wsl/sync-windows-shell.sh.
Platform Detection¶
Shell (configs/common/.config/zsh/.zshrc):
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
elif [[ -f /proc/version ]] && grep -q Microsoft /proc/version; then
# WSL
elif [[ -f /etc/arch-release ]]; then
# Arch
else
# generic Debian/Ubuntu Linux → the `linux` platform (LXCs, small boxes)
fi
Install script: Platform is read from the machine manifest via manifest_field "platform" rather than auto-detected.
Configuration Layers¶
Configurations use inheritance: shared base with platform overrides.
Example: Git Config
macOS (configs/macos/.gitconfig):
WSL (configs/wsl/.gitconfig):
[core]
editor = nvim
[credential]
helper = /mnt/c/Program\\ Files/Git/mingw64/bin/git-credential-wincred.exe
Example: Neovim
Common (configs/common/.config/nvim/): Base LSP, core plugins, keybindings
Platform-specific (optional): platform LSP configs
Design Decisions¶
Symlinks over Stow: Custom tool provides better two-layer linking, clearer error messages, platform awareness.
Taskfile over Makefile: Cross-platform consistency, better syntax for complex commands, modular includes, self-documenting.
Version Managers for Languages: Same Node/Python versions across platforms, project-specific versions, no system conflicts.
Unified Theme System: The theme CLI generates consistent configs for ghostty, tmux, btop, and Neovim from a single theme.yml source file per theme.
Advantages¶
Minimal Duplication: Only platform differences exist in platform directories.
Clear Separation: configs/common/ for shared, platform dirs for quirks only, apps/ for tools, install/ for repo tooling.
Easy Maintenance: Update shared config once, all platforms benefit.
Testable: Each platform can be tested independently with Docker containers.
Trade-offs¶
Symlink Complexity: Two-layer system adds complexity, but symlinks tool handles it with clear errors.
Platform Knowledge: Need to know whether to edit configs/common/ or platform dir. Experience makes this clear.
See Platform Differences for platform-specific quirks.
Deep Dives¶
-
System vs language version managers
-
Tool precedence and environment setup
-
How tools work together