Files
scripts/zsh/README.md
T
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

132 lines
7.8 KiB
Markdown

# 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`](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`](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.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.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`:
```bash
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:
```bash
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: `batcat``bat`, `fdfind``fd` (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`):
- **zoxide** — `z <substring>` jumps to frecency-ranked dirs; `zi` opens the fzf picker. `cd` still works.
- **bat** — `alias cat='bat'`, `BAT_THEME=Monokai Extended`, and `MANPAGER` set so `man` pages are colored and paginated by bat.
- **delta** — `GIT_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) |
## Eza Hyperlinks — Implementation Note
`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
```bash
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
```