Architecture

One Core, vendored everywhere

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.

Prefer to just read it? Browse the actual config for each layer → — real files, pulled straight from the source repos.

If it changes when the operating system changes, it does not belong in Core.

If it changes when you as an operator change, it does not belong in Core.

Everything left over is Core — and it lives in exactly one place.

The three layers

LayerLives inExamples
Core this repo, vendored into each OS repo via git subtree zsh modules, tmux base, nvim, git/delta
OS-native dotfiles-{MacBook,Windows,Fedora,Arch,…} package manager, clipboard shim, paths
Role dotfiles-Kali · dotfiles-Defense offensive engagement (Kali) · defensive detection/hunt (Defense)

The fleet at a glance

Core vendoring topology dotfiles-core is vendored into 8 machine repositories via git subtree (solid edges); dotfiles-Windows mirrors only the Neovim and starship configs via sync scripts (dashed edge). Edges are coloured by live vendoring drift — green when the repo carries Core's current release, orange when it is behind — from each repo's recorded provenance. dotfiles-core authored once · vendored via git subtree MacBook OS-native v2.6.0 Fedora OS-native v2.6.0 Arch OS-native v2.6.0 openSUSE OS-native v2.6.0 Alpine OS-native v2.6.0 Gentoo OS-native v2.6.0 Kali OS + offensive v2.6.0 Defense OS + defensive v2.6.0 Windows nvim+starship v2.6.0 git subtree — full Core, self-contained nvim + starship mirror — sync scripts (Windows host) in sync with Core v2.6.0 behind — run a sync Core OS-native Host Role
One Core, vendored into 8 machine repos via git subtree — each clone is self-contained (no submodule init). dotfiles-Kali stacks a Role layer on its OS layer; dotfiles-Windows carries no core/ subtree and mirrors only the Neovim and starship configs. Edge colour tracks live vendoring drift — green = carrying Core v2.6.0, orange = behind (run a sync). Each repo's label shows the release it currently vendors.

How an OS repo consumes Core

Each machine repo (except Windows, which replicates Core natively in PowerShell) vendors dotfiles-core under core/ as a git subtree. That physically copies Core in and commits it, so the repo clones and works with no submodule flags — important, since these are public showcase repos people will browse.

# one-time, inside an OS repo:
$ git subtree add --prefix=core https://github.com/dotgibson/dotfiles-core main --squash

# later, to pull Core updates down:
$ git subtree pull --prefix=core https://github.com/dotgibson/dotfiles-core main --squash

Maintainers fan a Core change out to every OS repo in one shot with scripts/sync-core.sh, which prints the exact short SHA each repo receives so a sync is traceable.

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. Subtree vendors the actual files, so every machine repo is self-contained and clone-and-go. The cost subtree pays back: a vendored copy in each repo (kept honest by the sync script + 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. tools initializes atuin (registering its widget), options runs compinit (fzf-tab + carapace need it), and fzf defines its widgets before plugins loads zsh-vi-mode, whose init fires the keybinding hook in bindings. Every OS repo’s .zshrc sources the chain in this order:

  1. 01 tools detection + single init point (zoxide/starship/atuin/mise) — loads first
  2. 02 ui terminal-UX primitives (_core_err/ok/hint/confirm/spin), gum-aware
  3. 03 options setopts + completion system (compinit, cached) + zstyles
  4. 04 history HISTFILE/SIZE + history setopts + secret-ignore
  5. 05 aliases modern-CLI aliases, each guarded by tools.zsh detection
  6. 06 git curated OMZ-style git aliases + git_main_branch helper
  7. 07 functions cross-OS shell functions (mkcd, extract, up, …)
  8. 08 fzf fzf env + zle widgets (Ctrl-F/R, Alt-Z) + fif/fbr
  9. 09 bindings vi-mode keybindings (zvm_after_init hook)
  10. 10 plugins lightweight plugin loader + pinned plugin list
  11. 11 op 1Password CLI helpers
  12. 12 maint / update daily-maintenance surface + the up updater nudge
  13. 13 os the OS-native layer (os/<platform>.zsh)
  14. 14 offensive Kali only — engagement scaffolding
  15. 15 defense Defense only — detection/hunt tooling
  16. 16 local machine-specific overrides (untracked) — always wins

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 loader ends … os local, Kali inserts one more stage — … os offensive local — 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, apps auto-place on a home workspace, and 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 inside the repos. These are the long-form references worth reading next: