Manage a full dev environment across Linux, macOS, and WSL2 using:
- GNU Stow for clean, reversible symlinks
- mise for versioned runtimes and global packages (Node, Go, Rust +
npm:andcargo:backends) - Neovim installed from GitHub releases (stable/nightly) with easy switching
- Linux install policy: apt → mise → fallback
- Optional Neovim config via git subtree (keep your nvim repo separate but vendor it here)
- Startup checks/upgrades (apt/brew/mise) +
fastfetch, with safe caching - Shared fonts manifest for Windows & Linux installs
- WSL niceties, host overlays, and idempotent bootstrap
If you clone to a different path than
~/.dotfiles, update the.zshrcstartup hook accordingly.
cd ~
git clone [email protected]:YOURUSER/dotfiles.git .dotfiles
cd .dotfiles
# (Optional, one-time per repo) vendor your Neovim config via subtree
# scripts/nvim-subtree.sh init [email protected]:YOURUSER/your-nvim-repo.git main
# Install tooling, mise, global toolchains & packages, fonts (by default), Neovim stable, etc.
bash bootstrap.sh
# Symlink all configs (zsh, git, nvim, starship, env, etc.)
./stow-all.sh
# Open a NEW terminal so mise activation & PATH applySmoke test
nvim --version | head -n1
node -v && npm -v && go version
lazygit --versionImport your current dotfiles into this repo without losing anything:
cd ~ && git clone [email protected]:YOURUSER/dotfiles.git .dotfiles && cd .dotfiles
# See what would change
make adopt-dry # or: scripts/adopt-existing.sh --dry-run zsh nvim git
# Adopt: backs up conflicts to ./.migration_backups/, moves files into packages, then symlinks
make adopt # or: scripts/adopt-existing.sh zsh nvim git
git add -A && git commit -m "adopt: import existing dotfiles"
# Install/refresh tooling and stow
bash bootstrap.sh
./stow-all.shAfter make adopt, compare your repo files with the backups and merge differences:
# Use nvimdiff (default):
make adopt-merge
# Or explicitly choose a tool:
MERGE_TOOL=meld make adopt-merge
MERGE_TOOL=code make adopt-mergeWhat it does:
-
Uses the latest
.migration_backups/<timestamp>/snapshot. -
For each package, opens a 2-way diff: repo vs backup.
-
Save your reconciled file in the repo; it auto-stages. Commit when done:
git commit -m "merge: reconcile adopted configs"
make adopt-dry/make adoptwill auto-install GNU Stow if missing (apt on Linux, Homebrew on macOS) and require a build with--adopt.
-
Shell & CLI
zsh+ Starship promptripgrep,fzf,fd,bat,lazygit(via smart installer)fastfetchsummary on shell start (opt-in auto-upgrades)
-
Runtimes & globals (via mise)
rust@latest,node@lts,go@latestnpm:npm@latest,npm:prettiercargo:ripgrep-all(example; extend as you like)
-
Neovim
- Installed from GitHub release tarballs on Linux (stable/nightly channels)
- Easy switching via symlink (
~/.local/bin/nvim) - On macOS, uses Homebrew stable or
--HEADfor nightly
-
Config management
- Stowed packages under
stow/(zsh,nvim,git,starship,env, etc.) - Host overlays:
stow/hosts/@common,stow/hosts/$(hostname),stow/hosts/wsl
- Stowed packages under
-
Fonts
- Shared manifest at
fonts/manifest.jsondrives installs on both Windows & Linux
- Shared manifest at
Configure once in fonts/manifest.json:
{
"nerd_fonts": ["JetBrainsMono", "FiraCode"],
"include_symbols": false,
"extra_urls": [],
"local_dir": "fonts/local"
}Windows (per-user fonts)
pwsh -NoProfile -ExecutionPolicy Bypass -File (Join-Path $HOME '.dotfiles/scripts/win/install-fonts.ps1')Then set your Windows Terminal profile:
"fontFace": "JetBrainsMono Nerd Font"Linux / WSL (for WSLg GUI apps & Linux terminals)
scripts/install-fonts-linux.shInstalls into ~/.local/share/fonts/NerdFonts/... and refreshes with fc-cache.
Fonts during bootstrap
-
By default,
bootstrap.shinstalls fonts fromfonts/manifest.json. -
Linux/WSL: installs to
~/.local/share/fontsand refreshes cache. -
WSL2: also calls the Windows installer via PowerShell.
-
Skip on a machine:
DOTFILES_INSTALL_FONTS=0 bash bootstrap.sh # or set in ~/.config/dotfiles/env.sh: # export DOTFILES_INSTALL_FONTS=0
.dotfiles/
├── README.md
├── Makefile
├── bootstrap.sh
├── stow-all.sh
├── fonts/
│ ├── manifest.json # shared list of fonts for both OSes
│ └── local/ # optional: extra .ttf/.otf you track
├── packages/
│ ├── apt.txt # safe defaults on Linux; avoid version-critical stuff here
│ └── brew-Brewfile # optional; brew bundle if you want
├── scripts/
│ ├── detect-os.sh
│ ├── install-apt.sh
│ ├── install-brew.sh
│ ├── install-mise.sh
│ ├── mise-setup-globals.sh
│ ├── nvim-manager.sh
│ ├── nvim-subtree.sh
│ ├── set-default-shell-zsh.sh
│ ├── wsl-post.sh
│ ├── smart-install.sh
│ ├── startup.sh
│ ├── merge-from-backup.sh
│ ├── install-fonts-linux.sh
│ └── win/
│ ├── install-fonts.ps1
│ └── sync-windows-terminal.ps1
└── stow/
├── zsh/ # .zshrc (sources ~/.config/dotfiles/env.sh, runs startup hook)
├── git/ # .gitconfig, .gitignore_global, aliases
├── nvim/ # .config/nvim (can be a subtree of your separate nvim repo)
├── starship/ # .config/starship.toml
├── env/ # .config/dotfiles/env.sh (env vars; stowed and sourced by .zshrc)
└── hosts/
├── @common/ # overlay on all machines
├── $(hostname)/ # machine-specific overlay
└── wsl/ # WSL overlay
bootstrap Install base tooling, fonts (configurable), mise + globals, Neovim, etc.
link / unlink / restow Stow, unstow, or restow all packages
adopt-dry / adopt Dry-run or adopt existing files into repo (backs up conflicts)
adopt-merge Interactively merge repo files with latest backup snapshot
mise-globals Re-apply global mise toolchains and packages
nvim-stable Install + switch to Neovim stable
nvim-nightly Install + switch to Neovim nightly
nvim-switch-stable Switch symlink to stable (Linux)
nvim-switch-nightly Switch symlink to nightly (Linux)
nvim-subtree-pull Pull latest nvim config from upstream subtree remote
nvim-subtree-push Push changes in subtree back to upstream
fonts-linux Install fonts per manifest on Linux/WSL
fonts-windows Install fonts per manifest on Windows
doctor Quick tool presence checks
- Linux: tarballs installed to
~/.local/nvim/{stable,nightly}, with~/.local/bin/nvimsymlink - macOS: Homebrew stable; nightly via
brew install --HEAD neovim
# Install/switch stable
make nvim-stable
# Install/switch nightly
make nvim-nightly
# Switch back and forth (Linux)
make nvim-switch-stable
make nvim-switch-nightlyOne-time setup (run once in the dotfiles repo):
scripts/nvim-subtree.sh init [email protected]:YOURUSER/your-nvim-repo.git main
git pushAuto-update on bootstrap: bootstrap.sh runs scripts/nvim-subtree.sh pull --auto (no-op if unconfigured).
Manual:
make nvim-subtree-pull # update vendored config from upstream
make nvim-subtree-push # publish local changes back to upstreamEvery interactive shell runs:
~/.dotfiles/scripts/startup.sh --auto
Default behavior (safe/fast):
- APT/Homebrew/mise checked on a 24h cache (tweak via
DOTFILES_STARTUP_INTERVAL_HOURS) - No password prompts (skips upgrades if sudo isn’t cached)
- Prints a brief status and then runs fastfetch (or neofetch, or a tip to install)
Enable auto-upgrades on login (noninteractive) in ~/.config/dotfiles/env.sh:
export DOTFILES_STARTUP_AUTO_UPGRADE=1
export DOTFILES_STARTUP_INTERVAL_HOURS=8 # optional, default 24Manual full run (allows sudo prompts):
DOTFILES_STARTUP_AUTO_UPGRADE=1 ~/.dotfiles/scripts/startup.sh-
apt first for stable, non-version-critical packages (e.g., ripgrep, fzf, starship, lazygit if new enough).
-
mise for version-controlled toolchains and backends:
- Toolchains:
rust@latest,node@lts,go@latest - Global packages:
npm:prettier,npm:npm@latest,cargo:ripgrep-all, etc.
- Toolchains:
-
Fallback for:
- Official scripts/binaries (e.g., Starship, lazygit releases) when needed
go install ...@latestif appropriate
The helper script scripts/smart-install.sh applies this policy and exposes flags like --min-version.
Create or edit ~/.config/dotfiles/env.sh (stowed from stow/env), e.g.:
# Auto-upgrade on login (apt/brew/mise) – default is 0 (off)
export DOTFILES_STARTUP_AUTO_UPGRADE=0
# Cache window for startup checks (hours)
export DOTFILES_STARTUP_INTERVAL_HOURS=24
# Fonts during bootstrap (Linux + Windows via WSL): 1=install, 0=skip
export DOTFILES_INSTALL_FONTS=1
# Add anything else global you want here:
# export EDITOR="nvim".zshrc sources this file before running the startup hook, and also runs:
if [ -t 1 ] && [ -x "$HOME/.dotfiles/scripts/startup.sh" ]; then
"$HOME/.dotfiles/scripts/startup.sh" --auto || true
fiKeep your Windows Terminal JSONs in the repo and sync them:
win/windows-terminal/
settings.json
schemes.json
scripts/win/sync-windows-terminal.ps1
Run:
pwsh -NoProfile -ExecutionPolicy Bypass -File (Join-Path $HOME '.dotfiles/scripts/win/sync-windows-terminal.ps1')
# add -Mode Symlink if you prefer live-linking (requires Dev Mode or admin)Idempotent setup: installs Stow, runs install-apt.sh (Linux), installs fonts (Linux + Windows via WSL by default), installs mise & globals, pulls Neovim subtree (if configured), installs Neovim stable, runs smart installers (starship/lazygit), and prints next steps.
Stows all packages under stow/, then overlays hosts/@common, hosts/$(hostname), and hosts/wsl (if detected).
Installs safe defaults from packages/apt.txt (includes fontconfig and unzip for fonts). Adds QoL aliases (e.g., batcat→bat, fdfind→fd) if needed.
Installs Homebrew on macOS if missing and wires shell env. Use brew bundle with packages/brew-Brewfile if you want.
Official installer (adds activation to your ~/.zshrc):
curl -fsSL https://mise.run/zsh | shApplies global toolchains and packages:
mise use -g -y rust@latest
mise use -g -y node@lts
mise use -g -y go@latest
mise use -g -y npm:npm@latest
mise use -g -y npm:prettier
mise use -g -y cargo:ripgrep-all
mise reshimInstall/switch Neovim channels.
Usage: nvim-manager.sh {install|switch|current} {stable|nightly}
Linux:
install stable # download tarball to ~/.local/nvim/stable
install nightly # download tarball to ~/.local/nvim/nightly
switch stable # symlink ~/.local/bin/nvim -> stable
switch nightly # symlink ~/.local/bin/nvim -> nightly
current # show current symlink target
macOS:
install stable # brew install neovim
install nightly # brew install --HEAD neovim
switch ... # (advisory) PATH determines which brew nvim wins
current # prints nvim version
Manage your separate Neovim repo as a subtree under stow/nvim/.config/nvim.
Usage:
nvim-subtree.sh init <repo-url> [branch] # one-time add
nvim-subtree.sh pull [--auto] # update subtree from upstream
nvim-subtree.sh push # publish changes upstream
Repo-local config:
subtree.nvim.remote (default: nvim-origin)
subtree.nvim.url
subtree.nvim.branch (default: main)
Safely import legacy dotfiles into this repo. Auto-installs Stow if missing.
Usage:
adopt-existing.sh # adopt all packages
adopt-existing.sh zsh nvim # adopt selected packages
adopt-existing.sh --dry-run # show changes only
Behavior:
- Backs up real files that would be replaced to ./.migration_backups/<ts>/<pkg>/
- Uses `stow --adopt` to move your existing files into package dirs
- Restows to create symlinks
Interactively merge repo files with the latest backup snapshot from adoption.
Usage:
MERGE_TOOL=nvimdiff|meld|code scripts/merge-from-backup.sh [pkg...]
Implements apt → mise → fallback installation policy.
Usage examples:
smart-install.sh starship
smart-install.sh lazygit --min-version 0.41.0
smart-install.sh "npm:prettier"
smart-install.sh "cargo:ripgrep-all"
Cached checks/upgrades + fastfetch. Won’t block your prompt in --auto.
Usage:
startup.sh [--auto]
Env:
DOTFILES_STARTUP_INTERVAL_HOURS=24
DOTFILES_STARTUP_AUTO_UPGRADE=0|1
Installs fonts listed in fonts/manifest.json into ~/.local/share/fonts, then runs fc-cache.
Installs fonts listed in fonts/manifest.json into the per-user Windows fonts folder and refreshes font cache.
Copies or symlinks win/windows-terminal/settings.json into the correct Windows Terminal directory.
Exports OS (linux|mac) and WSL (1 if WSL detected).
If zsh exists, sets it as the login shell via chsh (best-effort; safe to fail).
WSL niceties: clipboard tool, Git line endings (core.autocrlf=input), and optional win32yank.exe shim for Neovim clipboard.
stow/hosts/@common/stows after your base packagesstow/hosts/$(hostname)/stows after@commonstow/hosts/wsl/stows last on WSL
Put machine-specific tweaks (aliases, term settings, etc.) into the appropriate overlay and re-run ./stow-all.sh.
nvimwrong binary → Ensure~/.local/binis early inPATH(set in.zshrc).misenot recognized after bootstrap → Open a new shell (installer adds activation to your~/.zshrc) orsource ~/.zshrc.git subtreemissing → Linux:sudo apt install git-subtree(inpackages/apt.txt). macOS:brew install git.- Startup upgrades blocking prompt → They won’t in
--auto. To do real upgrades, setDOTFILES_STARTUP_AUTO_UPGRADE=1. For immediate full upgrades with prompts, run~/.dotfiles/scripts/startup.shwithout--auto. - Nightly Neovim download fails → Check CPU arch handling in
nvim-manager.sh; open an issue to add your arch if needed.
- Add more stow packages under
stow/<name>/mirroring$HOMEpaths. - Extend
mise-setup-globals.shwith morenpm:orcargo:globals and tool pins. - Layer host-specific overrides in
stow/hosts/. - Tweak
packages/apt.txtas you like; keep version-critical stuff out (we use mise for that).
Happy dotfiling ✨