- Shell 100%
The Oh My Zsh installer's template .zshrc sources oh-my-zsh.sh itself, and the managed block sources it again. The second compinit runs with a different ZSH_CUSTOM/fpath, which invalidates .zcompdump on every start - ~6 s per shell on a Raspberry Pi. setup_zsh_custom now comments out any source line above the managed block, as already done by hand on the Mac. |
||
|---|---|---|
| zsh_custom | ||
| .dircolors-256dark | ||
| .gitconfig | ||
| .gitignore | ||
| .lessfilter | ||
| .vimrc | ||
| atuin-config.toml | ||
| bat-config | ||
| README.md | ||
| ripgreprc | ||
| setup.sh | ||
| ssh_config | ||
| starship.toml | ||
| tmux.conf | ||
dotfiles
Personal dotfiles, used on macOS and Debian/Ubuntu.
Quick start
git clone <this repo> ~/git/dotfiles
~/git/dotfiles/setup.sh
exec zsh -l
setup.sh installs the prerequisites below, creates the symlinks, and
wires up $ZSH_CUSTOM in ~/.zshrc. It's idempotent - safe to re-run,
and backs up (.bak.<timestamp>) rather than overwrites anything
already in place at a symlink target. The steps below are what it
does and why, for reference or if you'd rather run them by hand.
1. Install prerequisites
| Tool | macOS | Debian/Ubuntu | Why |
|---|---|---|---|
| Homebrew | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
— | Used to install most of the tools below. If it's not available and there's no sudo either (e.g. a locked-down work Mac), setup.sh bootstraps pixi instead - installs entirely under $HOME, no admin rights needed |
| Oh My Zsh | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" |
same | Framework that loads everything in zsh_custom/ |
| nvm | brew install nvm |
see nvm's install instructions for the current install script URL | Node version manager. zsh_custom/08-nvm.zsh auto-installs and pins the global default version (NVM_DEFAULT_NODE_VERSION) on first shell start |
| uv | curl -LsSf https://astral.sh/uv/install.sh | sh |
same | Python version/package manager. zsh_custom/12-uv.zsh auto-installs and pins the global default version (UV_DEFAULT_PYTHON_VERSION) on first shell start, and blocks bare pip/pip3 outside a venv |
| pnpm | corepack enable pnpm (bundled with Node via nvm above) |
same | JS package manager. zsh_custom/10-pnpm.zsh sets PNPM_HOME; zsh_custom/03-command_not_found.zsh falls back to pnpm bin for unresolved commands (also covers nx, since no separate Angular/nx CLI is installed) |
| Neovim | brew install neovim |
sudo apt install neovim |
zsh_custom/11-settings.zsh aliases vim/vi to nvim and sets it as $EDITOR/$VISUAL when present, falls back to vim otherwise |
| iTerm2 | download + install, then Preferences → General → Install Shell Integration | — | Picked up automatically by zsh_custom/05-iterm2.zsh if ~/.iterm2_shell_integration.zsh exists |
| Meslo Nerd Font (macOS only) | brew install --cask font-meslo-lg-nerd-font |
— | Provides the icon glyphs eza --icons (zsh_custom/02-cli.zsh) and the starship prompt (starship.toml) need - without it they render as tofu boxes. No Homebrew? setup.sh downloads the same release straight into ~/Library/Fonts instead. Pick "MesloLGS Nerd Font Mono" in your terminal's font setting after install |
| OrbStack (personal machines) | installed by setup.sh |
— | zsh_custom/04-containers.zsh wires up OrbStack's completions, bundled at a fixed app path |
| Podman (work machine only) | not installed by setup.sh - install by hand |
Not installed by setup.sh - only needed on the work machine, install by hand there. On macOS, podman's own docs recommend the signed installer from its GitHub releases over Homebrew, since running containers needs a VM backed by admin-gated permissions either way. zsh_custom/04-containers.zsh wires up its completions (podman completion zsh) if present |
|
bat |
brew install bat |
sudo apt install bat |
Used by .lessfilter for syntax-highlighted less output. Direct invocations pick up theme/style from bat-config, symlinked to ~/.config/bat/config |
lesspipe |
brew install lesspipe |
already bundled with less |
Invokes .lessfilter. zsh_custom/06-less.zsh sets $LESSOPEN to wire it up if found on $PATH (checks both lesspipe.sh and Debian's lesspipe binary name) |
| tmux | brew install tmux |
sudo apt install tmux |
Terminal multiplexer, config in tmux.conf |
eza |
brew install eza |
sudo apt install eza |
Modern ls. zsh_custom/02-cli.zsh points ll/la at it if present, falls back to plain ls otherwise |
ripgrep |
brew install ripgrep |
sudo apt install ripgrep |
Modern recursive grep (rg). Not aliased over grep - flag semantics differ enough (e.g. -r means "replace" in rg) that it'd be a footgun. Default flags (--smart-case, search hidden files, skip .git/) come from ripgreprc, pointed to via $RIPGREP_CONFIG_PATH in zsh_custom/02-cli.zsh |
fd |
brew install fd |
sudo apt install fd-find |
Modern find. Debian/Ubuntu installs it as fdfind; zsh_custom/02-cli.zsh aliases fd to it if the plain fd binary isn't present |
delta |
brew install git-delta |
sudo apt install git-delta |
Syntax-highlighting pager for git diff/show/blame, wired up as core.pager in .gitconfig |
dust/duf |
brew install dust duf |
sudo apt install du-dust duf |
Modern du (dust) and df (duf) |
zoxide |
brew install zoxide |
sudo apt install zoxide |
Modern cd with frecency-based fuzzy jumping. zsh_custom/02-cli.zsh initializes it as a transparent cd replacement (--cmd cd) if present |
tealdeer |
brew install tealdeer |
sudo apt install tealdeer |
Fast tldr client - example-driven cheat sheets instead of full man pages |
btop |
brew install btop |
sudo apt install btop |
Modern htop - graphs, mouse support, per-core CPU. Not aliased over anything, just run btop directly |
starship |
brew install starship |
sudo apt install starship |
Cross-shell prompt. Owns $PROMPT instead of oh-my-zsh - zsh_custom/11-settings.zsh sets ZSH_THEME="" and initializes it; config lives in starship.toml, symlinked to ~/.config/starship.toml |
atuin |
brew install atuin |
upstream installer into ~/.atuin/bin (setup.sh does this and removes the apt package - Debian's is too old to sync with newer clients) |
Shell history backed by a searchable SQLite database, synced via sync_address. zsh_custom/02-cli.zsh initializes it, binding ctrl-r to its fuzzy search UI (up-arrow keeps the default history behavior); config (compact inline UI, global search scope, no update check, sync server) in atuin-config.toml, symlinked to ~/.config/atuin/config.toml. When run from a terminal, setup.sh also runs atuin login (needs the key from atuin key on another machine), imports ~/.zsh_history once and syncs |
sd |
brew install sd |
sudo apt install sd |
Modern sed for find & replace (e.g. sd 'foo' 'bar' file.txt). Not aliased over sed - same reasoning as ripgrep/grep, syntax differs enough to be a footgun |
| GNU coreutils (macOS only) | brew install coreutils |
already the default on Linux | macOS ships BSD coreutils, which differ from GNU's (e.g. date -d, sed -i without a backup-suffix arg). Installed g-prefixed (gls, gsed, gdate, ...) same as Homebrew's own default, so nothing on $PATH gets silently shadowed - use explicitly by name when a script needs GNU-specific behavior. No Homebrew? setup.sh installs conda-forge's gnu-coreutils, not its plain coreutils package - that one installs unprefixed and would shadow the BSD tools |
watch (macOS only) |
brew install watch |
already the default on Linux (procps) |
Periodically re-runs a command, showing live output - macOS doesn't ship it |
Everything above is optional except Oh My Zsh — the rest is auto-detected/guarded and simply no-ops if missing.
2. Clone the repo
git clone <this repo> ~/git/dotfiles
(any path works, just update the symlink targets below accordingly)
3. Symlink
| Repo path | Symlink target | Command |
|---|---|---|
.vimrc |
~/.vimrc |
ln -s ~/git/dotfiles/.vimrc ~/.vimrc |
.gitconfig |
~/.gitconfig |
ln -s ~/git/dotfiles/.gitconfig ~/.gitconfig |
.lessfilter |
~/.lessfilter |
ln -s ~/git/dotfiles/.lessfilter ~/.lessfilter |
tmux.conf |
~/.tmux.conf |
ln -s ~/git/dotfiles/tmux.conf ~/.tmux.conf |
starship.toml |
~/.config/starship.toml |
mkdir -p ~/.config && ln -s ~/git/dotfiles/starship.toml ~/.config/starship.toml |
bat-config |
~/.config/bat/config |
mkdir -p ~/.config/bat && ln -s ~/git/dotfiles/bat-config ~/.config/bat/config |
atuin-config.toml |
~/.config/atuin/config.toml |
mkdir -p ~/.config/atuin && ln -s ~/git/dotfiles/atuin-config.toml ~/.config/atuin/config.toml |
Not directly symlinked:
ssh_config— your real~/.ssh/configalready has its ownHostblocks and anInclude config.d/*line. Instead, drop this file into that include path:mkdir -p ~/.ssh/config.d && ln -s ~/git/dotfiles/ssh_config ~/.ssh/config.d/dotfiles..dircolors-256dark— not symlinked anywhere;zsh_custom/07-linux.zshsources it directly from the repo via a relative path on Linux.ripgreprc— not symlinked either; ripgrep doesn't look for a config file by path, only via$RIPGREP_CONFIG_PATH, whichzsh_custom/02-cli.zshpoints straight at this file.zsh_custom/— not symlinked either; see step 4.
4. Wire up zsh_custom
Oh My Zsh loads every *.zsh file in $ZSH_CUSTOM automatically, so instead of symlinking individual files, point $ZSH_CUSTOM at this repo. Your local ~/.zshrc (not tracked in this repo) needs:
export ZSH="$HOME/.oh-my-zsh"
ZSH_CUSTOM="$HOME/git/dotfiles/zsh_custom"
source $ZSH/oh-my-zsh.sh
ZSH_THEME and Oh My Zsh's update mode are set from within zsh_custom/11-settings.zsh, so they don't need to be set here. ZSH_THEME is set to "" there - the prompt itself comes from starship (also initialized in that file), not an oh-my-zsh theme.
5. Restart your shell
exec zsh -l
First start after a fresh clone will be slower than usual — nvm.zsh and uv.zsh will detect their pinned Node/Python versions aren't installed yet and install them automatically. Every start after that is fast.