Skip to content

Latest commit

History

105 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Dotfiles

Personal dotfiles for macOS and Ubuntu/Debian, managed with chezmoi.

Source of truth: github.com/abkolan/dotfiles. Everything below is verified against the files in this repository — if a command here disagrees with the source, the source wins and the docs are a bug.


Table of Contents


What's Included

Shell: ZSH with Zinit plugin management, Powerlevel10k prompt, turbo-mode lazy loading, fzf-tab completion, Atuin history.

Editor: Neovim (NvChad v2.5 base) with LSP, Telescope, FZF-Lua, Harpoon.

Git: delta pager, difftastic structural diffs, nvimdiff mergetool, ~30 aliases, per-account identity switching.

Terminals: Ghostty (with custom cursor shaders) and Kitty.

CLI tools: fd, fzf, ripgrep, bat, lsd, eza, zoxide, broot, lazygit, btop, zellij, atuin, direnv, hyperfine, gh.

macOS extras: LinearMouse, dark-mode/dock/audio toggle scripts.

Package lists are declared in Brewfile (macOS) and Aptfile (Ubuntu/Debian). See Platform Notes — the two lists are not at parity.


Before You Start

Requirements

RequirementNotes
macOS or Ubuntu/DebianOther Linux distros are not handled by the bootstrap scripts
git and curlPre-installed on macOS; installed by bootstrap on Ubuntu
sudo accessNeeded for apt, for chsh, and to append to /etc/shells
A Nerd FontInstalled automatically on macOS; manual on Linux (see below)

chezmoi 2.40.0 or newer is required — this is enforced by .chezmoiversion. The bootstrap one-liner installs a current chezmoi for you.

Fork first (recommended)

This is a personal dotfiles repo, and some parts are wired to the owner's identity: dot_gitconfig.tmpl routes commit identity through gh-abkolan / gh-abhilast SSH host aliases, and .zshenv hardcodes a 1Password SSH agent socket. You can install it as-is and it will work, but you will be prompted for email addresses for two GitHub accounts that aren't yours.

If you want this as a starting point for your own setup:

# Fork on GitHub, then:
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply <your-github-username>

Then read Git Configuration and adjust the identity rules.


Install

macOS

# Fresh Mac — installs chezmoi, then Homebrew, then everything else:
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply abkolan
# If you already have Homebrew:
brew install chezmoi && chezmoi init --apply abkolan

Ubuntu / Debian

sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply abkolan

Read the Ubuntu caveats before you rely on this — the apt package list is much smaller than the Homebrew one.

Where the source lives

chezmoi init clones this repo to chezmoi's source directory, which by default is:

~/.local/share/chezmoi

That is the directory you edit. chezmoi apply copies rendered files from there into $HOME. Use chezmoi cd to get there. If you want it somewhere else, pass --source ~/path/of/your/choice to chezmoi init.

Preview before applying

If you'd rather look before you leap:

chezmoi init abkolan # clone only, don't apply
chezmoi diff # show exactly what would change in $HOME
chezmoi apply # commit to it

The Setup Prompts

On first init, chezmoi asks five questions (defined in .chezmoi.toml.tmpl). Answers are saved to ~/.config/chezmoi/chezmoi.toml and never committed.

PromptUsed forIf you don't care
Git email addressDefault user.email in ~/.gitconfigYour everyday email
Git user nameDefault user.name in ~/.gitconfigYour name
Workspace path (default: ~/repos)Sets $WORKSPACE, which the wt worktree helper and pj project switcher use as their rootEnter ~/repos
Git email for the abkolan GitHub accountFills ~/.gitconfig-abkolan, used for repos whose remote matches the gh-abkolan SSH alias or plain git@github.comRepeat your main email
Git email for the abhilast GitHub accountFills ~/.gitconfig-abhilast, used for repos whose remote matches the gh-abhilast SSH aliasRepeat your main email

The last two exist for multi-account Git identity switching. There is no default — chezmoi will wait for input, so a fully unattended install needs the values passed up front:

chezmoi init --apply abkolan \
--promptString email=you@example.com \
--promptString name="Your Name" \
--promptString workspacePath=~/repos \
--promptString abkolanEmail=you@example.com \
--promptString abhilastEmail=you@example.com

To change an answer later, edit ~/.config/chezmoi/chezmoi.toml and run chezmoi apply.


