Skip to content

Instantly share code, notes, and snippets.

@sanyer
Created June 13, 2026 18:20
Show Gist options
  • Select an option

  • Save sanyer/a930c734795269e4238dfa6ac644b212 to your computer and use it in GitHub Desktop.

Select an option

Save sanyer/a930c734795269e4238dfa6ac644b212 to your computer and use it in GitHub Desktop.
zsh setup bootstrap

zsh Setup Instructions

Set up my standard zsh configuration on this machine. Follow these steps exactly.

What this installs

  • powerlevel10k — prompt theme with instant prompt
  • zsh-syntax-highlighting — colors commands as you type
  • zsh-autosuggestions — inline history suggestions
  • zsh-history-substring-search — Up/Down arrows search history by what you've typed
  • fzf — Ctrl+R fuzzy history, Ctrl+T fuzzy file picker, Alt+C fuzzy cd

Steps

1. Backup existing .zshrc

Copy ~/.zshrc to ~/.zshrc.backup (with a timestamp suffix if you want to be safe).

2. Clone plugins

Clone each repo with --depth=1 into ~/.zsh/plugins/<name>. If the directory already exists, do git pull --ff-only instead.

Plugin name GitHub repo
powerlevel10k romkatv/powerlevel10k
zsh-syntax-highlighting zsh-users/zsh-syntax-highlighting
zsh-autosuggestions zsh-users/zsh-autosuggestions
zsh-history-substring-search zsh-users/zsh-history-substring-search

3. Install fzf

  • If ~/.local/bin/mise exists: mise use -g fzf
  • Else if brew exists: brew install fzf
  • Otherwise: print a note and skip — user will handle it

4. Write ~/.zshrc

Write the file below verbatim. Do not add, remove, or reorder sections — the ordering is load-order-sensitive (instant prompt must be first; history-substring-search must be sourced after syntax-highlighting; fzf must be sourced after PATH so the binary is findable).

# Enable Powerlevel10k instant prompt. Must stay at the very top.
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
  source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi

# ─── Plugin directory ─────────────────────────────────────────────────────────
ZSH_PLUGIN_DIR="${HOME}/.zsh/plugins"

# ─── History ──────────────────────────────────────────────────────────────────
HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.zsh_history
setopt histignorealldups sharehistory hist_ignore_space

# ─── Keybindings ──────────────────────────────────────────────────────────────
bindkey -e

# ─── Completion ───────────────────────────────────────────────────────────────
autoload -Uz compinit
compinit

zstyle ':completion:*' auto-description 'specify: %d'
zstyle ':completion:*' completer _expand _complete _correct _approximate
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' menu select=2
eval "$(dircolors -b)"
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-colors ''
zstyle ':completion:*' list-prompt '%SAt %p: Hit TAB for more, or the character to insert%s'
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s'
zstyle ':completion:*' use-compctl false
zstyle ':completion:*' verbose true
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'

# ─── PATH ─────────────────────────────────────────────────────────────────────
export PATH="${HOME}/.local/bin:${PATH}"

# ─── mise ─────────────────────────────────────────────────────────────────────
eval "$("${HOME}/.local/bin/mise" activate zsh)"

# ─── Plugins ──────────────────────────────────────────────────────────────────
source "${ZSH_PLUGIN_DIR}/zsh-autosuggestions/zsh-autosuggestions.zsh"
source "${ZSH_PLUGIN_DIR}/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
# history-substring-search must come after syntax-highlighting
source "${ZSH_PLUGIN_DIR}/zsh-history-substring-search/zsh-history-substring-search.zsh"
source "${ZSH_PLUGIN_DIR}/powerlevel10k/powerlevel10k.zsh-theme"

# ─── History substring search — Up/Down arrows ────────────────────────────────
bindkey "${terminfo[kcuu1]}" history-substring-search-up
bindkey "${terminfo[kcud1]}" history-substring-search-down
# fallback escape sequences for terminals that don't populate terminfo
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down

# ─── fzf ──────────────────────────────────────────────────────────────────────
# Ctrl+R: fuzzy history  Ctrl+T: fuzzy file  Alt+C: fuzzy cd
source <(fzf --zsh)

# ─── Powerlevel10k config ─────────────────────────────────────────────────────
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh

