Files
jeremy 7acdb761d6 zsh: detect eza --hyperlink capability, fix 'cannot take a value' on Debian's older eza
Newer eza (clap) treats --hyperlink as taking an optional value, so the
oh-my-zsh plugin's bare --hyperlink eats the next path arg (la / ->
invalid value '/'). The previous fix glued --hyperlink=always to avoid
that. But Debian ships an older eza where --hyperlink is a pure boolean
that rejects a value, so --hyperlink=always errors with 'Flag
--hyperlink cannot take a value'.

Detect the installed eza's behavior via 'eza --help | grep <WHEN>':
- newer eza: use --hyperlink=always (glued, avoids path-eating)
- older eza: use bare --hyperlink (boolean, doesn't eat tokens so the
  original bug doesn't apply)
Apply the chosen form to every eza alias. Both paths keep hyperlinks on
and paths working.
2026-07-29 12:15:06 -04:00

7.8 KiB

Zsh Config

A reproducible, self-configuring Zsh setup managed from a remote git repo. The zshrc auto-updates itself and the Starship prompt config on every interactive shell start, and a versioned bootstrap script installs all missing dependencies across distros. Drop ~/.zshrc in place once and every machine stays in sync automatically.

Design

  • Reproducible — one zshrc drives an identical environment across every machine and distro.
  • Self-configuring — tool integrations are guarded by command -v, so the shell works degraded-but-fine when a tool is absent, and lights up automatically once the bootstrap installs it.
  • Self-updating — on each interactive shell start, zshrc and starship.toml are re-pulled from the repo and applied if changed (downloaded files are validated with zsh -n before replacing anything, so a 502 error page or corrupted content can't break the shell).
  • Network-resilient — all remote calls use --fail + --connect-timeout/--max-time, so an unreachable server silently no-ops instead of hanging the shell.

Files

File Description
zshrc Main config. Path setup, history options, oh-my-zsh + plugins, eza theme, tool integrations, aliases, and the auto-update logic. Sources ~/.zshrc.local at the end for per-machine overrides.
zshrc-bootstrap.zsh One-shot setup script run by zshrc when the bootstrap version changes. Installs all dependencies across distros and clones oh-my-zsh + custom plugins.
starship.toml Starship prompt config — two-line prompt with directory, git status, command duration, Nerd Font symbols, and an error indicator. Synced to ~/.config/starship.toml by the auto-update logic.
tmux.conf Tmux config with GitHub Dark theming, Vim-style pane navigation (h/j/k/l), TPM plugin manager, Nerd Font auto-install, and a built-in cheatsheet (prefix + ?). Installed to ~/.config/tmux/tmux.conf on first shell start.

Getting Started

Point ~/.zshrc at the remote zshrc:

curl -fsSL https://git.jeremymcclure.com/jeremy/scripts/raw/branch/master/zsh/zshrc -o ~/.zshrc && exec zsh

On first start, zshrc bootstraps missing dependencies (oh-my-zsh, plugins, and all CLI tools below) over the next shell starts. To force a re-bootstrap (e.g., after adding new tool installs to the bootstrap script), the bootstrap version is bumped in zshrc and every machine re-runs it automatically — or run manually:

BOOTSTRAP=true exec zsh

Dependencies

All installed automatically by the bootstrap across brew / apt / dnf / pacman / cargo:

Tool Purpose
starship Prompt (configured via starship.toml)
fzf Fuzzy finder — powers fzf-tab completion and zi
eza ls replacement with icons, git status, hyperlinks
bat cat replacement + colored man pages
zoxide Frecency directory jumping (z, zi)
git-delta Pretty git diff/log/show pager
fastfetch System info on shell start
lazydocker TUI for Docker
rsync / tmux / git Core utilities

Debian/Ubuntu renames handled: batcatbat, fdfindfd (symlinked into ~/.local/bin).

Oh-My-Zsh Plugins

git, sudo, extract, eza, history, kitty, docker, docker-compose, archlinux, encode64, universalarchive, zsh-autosuggestions, fast-syntax-highlighting, fzf, fzf-tab, systemd, vscode, rsync, starship.

Tool Integrations

Each is enabled only when the binary is present (command -v):

  • zoxidez <substring> jumps to frecency-ranked dirs; zi opens the fzf picker. cd still works.
  • batalias cat='bat', BAT_THEME=Monokai Extended, and MANPAGER set so man pages are colored and paginated by bat.
  • deltaGIT_PAGER=delta so git diff/log/show render side-by-side with syntax highlighting. (Not aliased to diff — delta is a pager reading diff input on stdin, not a diff a b replacement.)

History & Shell Options

SHARE_HISTORY, HIST_IGNORE_DUPS, HIST_IGNORE_ALL_DUPS, HIST_REDUCE_BLANKS, HIST_FCNTL_LOCK, HIST_VERIFY, EXTENDED_HISTORY, AUTO_CD, AUTO_LIST, INTERACTIVE_COMMENTS, AUTO_PUSHD, PUSHD_IGNORE_DUPS, PUSHD_SILENT.

AUTO_PUSHD maintains an automatic directory stack, so cd -<TAB> and dirs let you navigate recent locations without pushd/popd muscle memory.

Aliases

Alias Action
c clear
q exit
open-ports ss -tulpn | grep LISTEN
nbstat Netbird peer status as a TSV table
yeet yay -Rcs (remove + deps + config on Arch)

Keybindings

Shortcut Action
Ctrl+R fzf history search (via fzf plugin)
Ctrl+T fzf file finder
Alt+C fzf cd
Tab fzf-tab completion menu
Esc twice Toggle sudo prefix (via sudo plugin)

zshrc does not set the oh-my-zsh eza plugin's hyperlink zstyle (the plugin's bare --hyperlink causes issues). Instead, after oh-my-zsh loads, the aliases are rewritten to append the correct hyperlink flag for the installed eza version:

  • Newer eza (help shows --hyperlink [<WHEN>]): the oh-my-zsh plugin's bare --hyperlink is treated by clap as taking the next token as its optional value, so la / fails with invalid value '/'. We append the glued --hyperlink=always form, which can't eat a following path argument.
  • Older eza (e.g. Debian's, where --hyperlink is a pure boolean): --hyperlink=always is rejected with Flag --hyperlink cannot take a value. We detect this via eza --help and fall back to the bare --hyperlink, which is safe here because boolean flags don't consume the next token (the path-eating bug doesn't apply).

Detection runs once at shell start via eza --help | grep '<WHEN>'; the right form is applied to every eza alias. Hyperlinks stay on and paths keep working across eza versions.

Auto-Update Behavior

On every interactive shell start:

  1. zshrc — re-downloaded, validated with zsh -n, and applied via exec zsh only if it differs from ~/.zshrc.
  2. starship.toml — re-downloaded and moved to ~/.config/starship.toml if changed (applied live on the next prompt render; no shell reload needed).
  3. tmux.conf — downloaded once on first shell start if ~/.config/tmux/tmux.conf and ~/.config/byobu/.tmux.conf are both absent.
  4. bootstrap — runs (zshrc-bootstrap.zsh) when ZSHRC_BOOTSTRAP_VERSION in zshrc doesn't match ~/.config/zsh/.bootstrapped.

All downloads use --fail so a 502/HTML error page is never written over a working config, and --connect-timeout/--max-time so an unreachable server never hangs startup.

Per-Machine Overrides

~/.zshrc.local is sourced at the very end of zshrc (created empty if missing). Put machine-specific aliases, env vars, or PATH additions there — they survive repo updates since zshrc doesn't manage that file.

Environment Variables

Variable Default Purpose
ZSHRC_GIT .../scripts/raw/branch/master/zsh/ Base URL for all remote config files
ZSHRC_BOOTSTRAP_VERSION 4 Bump to force a re-bootstrap on all machines
FFENABLED (unset → enabled) Set to false to disable the fastfetch system info on shell start
BAT_THEME Monokai Extended Override before the bat block to use a different bat theme
GIT_PAGER delta (when installed) Override to use a different git pager

Helper Functions

update_zshrc        # Manually re-pull and reload the zshrc
fix_btopbg          # Disable btop theme_background (workaround for transparency)
install_opencode    # Install the opencode CLI
BOOTSTRAP=true exec zsh   # Force a re-bootstrap