What Bootstrap Actually Does

chezmoi runs the run_ scripts at the repo root in filename order:

OrderScriptmacOSLinux
1run_once_before_01-install-packages.sh.tmplInstalls Homebrew if missingapt-get install curl git zsh build-essential
2run_onchange_before_02-install-packages.sh.tmplbrew bundle from Brewfileapt-get install from Aptfile, plus snap install zellij if snap exists
3(config files applied here)
4run_once_after_10-neovim-plugins.shnvim --headless "+Lazy! sync" +qasame
5run_once_after_11-set-default-shell.sh.tmplchsh to Homebrew zshchsh to system zsh

Notes on behaviour that surprises people:

  • Step 2 is run_onchange, keyed on a hash of Brewfile/Aptfile. Edit either file and the next chezmoi apply re-runs package installation automatically.
  • Steps 4 and 5 never fail the install. If Neovim isn't on PATH, or chsh needs an interactive password it can't get, they print a WARNING: with the exact command to run by hand and exit 0. Read the bootstrap output.
  • Step 5 may prompt for your password twice: once for sudo tee -a /etc/shells, once for chsh.
  • Zinit is not installed by these scripts. ~/.zshrc clones it on your first interactive shell, then downloads plugins. Your first new terminal will be slow and print install messages. That is expected; the second one is fast.

After bootstrap finishes, open a new terminal (or exec zsh) so the new shell config loads.


Verify Your Install

chezmoi doctor # environment health check
chezmoi verify # every managed file matches the source
chezmoi diff # should print nothing if there's no drift
zsh -n ~/.zshenv ~/.zshrc # syntax-check shell config
hyperfine 'zsh -i -c exit'# startup benchmark (target: <200ms)

chezmoi doctor reporting warnings about optional tools is normal. Errors are not.


Daily Workflow

chezmoi edit ~/.zshrc # edit the source file behind ~/.zshrc
chezmoi diff # preview what would change
chezmoi apply # apply changes to $HOME
chezmoi cd# cd into the source directory
chezmoi update # git pull + apply (sync a second machine)
chezmoi managed # list every file chezmoi controls

Edit files in the chezmoi source directory, never in $HOMEchezmoi apply overwrites $HOME copies. ~/.zshenv even carries a comment saying so.

To pull your changes back into git:

chezmoi cd
git add .&& git commit -m "feat: ..."&& git push

Shell Helpers

Defined in dot_zsh_aliases and dot_zsh_functions:

AliasRunsPurpose
pjprojectFuzzy project switcher — fd+fzf over git repos in $WORKSPACE, ~/.local/share/chezmoi, ~/Projects
wtn <branch>wtnewCreate a git worktree for <branch> from the current repo
wtlwtlsList and jump between worktrees
wtr <target>wtrmRemove a worktree
prnprnowPush current branch and open a PR via gh

dot_zsh_aliases and dot_zsh_functions hold many more. The full annotated reference — aliases, functions, keybindings, completions, load order, performance — is in ZSH.md.


Scripts

Helper scripts live in scripts/.

These are not copied into $HOME..chezmoiignore excludes scripts/, and instead ~/.zshenv appends chezmoi's source directory to PATH:

[[ -d"{{ .chezmoi.sourceDir }}/scripts" ]] && PATH="$PATH:{{ .chezmoi.sourceDir }}/scripts"

The path is baked in at chezmoi apply time. If you relocate the chezmoi source directory, re-run chezmoi apply to refresh it.

