The system is built on one decision repeated consistently: draw a hard boundary between what is identical on every machine and what changes with the OS or the operator. Get the boundary right and a single Core can serve every machine with zero special-casing.
Native hostdotfiles-Windowspwsh, Terminal, the WSL bridge — vendors no Core, replicates it
The Windows host is the model's one named exception rather than a fourth layer: it carries no vendored core/, so it sits beside the three rather than inside the OS-native row.
The fleet at a glance
One Core, vendored into 10 machine repos as a full copy — each clone is self-contained (no submodule init).dotfiles-Offense and dotfiles-Defense are Role layers that stack on any OS layer; dotfiles-Windowscarries no core/ at all and mirrors only nvim/, starship.toml and theme/palette.toml. Edge colour tracks live vendoring drift — green = carrying Core v7.14.0, orange = behind (run a sync), cyan = replicated, with no vendored release to drift. Each vendoring repo's label shows the release it carries; the host's shows the Core ref its mirror came from.
How an OS repo consumes Core
Each machine repo (except Windows, which replicates Core natively in PowerShell)vendorsdotfiles-core under core/ — Core is physically copied in and committed, so the repo clones and works with no submodule flags, important since these are public showcase repos people will browse. Updates are not pulled from the machine repo; they are fanned out from Core.
# one-time, for a repo with no core/ yet — a RELEASED tag, never main:$ git subtree add --prefix=core https://github.com/dotgibson/dotfiles-core refs/tags/v7 --squash# every update after that, run from a dotfiles-core checkout:$ ./scripts/sync-core.sh dotfiles-<OS>
sync-core.sh resolves Core once, materializes core/ at that exact commit, and stamps core.lock beside it in the same commit — so every repo in a fan-out carries the same Core no matter how long the loop takes, and "which Core is this box on?" is answerable offline. The one command never to run isgit subtree pull: it moves the tree but not the lock, and the integrity check then reports a repo nobody touched as tampered. Seevendoring Core for the full contract.
How it compares — and when it's the wrong tool
Every dotfiles approach is a trade. This one optimizes for clone-and-go self-containment across many machines and a public, per-machine portfolio — paying a little vendored duplication for it. Here's how that lands against the usual alternatives, each of which wins in its own case.
vs git submodule
A submodule stores a pointer, so a fresh clone is empty until git submodule update --init — the classic "I cloned it and nothing works" footgun. Vendoring copies the actual files in, so every machine repo is self-contained and clone-and-go. The cost it pays back: a duplicated tree in each repo (kept honest by the sync script, a lockfile, and a manifest audit).
vs chezmoi / yadm
One repo + per-OS templates is the most DRY answer, and the right move the day you want to collapse the whole fleet into one. This system keeps the multi-repo portfolio instead — each machine is a clean, public, self-contained artifact. Because Core is already plain and OS-agnostic, moving to chezmoi later is a content migration, not a rewrite.
vs GNU stow
stow is a perfect, zero-magic symlink farmer over a single repo — and genuinely simpler if you have one machine. What it has no opinion about is layering or per-OS divergence, so a multi-OS setup drifts into host branches and .stow-local-ignore gymnastics. This system bakes the Core / OS / Role split in and ships its own idempotent symlinker (with backups + a dry-run).
vs a bare $HOME git repo
The git --bare + alias trick is the leanest possible — no symlinks, files live in $HOME. Hard to beat solo. But it has no layer model, no per-OS story, noisy status against your whole home dir, and it is a real footgun on a shared or multi-user box. This trades that leanness for an explicit, auditable structure.
When to reach for something else
A system that can't name its own constraints isn't worth trusting. Don't use this if:
You have one or two machines with no real OS spread — this is over-engineered for that; a bare $HOME repo or stow is far less ceremony.
You want ONE repo, not a fleet — reach for chezmoi or yadm. (The migration is content, not a rewrite, because Core is already plain.)
You can’t stomach any vendored duplication — the multi-repo + vendored-Core model deliberately trades a little duplication for clone-and-go self-containment and a public per-machine portfolio.
The canonical load order
Load order is load-bearing. 00-tools initializes atuin (registering its widget), 10-options runs compinit (fzf-tab + carapace need it), and 35-fzf defines its widgets before45-pluginsloads zsh-vi-mode, whose init fires the keybinding hook in 40-bindings. Every OS repo’s .zshrc just sources the vendored loader.zsh, which globs $ZSH_CFG/NN-*.zsh, sorts by the numeric prefix, and sources each — the order is the numbering, with no hand-maintained module list:
00toolsdetection + single init point (zoxide/starship/atuin/mise) — loads first
02capabilitiesreads the OS layer’s capability declaration (os.capabilities) — extracted, never sourced
One v4 refinement rides this loader. The XDG split moves mutable state out of the symlinked config tree — history to $XDG_STATE_HOME, the compdump to $XDG_CACHE_HOME, plugins to $XDG_DATA_HOME— while each fragment’s byte-compiled .zwc stays beside it for fast startup.
The Kali role stage
Kali stacks three layers — Core, its apt OS layer, and an offensive role stage (dotfiles-Defense mirrors this on the blue side). Where a plain OS repo’s fragments end … 80-os 99-local, Kali drops in one more —85-offensive.zsh (role band 85), sorting between the two — for engagement scaffolding (scope-first workspaces, an audit-trail logshell, NetExec/BloodHound CE helpers). Engagement data never lives in the repo; it stays under~/engagements, and every tool is for authorized engagements with written rules of engagement only.
The macOS desktop layer
OS-native layers own more than packages and paths. On macOS,dotfiles-MacBook commits a full tiling-desktop setup — window manager, menu bar, and keyboard — themed to Core’s tokyonight palette so the GUI reads as an extension of the terminal, not a separate world.
◳ AeroSpace
i3-style tiling window manager — pure TOML, no SIP disable required, no daemon. alt drives focus, move, resize, and workspaces; its keymap is kept identical to GlazeWM (the Windows host WM) so the tiling workflow is the same on both OSes. JankyBorders rings the focused tile.
▭ SketchyBar
A programmable menu bar themed to tokyonight-storm, matching starship + tmux. Live AeroSpace workspaces on the left; CPU, memory, disk, network, and a click-to-toggle keep-awake on the right — every glyph from the one Nerd Font, no extra deps.
⌨ Karabiner
Caps→Ctrl/Esc, and a Tab-hyper layer that mirrors the WM verbs — hyper+hjkl to focus, hyper+1–5 for workspaces — so the whole tiling workflow stays on the home row.
Deep dives
The design decisions above are documented in full on this site. These are the long-form references worth reading next: