One Core. Every machine. Zero drift. The shared heart of a cross-platform
dotfiles system β authored once, vendored into every OS layer via git subtree.
zsh Β· nvim Β· tmux Β· starship
Single source of truth for the Core layer shared across every machine repo. This is the keystone of a ten-repo dotfiles system. It holds the config that is identical everywhere β shell modules, tmux base, Neovim, git β and nothing that is OS-specific or offensive.
If it changes when the operating system changes, it does not belong here. If it changes when you as an operator change, it does not belong here. Everything left over is Core, and it lives here.
| Layer | Lives in | Examples |
|---|---|---|
| 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,openSUSE,Alpine,Gentoo} |
package manager, clipboard shim, paths |
| Role | dotfiles-Kali (offensive) Β· dotfiles-Defense (defensive) |
offensive: engagement scaffolding, C2, Impacket; defensive: detections, hunt/triage, lab |
Previously each repo carried its own copy of Core, and drift was caught
after the fact with core-diff.sh. That works at 4 repos. At 10 it doesn't.
This repo flips it: Core is authored once, here, then pulled into each OS
repo as a vendored core/ subtree. No more N-way reconciliation.
Each machine repo (e.g. dotfiles-Fedora) vendors this repo under core/:
# one-time, inside the OS repo:
git subtree add --prefix=core https://github.com/<you>/dotfiles-core main --squashThat physically copies Core into core/ and commits it. The repo now clones
and works with no submodule flags β important, since these are public
showcase repos people will browse.
To update every OS repo after a Core change, run the loop helper from this repo:
./scripts/sync-core.sh # subtree-pulls main into all 8 OS repos
./scripts/sync-core.sh --dry-runRun
make(no target) for a discoverable list of every entry point βmake setup/doctor/audit/test/bench/sync/hooksall shell out to thescripts/*.shdev tooling, which stays the single source of truth. (make doctoris the read-only triage half ofsetup;bin/holds only what ships βclip/clip-paste; the gate scripts live inscripts/.)
The OS repo's bootstrap.sh then symlinks core/zsh/*.zsh, core/tmux/,
core/nvim/, core/git/, core/vim/, core/starship/, core/mise/, and core/bin/ into
place alongside its own OS-native files. (core/bin/ is just clip/clip-paste
now β the dev scripts in core/scripts/ are repo tooling and aren't symlinked.)
- vs submodule β submodules store a pointer, so a fresh clone is empty
until
git submodule update --init. Subtree vendors the actual files, so every repo is self-contained and clone-and-go. Better for portfolio repos. - vs chezmoi β chezmoi (one repo + per-OS templates) is the most DRY answer and is the right move if you ever want to collapse ten repos into one. It trades the ten-repo breadth-portfolio for minimalism. This system keeps the portfolio; switching to chezmoi later is a content migration, not a rewrite, because the Core files here are already plain and OS-agnostic.
Core is fully populated β every layer below is authored here and synced out to
each OS repo's vendored core/. The canonical inventory is core.manifest;
this tree is the human-readable version of it.
bin/ SHIPPED β vendored into every OS repo (in core.manifest):
clip cross-OS "copy to clipboard" (WSL/macOS/Wayland/X11)
clip-paste cross-OS "paste from clipboard"
lib/ SHIPPED β vendored bash libraries (sourced, not run):
ux.sh shared palette/glyphs/spinner β each OS repo's bootstrap.sh
sources this (the pre-shell installer can't load the zsh ui.zsh)
scripts/ DEV TOOLING β runs the gate HERE, never vendored out:
audit-core.sh THE gate: manifest/exec-bit/syntax/lint/behavioral (CI + pre-commit run this)
test-core.sh behavioral suite: clip ladder + load-order smoke + functions + nvim load
bench-core.sh hermetic hyperfine benchmark of the canonical zsh load chain
sync-core.sh loop git-subtree pull across all OS repos (the maintain button)
update-plugins.sh roll the pinned zsh-plugin SHAs (zsh/plugins.zsh) to upstream HEAD
zsh/ sourced by each OS repo's .zshrc loader, IN THIS ORDER:
loader.zsh canonical byte-compile + source loop (vendored so each OS .zshrc can source THIS instead of duplicating it)
tools.zsh detection + single init point (zoxide/starship/atuin/mise) β load FIRST
ui.zsh terminal-UX primitives (_core_err/warn/ok/hint/confirm/spin) β gum-aware
options.zsh setopts + completion system (compinit, cached) + zstyles
history.zsh HISTFILE/HISTSIZE/SAVEHIST + history setopts + secret-ignore
aliases.zsh modern-CLI aliases, each guarded by tools.zsh detection
git.zsh curated OMZ-style git aliases + git_main_branch helper
functions.zsh cross-OS shell functions (mkcd, extract, up, ...)
fzf.zsh fzf env + zle widgets (Ctrl-T/R, Alt-Z, Ctrl-G) + fif/fbr
bindings.zsh vi-mode keybindings (zvm_after_init hook)
plugins.zsh lightweight plugin loader + plugin list
op.zsh 1Password CLI helpers
maint.zsh daily-maintenance control surface (maint-install/run/log)
update.zsh `up` updater + once/day "updates available" nudge
completions/ autoloaded completions for Core's verbs (up/extract/mkcd/β¦) β fpath-added by options.zsh
starship/
starship.toml prompt theme -> symlinked to ~/.config/starship.toml
mise/
config.toml global runtime versions (node/python/ruby/go/rust/java/lua)
sesh/
sesh.toml.example portable session-manager config (seeded, not symlinked)
tmux/
tmux.conf portable base config (OS bits -> os/<os>.conf)
tmux.reset.conf the keybinding layer (prefix C-a lives here)
scripts/ popup scripts: tmux-battery / tmux-cheat / tmux-menu / tmux-netinfo / tmux-scratch / tmux-sesh
maint/
dotfiles-maint.sh the daily "update everything (that's safe)" runner
git/
gitconfig portable git config (OS + identity layered via [include])
local.gitconfig.example identity template β seeded by bootstrap, never tracked
lazygit/
config.yml tokyonight theme -> symlinked to ~/.config/lazygit/config.yml
vim/
vimrc plugin-free fallback for boxes with only stock vim -> symlinked to ~/.vimrc
nvim/ entire lazy.nvim tree: lua/gerrrt/{config,plugins,servers,utils}
core.manifest the canonical list of Core files (drives sync + audits)
core.version human-readable Core version stamp (read by `core-version`)
Load order is load-bearing:
toolsinits atuin (registers its widget),optionsrunscompinit(fzf-tab + carapace need it), andfzfdefines its zle widgets BEFOREpluginsloads zsh-vi-mode, whose init fires the keybinding hook inbindings.uiloads right aftertools(it only defines the_core_*UX helpers every later module may call). Each OS repo's.zshrcsources them astools β ui β options β history β aliases β git β functions β fzf β bindings β plugins β op β maint β update β os β local(the canonical order incore.manifest).
Core is already complete (zsh, tmux, nvim, git, starship, mise, and the clip scripts are all here). The promotion from the old per-repo copies is done β so this is now just the procedure for the occasional new Core file:
- Confirm it's actually Core: identical on every machine, not OS-specific,
not offensive. (OS-specific β the OS repo; offensive β
dotfiles-Kali.) - Drop it into the matching path here.
- Strip anything OS-specific out into the OS repo (clipboard, paths, pkg mgr).
- Add the path to
core.manifestβ that's the contract the audits read. - Wire the symlink into each OS repo's
bootstrap.shif the file needs one. ./scripts/sync-core.shto push it into every OS repo's vendoredcore/.
Day-to-day this is the whole loop β author, gate, fan out:
- Pick the layer. Core only if identical everywhere and not OS-specific
and not offensive. Otherwise it belongs in the OS repo (or
dotfiles-Kali). - Edit here, add a
[Unreleased]CHANGELOG.mdbullet in the same commit (Conventional Commits message). make audit-changed(fast loop) βmake audit(the full gate).make syncβ fan Core out to every OS repo (only after a green audit).
Scheduled bots now cover the chores you used to have to remember to check. They report first (PR or deduped issue) and never vendor anything out on their own β your job is to glance at what they open and merge or act:
| Bot (workflow) | Repo | Cadence | Opens |
|---|---|---|---|
/doc-audit + /tool-scout (claude-routines.yml) |
core | weekly + on demand | findings issue |
pin freshness (freshness.yml) |
core | weekly | PR (rolls zsh-plugin + nvim pins forward) |
fleet-sync.yml |
web | weekly | PR (regenerated site data) |
nvim-sync.yml |
Windows | weekly | PR when nvim/ drifts |
package-freshness.yml |
Windows | weekly | scoop/winget issue |
A quiet week (no bot PR/issue) means nothing needs doing. Every bot is
workflow_dispatch-able to run on demand.
One wiring step:
claude-routines.ymlis inert until theCLAUDE_CODE_OAUTH_TOKENsecret is set on this repo (claude setup-token) β until then the doc-audit / tool-scout issues won't appear.
make release VERSION=X.Y.Zβ promotesCHANGELOG.md's hand-written[Unreleased]to a dated## [vX.Y.Z]heading, bumpscore.version, runs the audit.- Commit it, then β if you publish a GitHub Release β
make release-notesdrafts the release body from Conventional Commits (it does not touchCHANGELOG.md).