ScriptPurpose
wtGit worktree manager (see below)
benchmark.shShell startup benchmark (hyperfine, falls back to /usr/bin/time)
profile-zsh-startup.shDetailed zsh startup profiling via zprof
ghostty-theme-switcher.shList/apply Ghostty themes by rewriting ~/.config/ghostty/config
mac/*.shmacOS toggles: dark mode, dock position/visibility, audio source

Git worktree helper (wt)

wt new <repo><name> [--base <remote-branch>] [--branch <branch-name>]
wt ls <repo>
wt rm <repo><name>
wt prune <repo>
wt path <repo><name>
wt help

Layout and defaults, straight from scripts/wt:

  • Root directory: $WT_ROOT, falling back to $WORKSPACE, falling back to ~/repos. $WORKSPACE comes from the workspacePath setup prompt.
  • Main repo checkout is expected at $ROOT/<repo>.
  • New worktrees are created at $ROOT/<repo>-wt-<name>.
  • Default base branch: $WT_REMOTE_BASE, defaulting to origin/main.
# With WORKSPACE=~/repos, this creates ~/repos/myapp-wt-hero-fix from origin/main
wt new myapp hero-fix

Git Configuration

dot_gitconfig.tmpl renders to ~/.gitconfig. It fills user.email / user.name from your setup prompts and picks a delta light/dark theme by reading macOS AppleInterfaceStyle at apply time.

Per-account identity

Rather than switching identities by directory, this config keys off the remote URL:

Remote URL matchesIdentity file
gh-abkolan:*/**~/.gitconfig-abkolan
git@github.com:*/** (the default)~/.gitconfig-abkolan
gh-abhilast:*/**~/.gitconfig-abhilast

gh-abkolan and gh-abhilast are SSH host aliases, which must be defined in your ~/.ssh/config. That file is not managed by this repo, so the aliases won't exist on a fresh machine until you add them:

Hostgh-abkolanHostNamegithub.comUsergitIdentityFile~/.ssh/id_abkolanHostgh-abhilastHostNamegithub.comUsergitIdentityFile~/.ssh/id_abhilast

Then clone with git clone gh-abhilast:org/repo.git to get that identity. Without the SSH config, plain git@github.com remotes still work and fall through to the abkolan identity.

If you forked this repo, replace these three includeIf blocks in dot_gitconfig.tmpl with your own accounts, and update the matching prompts in .chezmoi.toml.tmpl.

Aliases

AliasCommandPurpose
ststatus -sbShort status
lllog --oneline --graph --decorate -15Compact log
lglog --graph --pretty=<format>Decorated log with author/relative date
co / brcheckout / branchSwitch branches / branch management
cm / camcommit -m / commit -amQuick commit / commit all
unstage / lastreset HEAD -- / log -1 HEADUnstage / show last commit
d / dcdiff / diff --cachedView changes (through delta)
dt / dtcdifftool / difftool --cachedStructural diff (through difftastic)
s / sp / slstash / stash pop / stash listStash shortcuts
f / fofetch / fetch originFetch
p / plpush / pullPush / pull
rb / rbi / rbc / rbarebase variantsRebase, interactive, continue, abort
brd / brDbranch -d / branch -DDelete branch, safe / forced
cleanupmerged-branch cleanupDelete branches merged into main/master/develop
findlog --grepSearch commit messages

Diff and merge tools

  • delta — pager for all git output. Side-by-side, line numbers, navigate on. Light/dark chosen at apply time from macOS appearance; always dark on Linux. Re-run chezmoi apply after switching your system theme.
  • difftastic — structural, AST-aware diff via git dt / git dtc.
  • nvimdiff — 3-way mergetool for conflicts, with diff3 conflict style.

Other defaults worth knowing: pull.rebase = true, rebase.autoStash = true, init.defaultBranch = main, diff.algorithm = histogram.


Per-Tool Documentation

ToolSourceDocs
ZSHdot_zshrc, dot_zsh_*ZSH.md — aliases, functions, load order, performance
Neovimdot_config/nvim/README — keybindings, plugins, LSP
Ghosttydot_config/ghostty/README — themes, shaders, splits
Kittydot_config/kitty/README — shortcuts, themes, diff
Gitdot_gitconfig.tmplabove — aliases, delta, difftastic
Atuindot_config/atuin/README — shell history sync
Brootdot_config/broot/README — tree navigation, verbs
Btopdot_config/btop/README — resource monitoring
Lazygitdot_config/lazygit/README — git TUI, diff pipeline
Zellijdot_config/zellij/README — multiplexer, keybindings
LSDdot_config/lsd/README — modern ls
LinearMousedot_config/linearmouse/README — pointer/scroll tuning
Scriptsscripts/README — utility scripts

Per-tool READMEs are listed in .chezmoiignore, so they stay in the repo and are never copied into ~/.config/.


Platform Notes and Known Limitations

These are real constraints in the current source, not aspirations.

macOS assumes Apple Silicon

