The strategic view of the dotfiles system: how the layers are drawn, how Core
fans out to every machine, and why the model is built this way. For the
operational detail (how to consume Core, the manifest contract, the audit gate)
see README.md and CONTRIBUTING.md; this
document is the altitude above them.
A dotfiles setup that serves more than one machine eventually faces the same fork in the road: either every machine keeps its own copy of the shared config (and they drift), or the shared config is centralized (and you fight submodules, or collapse everything into one unportable monorepo).
This system centralizes β but vendors the result so a clone is self-contained.
The shared config is authored once, in dotfiles-core, and physically copied
into each machine repo via git subtree. There is no N-way reconciliation, no
git submodule update --init, and no per-machine drift to chase after the fact.
Every file in the fleet has exactly one home, decided by a single question: what does this change with?
| Layer | Lives in | Changes with | Examples |
|---|---|---|---|
| Core | dotfiles-core, vendored into each OS repo's core/ |
nothing β identical | zsh modules, tmux base, Neovim, git, starship, mise |
| OS-native | one repo per platform | the operating system | package manager, paths, clipboard backend |
| Role | dotfiles-Kali (red) Β· dotfiles-Defense (blue) |
you as an operator | offensive engagement tooling Β· defensive detection/hunt tooling |
The boundary rule, stated as a test:
- If it changes when the operating system changes, it is OS-native β it belongs in the platform repo.
- If it changes when you as an operator change, it is Role β it belongs
in a role repo (
dotfiles-Kalifor offense,dotfiles-Defensefor defense). - Everything left over is Core, and it lives in
dotfiles-coreonly.
Core is not "the Neovim config" or "the shell config" β it is the entire machine-independent surface: the zsh module chain, the tmux base, Neovim, git, starship, and mise, taken together.
Ten repositories make up the configuration system (one Core plus nine machine
repos), with dotfiles-web as an eleventh public repo that documents the system
rather than configuring a machine.
| Repository | Layer | Vendors core/? |
Notes |
|---|---|---|---|
dotfiles-core |
Core | n/a (source) | Single source of truth; fanned out to the rest. |
dotfiles-MacBook |
OS-native | yes | Homebrew; reference implementation, synced first. |
dotfiles-Fedora |
OS-native | yes | dnf; the template the other Linux repos stamp from. |
dotfiles-Arch |
OS-native | yes | pacman + AUR, rolling release. |
dotfiles-openSUSE |
OS-native | yes | zypper; Tumbleweed (dup) + Leap (up) aware. |
dotfiles-Alpine |
OS-native | yes | musl + busybox + doas; the lean outlier. |
dotfiles-Gentoo |
OS-native | yes | emerge from source; USE flags, full atoms. |
dotfiles-Kali |
Role / offensive | yes | Core + apt OS layer + the offensive role layer. |
dotfiles-Defense |
Role / defensive | yes | Core + OS layer + the defensive detection/hunt role layer. |
dotfiles-Windows |
Native host | no | pwsh / scoop / winget; Core is reimplemented, not ported. |
dotfiles-web |
Showcase (none) | no | Astro docs site; the system's public face. |
The canonical Core-vendoring fleet is scripts/os-repos.txt β eight repos.
dotfiles-Windows is deliberately absent from it: its host layer is replicated
from scratch in PowerShell rather than ported one-to-one from the Unix Core, so
it carries no vendored core/ subtree and sync-core.sh must never fan out into
it. (dotfiles-Debian was once planned but is no longer pursued β the Debian
family is covered by dotfiles-Kali's apt OS layer.)
Core flows in one direction β authored here, copied out:
ββββββββββββββββββββββββ
β dotfiles-core β single source of truth
β (core.manifest = β
β the contract) β
ββββββββββββ¬ββββββββββββ
β git subtree pull --prefix=core β¦ --squash
ββββββββββββ¬ββββββββββββΌββββββββββββ¬βββββββββββ¬βββββββββββ
βΌ βΌ βΌ βΌ βΌ βΌ
MacBook Fedora Arch openSUSE Alpine Gentoo
(+ Kali and Defense, which each stack a Role layer β offensive / defensive β on top of an OS layer)
dotfiles-Windows ββ no subtree; Core reimplemented natively in PowerShell
Each machine repo vendors Core under core/ once:
git subtree add --prefix=core https://github.com/dotgibson/dotfiles-core main --squashAfter a Core change, one helper fans it out to the whole fleet:
./scripts/sync-core.sh # subtree-pull main into every os-repos.txt target
./scripts/sync-core.sh --dry-run # preview, change nothingBecause the subtree squash records the exact Core commit, a tagged clone of any
OS repo carries the precise Core it was tested with β the human-readable SemVer
lives in core.version and is vendored alongside it so a machine can report
which Core it runs.
The cardinal rule that follows from this topology: never edit a vendored
core/ tree in an OS repo. It is a copy and is overwritten on the next sync.
Fix Core here, then fan it out.
The zsh module chain is sourced in one canonical order, declared in
core.manifest and driven by zsh/loader.zsh:
tools β ui β options β history β aliases β git β functions β fzf
β bindings β plugins β op β maint β update β os β local
The order encodes real dependencies: tools initializes atuin and fzf defines
its widgets before plugins loads zsh-vi-mode (which fires the binding hook);
options runs compinit before plugins (fzf-tab and carapace need it); git
loads after aliases so its comprehensive git set is the single source of truth.
The chain ends with os then local, so a machine can override Core last
without editing it. Do not reorder casually.
scripts/audit-core.sh is the single definition of "Core is healthy" β manifest
drift in both directions, exec-bit assertions, shell and Lua syntax, shellcheck,
luacheck, markdownlint, and a behavioral test suite. CI, the pre-commit hook, and
make audit all call it. A red tree must never be vendored out, so it is green
before any sync.
make audit # the full gate
make audit-changed # only what the current diff touches
make sync # fan Core out to every OS repo (after a green audit)The manifest is the contract that the gate enforces: a file is Core only if
it is listed in core.manifest. Repo-meta and dev tooling (this document, the
other root docs, .github/, .claude/, scripts/) live in the audit's
allowlist instead β present in the repo, but never symlinked onto a machine.
- Clone-and-go. Subtree vendors the actual files, so a fresh clone of any machine repo just works β no submodule flags, no recursive init. These are public showcase repos people browse, so the first-run experience matters.
- Author once, fan out. A Core fix is written in one place and synced to every machine, instead of being hand-applied N times and drifting.
- One home per file. The boundary test means there is never a question of where a change goes β and never two copies of the same setting to keep aligned.
- Honest by construction. The manifest plus the audit gate make "what is Core" machine-checkable, so the docs and the code cannot quietly disagree.