# ─── Auto-update plugins (weekly, silent) ─────────────────────────────────────
_zsh_autoupdate_plugins() {
  local stamp="${HOME}/.zsh/.last_plugin_update"
  local interval=$(( 7 * 24 * 3600 ))
  local now; now=$(date +%s)
  local last=0
  [[ -f "$stamp" ]] && last=$(< "$stamp")
  if (( now - last > interval )); then
    printf '%s' "$now" > "$stamp"
    local dir
    for dir in "${ZSH_PLUGIN_DIR}"/*/; do
      [[ -d "${dir}.git" ]] && git -C "$dir" pull --quiet --ff-only &>/dev/null &
    done
    disown 2>/dev/null
  fi
}
_zsh_autoupdate_plugins

5. Tell the user what to do next

After writing the files, tell the user:

  • Open a new terminal
  • Run p10k configure to generate ~/.p10k.zsh (the prompt style wizard)
  • If ~/.p10k.zsh already exists (copied from another machine), it will be sourced automatically

Notes

  • The mise activation line assumes ~/.local/bin/mise — the standard install path for mise on Linux. If mise is not installed on this machine, remove that line and note it.
  • The source <(fzf --zsh) line requires fzf to be on PATH. With mise, it's available via shims after activation.
  • Do not install oh-my-zsh, zsh4humans, or any plugin manager — this setup manages plugins directly with git.
  • Do not use antigen, zplug, or zinit — direct sourcing is intentional (lighter, no framework overhead).
#!/usr/bin/env bash
# zsh setup bootstrap — https://zsh.zhuzha.tech/install
# Usage: sh -c "$(wget -O- https://zsh.zhuzha.tech/install)"
# sh -c "$(curl -fsSL https://zsh.zhuzha.tech/install)"
set -euo pipefail
PLUGIN_DIR="${HOME}/.zsh/plugins"
ZSHRC="${HOME}/.zshrc"
echo "==> zsh setup bootstrap"
# ── Backup existing .zshrc ────────────────────────────────────────────────────
if [[ -f "$ZSHRC" ]]; then
bak="${ZSHRC}.backup.$(date +%Y%m%d%H%M%S)"
cp "$ZSHRC" "$bak"
echo " backed up .zshrc → $bak"
fi
# ── Clone / update plugins ────────────────────────────────────────────────────
mkdir -p "$PLUGIN_DIR"
_clone() {
local repo="$1" dir="${PLUGIN_DIR}/$2"
if [[ -d "${dir}/.git" ]]; then
git -C "$dir" pull --quiet --ff-only
echo " updated $2"
else
git clone --depth=1 "https://github.com/${repo}.git" "$dir"
echo " cloned $2"
fi
}
_clone romkatv/powerlevel10k powerlevel10k
_clone zsh-users/zsh-syntax-highlighting zsh-syntax-highlighting
_clone zsh-users/zsh-autosuggestions zsh-autosuggestions
_clone zsh-users/zsh-history-substring-search zsh-history-substring-search
# ── fzf ───────────────────────────────────────────────────────────────────────
MISE_BIN="${HOME}/.local/bin/mise"
if [[ -x "$MISE_BIN" ]]; then
"$MISE_BIN" use -g fzf
echo " installed fzf via mise"
elif command -v brew &>/dev/null; then
brew install fzf
echo " installed fzf via brew"
else
echo " skipped fzf (no mise or brew found) — install mise, then: mise use -g fzf"
fi
# ── Write .zshrc ──────────────────────────────────────────────────────────────
cat > "$ZSHRC" << 'ZSHRC_EOF'
# Enable Powerlevel10k instant prompt. Must stay at the very top.
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
# ─── Plugin directory ─────────────────────────────────────────────────────────
ZSH_PLUGIN_DIR="${HOME}/.zsh/plugins"
# ─── History ──────────────────────────────────────────────────────────────────
HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.zsh_history
setopt histignorealldups sharehistory hist_ignore_space
# ─── Keybindings ──────────────────────────────────────────────────────────────
bindkey -e
# ─── Completion ───────────────────────────────────────────────────────────────
autoload -Uz compinit
compinit
zstyle ':completion:*' auto-description 'specify: %d'
zstyle ':completion:*' completer _expand _complete _correct _approximate
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' menu select=2
eval "$(dircolors -b)"
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-colors ''
zstyle ':completion:*' list-prompt '%SAt %p: Hit TAB for more, or the character to insert%s'
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' select-prompt '%SScrolling active: current selection at %p%s'
zstyle ':completion:*' use-compctl false
zstyle ':completion:*' verbose true
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'
# ─── PATH ─────────────────────────────────────────────────────────────────────
export PATH="${HOME}/.local/bin:${PATH}"
# ─── mise ─────────────────────────────────────────────────────────────────────
eval "$("${HOME}/.local/bin/mise" activate zsh)"
# ─── Plugins ──────────────────────────────────────────────────────────────────
source "${ZSH_PLUGIN_DIR}/zsh-autosuggestions/zsh-autosuggestions.zsh"
source "${ZSH_PLUGIN_DIR}/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
# history-substring-search must come after syntax-highlighting
source "${ZSH_PLUGIN_DIR}/zsh-history-substring-search/zsh-history-substring-search.zsh"
source "${ZSH_PLUGIN_DIR}/powerlevel10k/powerlevel10k.zsh-theme"
# ─── History substring search — Up/Down arrows ────────────────────────────────
bindkey "${terminfo[kcuu1]}" history-substring-search-up
bindkey "${terminfo[kcud1]}" history-substring-search-down
# fallback escape sequences for terminals that don't populate terminfo
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
# ─── fzf ──────────────────────────────────────────────────────────────────────
# Ctrl+R: fuzzy history Ctrl+T: fuzzy file Alt+C: fuzzy cd
source <(fzf --zsh)
# ─── Powerlevel10k config ─────────────────────────────────────────────────────
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
# ─── Auto-update plugins (weekly, silent) ─────────────────────────────────────
_zsh_autoupdate_plugins() {
local stamp="${HOME}/.zsh/.last_plugin_update"
local interval=$(( 7 * 24 * 3600 ))
local now; now=$(date +%s)
local last=0
[[ -f "$stamp" ]] && last=$(< "$stamp")
if (( now - last > interval )); then
printf '%s' "$now" > "$stamp"
local dir
for dir in "${ZSH_PLUGIN_DIR}"/*/; do
[[ -d "${dir}.git" ]] && git -C "$dir" pull --quiet --ff-only &>/dev/null &
done
disown 2>/dev/null
fi
}
_zsh_autoupdate_plugins
ZSHRC_EOF
echo " wrote ~/.zshrc"
echo ""
echo "==> Done. Open a new terminal, then run: p10k configure"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment