One repo, every machine. This repo is ~/.config, so Neovim, Ghostty,
Alacritty, starship and the rest find their config at the XDG path without
any setup. make install only links the few files that have to live outside
~/.config, and then it tells you what is still missing.
make install reports missing tools. It never installs them, so you
decide what goes on each machine. The steps below are the whole sequence.
There is one repo, and it is cloned as ~/.config on every machine.
There's no separate ~/dotfiles folder, and no stow or chezmoi.
-
Most tools need nothing. Neovim, Ghostty, Alacritty, starship, lazygit, gh-dash, k9s and bat all read
~/.config/<tool>, which is the repo. -
Four files get a symlink, because their tools only look in
$HOME:Link Points at ~/.zshrczsh/.zshrc~/.tmux.conftmux/tmux.conf~/.gitconfiggit/gitconfig~/.ssh/configssh/config(only if you restored it; it's gitignored)The list is
LINKSinbootstrap, and it should stay that short. -
Generated files are not committed. The theme fragments are rebuilt by
make installfromtheme/palettes/. -
Per-machine differences go in gitignored local files (
~/.zshrc.local,ghostty/local.conf), not inif hostname == …branches. macOS-vs-Linux differences that every machine of that OS shares are handled in the config itself ($OSTYPEchecks, every Homebrew prefix). -
Everything else a tool writes into
~/.config(caches, tokens, login state) is ignored by default..gitignoreis an allowlist: it ignores the whole top level and re-includes only the directories this repo tracks. The repo is public, so a new tool's directory stays invisible until you choose to track it. See Maintenance. -
Private lines stay local. A line in
gh-dash/config.ymlthat ends in# local-only(a client repo's path, say) is kept in your working copy but stripped before git stores the file.make installsets up the git filter that does this.
Changing something on one machine is a normal commit and push. Every other
machine picks it up with git pull && make install
(Keeping a machine in sync).
!!! info "Homebrew everywhere, including Linux"
The tool list in bootstrap is a list of Homebrew formulae. On Linux, use
Homebrew on Linux too. The
alternative is distro packages, which cause three problems:
- Debian and Ubuntu ship a Neovim older than the 0.12 this config needs.
- apt renames binaries (`bat` becomes `batcat`, `fd` becomes `fdfind`),
and `.zshrc` looks for the real names.
- Half the list (`delta`, `eza`, `lazygit`, `starship`…) isn't packaged at
all, so you end up with a mix of cargo, curl scripts and releases. The
versions then drift between machines.
With Homebrew, `make tools` prints the same install line on every machine.
`.zshrc` and `bootstrap` already look for every Homebrew prefix
(`/opt/homebrew`, `/usr/local`, `/home/linuxbrew/.linuxbrew`).
=== "macOS"
```bash
# 1. Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Clone (the path matters, see "Cloning somewhere else" below)
git clone https://github.com/codephilip/dotfiles.git ~/.config
# 3. Tools: formulae + casks (terminals, Nerd Font)
cd ~/.config && make tools | sh
# 4. Symlinks + generated theme files
make verify # dry run, shows what would change
make install
# 5. New shell, then let the editor bootstrap itself
exec zsh
nvim # lazy.nvim installs the plugins; then :Mason for language servers
```
Then fix **key repeat** (see [below](#macos-key-repeat)), or holding
++j++ in Neovim moves one line at a time.
=== "Linux"
```bash
# 1. Build prerequisites + zsh (pick your distro)
sudo apt install build-essential procps curl file git zsh # Debian / Ubuntu
sudo dnf group install development-tools \
&& sudo dnf install procps-ng curl file git zsh # Fedora
sudo pacman -S --needed base-devel procps-ng curl file git zsh # Arch
# 2. Homebrew on Linux, then put it on PATH for this shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
# 3. Clone
git clone https://github.com/codephilip/dotfiles.git ~/.config
# 4. Tools (formulae only, Linux has no casks)
cd ~/.config && make tools | sh
# 5. Symlinks + generated theme files
make verify
make install
# 6. zsh as login shell. Use the system zsh: chsh only accepts
# shells listed in /etc/shells, and brew's zsh isn't in it.
chsh -s "$(command -v /usr/bin/zsh || command -v zsh)"
exec zsh
nvim
```
On a **desktop** Linux machine, also install:
| What | How |
| --- | --- |
| JetBrainsMono **Nerd Font** | download from [nerd-fonts releases](https://github.com/ryanoasis/nerd-fonts/releases), unzip into `~/.local/share/fonts`, run `fc-cache -f` |
| Terminal | [Ghostty](https://ghostty.org/docs/install) or `alacritty` from the distro |
| Clipboard tool | `wl-clipboard` (Wayland) or `xclip` (X11). Neovim yanks to the system clipboard (`clipboard=unnamedplus`) and needs one of these |
A **headless server** needs neither the font nor a terminal. Icons are
drawn by the terminal on the machine you SSH *from*. `make verify` lists
the desktop items separately, so you can skip them.
=== "Windows"
Windows has its own installer (scoop + MSYS2). `make install` refuses to
run there. Full detail is on the [Windows](windows.md) page.
```bash
# Git Bash, with scoop installed (https://scoop.sh)
git clone https://github.com/codephilip/dotfiles.git ~/.config
cd ~/.config && make windows
```
make install is idempotent and never overwrites a real file. If ~/.zshrc
already exists as a regular file, it tells you and leaves it alone. Here is
what it does:
- Links
~/.zshrc,~/.tmux.conf,~/.gitconfigand, if present,~/.ssh/config. - Lists missing tools as copy-pasteable
brew installlines. - Runs
theme regen, which writes the generated colour fragments (a fresh clone has none, they're gitignored) and fetches thebattheme that Tokyo Night needs.
The repo can't carry secrets, logins or per-machine state, and nothing
checks for them, so do these once per machine after make install:
| What | Command | Needed for |
|---|---|---|
| SSH config | restore ssh/config from the password manager, chmod 600, re-run make install |
the github, prox-*, macmini* and Hetzner hosts |
| Push over SSH | git -C ~/.config remote set-url origin git@github.com:codephilip/dotfiles.git |
pushing from this machine (the clone above is HTTPS, which works before SSH keys exist) |
| GitHub auth | gh auth login |
gh, gh-dash, octo.nvim. The token lives in the OS keychain |
| PR dashboard | gh extension install dlvhdr/gh-dash |
gh dash. Its config is already in gh-dash/ |
| Claude Code | curl -fsSL https://claude.ai/install.sh | bash |
the claude CLI and its Neovim integration |
| Copilot | :Copilot auth inside Neovim |
inline suggestions. The token goes to github-copilot/ (gitignored) |
| Secrets | create ~/.zshrc.local with export ANTHROPIC_API_KEY=… |
CodeCompanion in Neovim (inert without it) |
| Ghostty overrides | ghostty/local.conf (gitignored), e.g. background-blur = macos-glass-regular on macOS 26+ |
per-machine terminal tweaks |
| Docs toolchain | brew install mkdocs-material (or pip install mkdocs-material) |
make serve / make docs |
~/.zshrc.local is sourced last and gitignored. Put anything that differs
between machines there (secrets, work-only PATH entries, aliases for one
host) rather than adding if hostname == … branches to .zshrc.
Run make verify and read the output. Every line should be a ✓ apart from
the desktop items on a headless server. Then:
- A new terminal opens zsh with the starship prompt and Nerd Font icons (boxes instead of icons means the font is missing)
-
ls -la ~ | grep -- '->'shows the four symlinks pointing into~/.config -
nvimstarts with no errors, and:Doctorand:checkhealthare clean apart from the expected provider warnings -
:Masonshows the language servers installed (first launch installs them in the background, so give it a minute) -
theme nextchanges the colours in the terminal, prompt and Neovim -
gh auth statusis logged in, andgh dashopens - On a Mac, holding ++j++ in Neovim scrolls smoothly (key repeat, below)
A stock Mac makes Neovim feel broken: holding ++j++ moves one line per press.
The fix is per-user macOS defaults, which can't live in a repo, so
make install reports them and you run them yourself. All three steps are
needed, in this order:
- System Settings → Keyboard. Set Key Repeat Rate to the fastest notch and Delay Until Repeat to the shortest. Do this first. Writing the defaults without it doesn't take effect.
- Run:
defaults write -g KeyRepeat -int 1 # below the slider's floor of 2 defaults write -g InitialKeyRepeat -int 15 defaults write -g ApplePressAndHoldEnabled -bool false # no GUI control for this
- Log out and back in. Relaunching the terminal isn't enough.
make verify reports the stored value, which is correct right away even
though the session still uses the old one. The only real test is holding ++j++
after logging out. More detail: Troubleshooting.
git clone … ~/.config fails if ~/.config already has content, which is
common on a machine that has been in use. Adopt the directory in place
instead:
git init ~/.config && cd ~/.config
git remote add origin https://github.com/codephilip/dotfiles.git
git fetch origin && git checkout -f main # -f: repo files win over existing onesCheck git status afterwards. Untracked files that tools wrote there stay
put, and the .gitignore already covers the common ones.
Cloning to a path other than ~/.config only half works. bootstrap and
scripts/theme resolve the repo from their own location, so linking and
theme generation are fine. But every tool that reads an XDG path (Neovim,
Ghostty, Alacritty, starship) still looks in ~/.config. Symlink ~/.config
to the clone, or clone there in the first place.
After a change lands on another machine:
cd ~/.config
git pull
make install # picks up new links, regenerates theme files
exec zsh
nvim "+Lazy restore" # match plugins to the committed lazy-lock.jsonUse :Lazy restore on the secondary machines, not :Lazy update.
restore checks out the exact commits that were vetted and committed,
while update pulls whatever upstream has now. Update on one machine, using
the Neovim maintenance routine,
commit the lockfile, then restore everywhere else.
Some tools arrive by several routes (native installer, npm, pip, cargo, a
direct download), so make verify has an External tools section that
prints each one's path, how it was probably installed, and its version:
External tools
not from this homebrew — compare versions across machines
✓ Claude Code 2.1.318
/Users/pg/.local/bin/claude — native installer / pipx / uv
✓ mkdocs 1.6.1
/opt/homebrew/bin/mkdocs — homebrew
A missing tool shows up as an error. The same tool installed three ways on
three machines just quietly runs three versions. Run make verify on each
machine now and then and compare.