dot_zshenv.tmpl statically exports HOMEBREW_PREFIX=/opt/homebrew and friends rather than calling brew shellenv, deliberately, to save ~15ms per shell. On an Intel Mac, where Homebrew lives in /usr/local, those exported variables will be wrong. Homebrew binaries still resolve (/usr/local/bin is on the default PATH), but anything reading $HOMEBREW_PREFIX breaks. Intel users should edit the prefix block in dot_zshenv.tmpl.

The bootstrap and chsh scripts do handle both prefixes correctly.

Ubuntu / Debian is a subset

Aptfile installs only: direnv zsh fd-find fzf ripgrep bat eza tree neovim git jq curl wget btop, plus zellij via snap.

Relative to macOS, an apt install does not get you: lsd, zoxide, broot, lazygit, git-delta, difftastic, atuin, gh, hyperfine, chezmoi (from the package manager), fnm, bun, or any of the LSP servers. Configs for those tools are still deployed, and the shell is written to degrade gracefully — .zshrc guards each integration with command -v — but aliases that call a missing binary will fail until you install it.

Also note on Debian/Ubuntu: fd-find installs the binary as fdfind, and bat as batcat. Anything expecting fd or bat needs symlinks:

mkdir -p ~/.local/bin
ln -sf "$(which fdfind)"~/.local/bin/fd
ln -sf "$(which batcat)"~/.local/bin/bat

This matters because FZF_DEFAULT_COMMAND in .zshrc calls fd directly.

Nerd Font installation is macOS-only (cask "font-jetbrains-mono-nerd-font"). On Linux, install a Nerd Font manually or the Powerlevel10k prompt and lsd icons will render as tofu boxes.

Other machine-specific bits

  • dot_zshenv.tmpl sets SSH_AUTH_SOCK=~/.1password/agent.sock unconditionally. Without 1Password's SSH agent, unset or override this or SSH auth will fail.
  • dot_gitconfig.tmpl points credential.helper at /usr/local/share/gcm-core/git-credential-manager, which is the Intel-Homebrew path.
  • The macOS scripts under scripts/mac/ use defaults/osascript and are macOS-only.

Troubleshooting

Prompt shows boxes/garbage instead of icons. You need a Nerd Font installed and selected in your terminal. macOS gets JetBrains Mono Nerd Font from the Brewfile; set it as your terminal font. On Linux, install one manually.

First shell is slow and prints install messages. Zinit is bootstrapping. Let it finish; subsequent shells are fast. Benchmark with hyperfine 'zsh -i -c exit'.

chezmoi apply says my shell config is modified. You probably edited ~/.zshrc directly. Move the change into the source (chezmoi edit ~/.zshrc), or capture it with chezmoi add ~/.zshrc. Use chezmoi diff to see the discrepancy first.

Default shell didn't change. Step 5 exits 0 on failure by design. Run it by hand:

echo /opt/homebrew/bin/zsh | sudo tee -a /etc/shells
chsh -s /opt/homebrew/bin/zsh

(Use $(which zsh) on Linux.) Log out and back in for it to take effect.

Neovim plugins are missing. The sync step is best-effort. Run it manually:

nvim --headless "+Lazy! sync" +qa

fd: command not found on Ubuntu. See the fdfind/batcat symlinks above.

Git is committing as the wrong identity. Check git config user.email inside the repo. Identity is chosen by remote URL — see Per-account identity.

Starting over.chezmoi doesn't uninstall. To reset the shell layer specifically:

rm -rf ~/.local/share/zinit ~/.cache/zinit ~/.zcompdump

Then open a new terminal to let Zinit reinstall.


Adding New Tools

chezmoi cd# 1. Declare the package# - macOS: add a brew/cask line to Brewfile# - Ubuntu: add the apt package name to Aptfile# 2. Add its config with chezmoi naming# dot_foo -> ~/.foo# dot_config/foo/ -> ~/.config/foo/# *.tmpl suffix -> rendered as a Go template# executable_bar -> ~/bar with the executable bit set
chezmoi add ~/.config/foo/config.toml
# 3. Apply (this also re-runs package install, since Brewfile/Aptfile changed)
chezmoi apply
# 4. Verify, then commit
chezmoi verify
git add .&& git commit -m "feat: add foo"&& git push

Conventions for contributions are in AGENTS.md.

About

🔧 .files — personal dotfiles for my development environment, managed with chezmoi

Resources

Stars

3 stars

Watchers

1 watching

Forks

Contributors

Languages