Compare commits
6
Commits
eed5879a25
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7acdb761d6
|
||
|
|
18af4de0c0
|
||
|
|
0281b06154
|
||
|
|
9343715696
|
||
|
|
10caebad29
|
||
|
|
c4a804e760
|
+126
-6
@@ -1,12 +1,132 @@
|
||||
# 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
|
||||
|
||||
### `zshrc`
|
||||
Main Zsh configuration file. Sets up Oh My Zsh, plugins (git, sudo, eza, fzf, starship, etc.), history options, aliases, and auto-updates itself from a remote source. Before replacing `~/.zshrc`, the downloaded file is validated with `zsh -n` to ensure it's syntactically valid — this prevents error pages or corrupted content from breaking the shell. Also bootstraps tmux config and runs a fastfetch system info display on interactive shells.
|
||||
| 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. |
|
||||
|
||||
### `zshrc-bootstrap.zsh`
|
||||
One-shot setup script executed by `zshrc`. Installs missing dependencies (starship, lazydocker, fastfetch, eza, fzf, etc.) via the system package manager (brew, apt, dnf, pacman) or cargo, clones Oh My Zsh and custom plugins (zsh-autosuggestions, fast-syntax-highlighting, fzf-tab). Runs only when the bootstrap version changes.
|
||||
## Getting Started
|
||||
|
||||
### `tmux.conf`
|
||||
Tmux configuration with GitHub Dark theming, Nerd Font auto-install, TPM plugin manager, pane/window keybindings (Vim-style navigation with `h/j/k/l`), status bar showing session info, battery, online status, and a built-in cheatsheet (bound to `prefix + ?`).
|
||||
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
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
# Starship prompt configuration - managed by the zshrc repo.
|
||||
# Local edits to ~/.config/starship.toml are overwritten on the next interactive
|
||||
# shell start (reproducible/self-configuring design). Uses Nerd Font symbols
|
||||
# (JetBrainsMono Nerd Font is installed by the tmux config).
|
||||
|
||||
command_timeout = 1000
|
||||
add_newline = true
|
||||
|
||||
# Two-line prompt:
|
||||
# line 1: directory + git status + command duration
|
||||
# line 2: the input arrow (red on error)
|
||||
format = """
|
||||
$directory\
|
||||
$git_branch\
|
||||
$git_status\
|
||||
$cmd_duration\
|
||||
$line_break\
|
||||
$character\
|
||||
"""
|
||||
|
||||
[character]
|
||||
success_symbol = "[➜](bold green)"
|
||||
error_symbol = "[✗](bold red)"
|
||||
vimcmd_symbol = "[V](bold green)"
|
||||
|
||||
[directory]
|
||||
truncation_length = 3
|
||||
truncate_to_repo = true
|
||||
style = "bold blue"
|
||||
read_only = " "
|
||||
read_only_style = "red"
|
||||
repo_root_style = "bold cyan"
|
||||
|
||||
[git_branch]
|
||||
symbol = " "
|
||||
style = "bold purple"
|
||||
format = "[$symbol$branch]($style) "
|
||||
|
||||
[git_status]
|
||||
style = "bold red"
|
||||
conflicted = "=${count}"
|
||||
ahead = "⇡${count}"
|
||||
behind = "⇣${count}"
|
||||
diverged = "⇕↑${ahead_count}↓${behind_count}"
|
||||
untracked = "?${count}"
|
||||
stashed = " *${count}"
|
||||
modified = " !${count}"
|
||||
staged = " +${count}"
|
||||
renamed = " »${count}"
|
||||
deleted = " ✘${count}"
|
||||
format = '([$all_status$ahead_behind]($style) )'
|
||||
|
||||
[cmd_duration]
|
||||
min_time = 2000
|
||||
format = "took [$duration](bold yellow) "
|
||||
|
||||
[status]
|
||||
disabled = false
|
||||
format = '[$symbol]($style)'
|
||||
symbol = "✗ "
|
||||
success_symbol = ""
|
||||
style = "bold red"
|
||||
|
||||
[username]
|
||||
show_always = false
|
||||
style_user = "bold yellow"
|
||||
style_root = "bold red"
|
||||
format = "[$user]($style)@"
|
||||
|
||||
[hostname]
|
||||
ssh_only = true
|
||||
style = "bold green"
|
||||
format = "[$hostname]($style) "
|
||||
|
||||
[python]
|
||||
symbol = " "
|
||||
format = '[${symbol}${pyenv_prefix}(${version})(\($virtualenv\))]($style) '
|
||||
|
||||
[nodejs]
|
||||
symbol = " "
|
||||
format = "[$symbol($version)]($style) "
|
||||
|
||||
[rust]
|
||||
symbol = " "
|
||||
format = "[$symbol($version)]($style) "
|
||||
|
||||
[golang]
|
||||
symbol = " "
|
||||
format = "[$symbol($version)]($style) "
|
||||
|
||||
[java]
|
||||
symbol = " "
|
||||
format = "[$symbol($version)]($style) "
|
||||
|
||||
[docker_context]
|
||||
symbol = " "
|
||||
format = "[$symbol$context]($style) "
|
||||
|
||||
[shell]
|
||||
disabled = true
|
||||
style = "bold cyan"
|
||||
@@ -6,7 +6,7 @@ export ZSHRC_GIT="https://git.jeremymcclure.com/jeremy/scripts/raw/branch/master
|
||||
|
||||
export ZSHRC_URL="$ZSHRC_GIT/zshrc"
|
||||
export ZSHRC_BOOTSTRAP_URL="$ZSHRC_GIT/zshrc-bootstrap.zsh"
|
||||
export ZSHRC_BOOTSTRAP_VERSION="2"
|
||||
export ZSHRC_BOOTSTRAP_VERSION="4"
|
||||
export ZSHRC_BOOTSTRAP="$ZSH_CONFIG/.bootstrapped"
|
||||
|
||||
[[ -d "$HOME/Scripts" ]] && path+=("$HOME/Scripts")
|
||||
@@ -32,7 +32,10 @@ install_opencode(){
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
}
|
||||
|
||||
# Bootstrap: download and run once per bootstrap version
|
||||
# Bootstrap: download and run once per bootstrap version. Installs missing CLI
|
||||
# tools and oh-my-zsh/plugins across distros; prints a banner so the user knows
|
||||
# what the activity is. Re-runs only when ZSHRC_BOOTSTRAP_VERSION is bumped or
|
||||
# BOOTSTRAP=true is set.
|
||||
if [[ ! -f "$ZSHRC_BOOTSTRAP" || "$(cat "$ZSHRC_BOOTSTRAP" 2>/dev/null)" != "$ZSHRC_BOOTSTRAP_VERSION" || "$BOOTSTRAP" == "true" ]]; then
|
||||
tmp=/tmp/.zshrc_bootstrap.$$
|
||||
curl -fsSL --connect-timeout 5 --max-time 10 -o "$tmp" "$ZSHRC_BOOTSTRAP_URL" 2>/dev/null
|
||||
@@ -76,6 +79,11 @@ setopt HIST_FCNTL_LOCK
|
||||
setopt AUTO_CD
|
||||
setopt AUTO_LIST
|
||||
setopt INTERACTIVE_COMMENTS
|
||||
setopt HIST_VERIFY
|
||||
setopt EXTENDED_HISTORY
|
||||
setopt AUTO_PUSHD
|
||||
setopt PUSHD_IGNORE_DUPS
|
||||
setopt PUSHD_SILENT
|
||||
|
||||
ZSH_THEME=""
|
||||
ENABLE_CORRECTION="false"
|
||||
@@ -112,7 +120,10 @@ zstyle ':omz:plugins:eza' 'icons' yes
|
||||
zstyle ':omz:plugins:eza' 'color-scale' all
|
||||
zstyle ':omz:plugins:eza' 'color-scale-mode' fixed
|
||||
zstyle ':omz:plugins:eza' 'size-prefix' si
|
||||
zstyle ':omz:plugins:eza' 'hyperlink' yes
|
||||
# NOTE: 'hyperlink' is intentionally NOT set here. The oh-my-zsh eza plugin adds
|
||||
# a bare "--hyperlink" flag which eza (clap) treats as taking the next token as
|
||||
# its optional value, so `la /` fails with "invalid value '/'". We re-enable
|
||||
# hyperlinks safely below via the glued `--hyperlink=always` form.
|
||||
|
||||
zstyle ':completion:*' menu select
|
||||
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
|
||||
@@ -121,21 +132,74 @@ zstyle ':completion:*' special-dirs true
|
||||
|
||||
source $OHMYZSH/oh-my-zsh.sh
|
||||
|
||||
# Re-enable eza hyperlinks, picking the flag form the installed eza supports.
|
||||
# Newer eza: --hyperlink takes an optional value; the oh-my-zsh plugin's bare
|
||||
# --hyperlink would eat the following path arg (e.g. `la /` -> invalid value '/'),
|
||||
# so we use the glued --hyperlink=always form.
|
||||
# Older eza (e.g. Debian's): --hyperlink is a pure boolean and rejects a value,
|
||||
# so --hyperlink=always errors; the bare --hyperlink is correct and safe there
|
||||
# (boolean flags don't consume the next token, so the path-eating bug doesn't
|
||||
# apply).
|
||||
_hl_flag="--hyperlink"
|
||||
if command -v eza >/dev/null && eza --help 2>&1 | grep -q -- '--hyperlink.*<WHEN>'; then
|
||||
_hl_flag="--hyperlink=always"
|
||||
fi
|
||||
for _ea in la ldot lD lDD ll ls lsd lsdl lS lT; do
|
||||
(( $+aliases[$_ea] )) && alias "$_ea"="${aliases[$_ea]} $_hl_flag"
|
||||
done
|
||||
unset _ea _hl_flag
|
||||
|
||||
# --- Tool integrations (self-configuring: enabled only when the tool exists) ---
|
||||
|
||||
# zoxide: frecency-based directory jumping — `z <substring>` / `zi` (fzf picker).
|
||||
# Replaces nothing; `cd` still works. Auto-installed by the bootstrap.
|
||||
command -v zoxide >/dev/null && eval "$(zoxide init zsh)" 2>/dev/null
|
||||
|
||||
# bat: colored `cat` and colored man pages. bat/batcat handled by bootstrap.
|
||||
if command -v bat >/dev/null; then
|
||||
alias cat='bat'
|
||||
export BAT_THEME="${BAT_THEME:-Monokai Extended}"
|
||||
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
|
||||
export MANROFFOPT='-c'
|
||||
fi
|
||||
|
||||
# delta: prettier git diffs as git's pager (git respects GIT_PAGER). Auto-installed
|
||||
# by the bootstrap. Not aliased to `diff` since delta reads diff input on stdin,
|
||||
# not two file arguments — GIT_PAGER is the correct integration.
|
||||
command -v delta >/dev/null && export GIT_PAGER='delta'
|
||||
|
||||
alias c='clear'
|
||||
alias q="exit"
|
||||
alias nbstat=$'netbird status --json | jq -r \'.peers.details[]? | [(.hostname // .fqdn), .netbirdIp, .status] | @tsv\' | column -t -s $\'\\t\''
|
||||
alias open-ports="ss -tulpn | grep LISTEN"
|
||||
alias yeet='yay -Rcs'
|
||||
|
||||
# Auto-update: check every shell start, silently fail if server unreachable
|
||||
# Auto-update self and managed configs: checked every interactive shell start,
|
||||
# silently skipped if the server is unreachable. Keeps zshrc + starship.toml in
|
||||
# sync across machines (reproducible/self-configuring); only reloads the shell
|
||||
# when the zshrc itself changes (starship applies its config live per render).
|
||||
if [[ -o interactive ]]; then
|
||||
# zshrc (reload shell if changed)
|
||||
tmp=/tmp/.zshrc.$$
|
||||
curl -fsSL --connect-timeout 3 --max-time 8 "$ZSHRC_URL" -o "$tmp" 2>/dev/null
|
||||
if [[ -f "$tmp" ]] && zsh -n "$tmp" 2>/dev/null && ! cmp -s "$tmp" "$HOME/.zshrc" 2>/dev/null; then
|
||||
if curl -fsSL --connect-timeout 3 --max-time 8 "$ZSHRC_URL" -o "$tmp" 2>/dev/null \
|
||||
&& [[ -f "$tmp" ]] && zsh -n "$tmp" 2>/dev/null \
|
||||
&& ! cmp -s "$tmp" "$HOME/.zshrc" 2>/dev/null; then
|
||||
echo "Updating zshrc..."
|
||||
mv "$tmp" "$HOME/.zshrc" && exec zsh
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
|
||||
# starship prompt config (overwrite if changed; applied live on next render)
|
||||
if command -v starship >/dev/null; then
|
||||
stmp=$(mktemp)
|
||||
if curl -fsSL --connect-timeout 3 --max-time 8 "$ZSHRC_GIT/starship.toml" -o "$stmp" 2>/dev/null \
|
||||
&& [[ -f "$stmp" ]] && ! cmp -s "$stmp" "$HOME/.config/starship.toml" 2>/dev/null; then
|
||||
mkdir -p "$HOME/.config"
|
||||
mv "$stmp" "$HOME/.config/starship.toml"
|
||||
else
|
||||
rm -f "$stmp"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ -f $HOME/.zshrc.local ]] || touch $HOME/.zshrc.local
|
||||
|
||||
+179
-22
@@ -2,50 +2,183 @@ export ZSH_CONFIG=$HOME/.config/zsh
|
||||
export OHMYZSH=$ZSH_CONFIG/oh-my-zsh
|
||||
export OHMYZSH_CUSTOM=$OHMYZSH/custom
|
||||
|
||||
# Bootstrap banner — built dynamically so the box stays aligned regardless
|
||||
# of the version string length or terminal width.
|
||||
_bootstrap_banner() {
|
||||
local inner=61 # inner width (between the vertical bars)
|
||||
local pad line
|
||||
local title="Zsh bootstrap - one-time environment setup (v${ZSHRC_BOOTSTRAP_VERSION})"
|
||||
local rule=$(printf '═%.0s' {1..$inner})
|
||||
local mid=$(printf '─%.0s' {1..$inner})
|
||||
# left-pad the title by 2 spaces so it doesn't hug the left bar
|
||||
title=" $title"
|
||||
pad=$(( inner - ${#title} ))
|
||||
(( pad < 0 )) && pad=0
|
||||
echo ""
|
||||
echo "╔${rule}╗"
|
||||
printf '║%s%*s║\n' "$title" "$pad" ""
|
||||
echo "╠${mid}╣"
|
||||
while IFS= read -r line; do
|
||||
line=" $line"
|
||||
pad=$(( inner - ${#line} ))
|
||||
(( pad < 0 )) && pad=0
|
||||
printf '║%s%*s║\n' "$line" "$pad" ""
|
||||
done <<'EOF'
|
||||
This runs on first login or when the bootstrap version
|
||||
changes. It installs missing CLI tools and clones oh-my-zsh
|
||||
+ plugins across distros. Network calls time out after
|
||||
~30s so an unreachable host skips silently. The first run
|
||||
may take a minute; subsequent runs are a no-op.
|
||||
EOF
|
||||
echo "╚${rule}╝"
|
||||
echo ""
|
||||
}
|
||||
_bootstrap_banner
|
||||
unfunction _bootstrap_banner
|
||||
|
||||
# GitHub-release binary fallback. Used for tools not packaged in a distro's
|
||||
# repos (e.g. bat/zoxide/git-delta on RHEL-family without EPEL, or no cargo).
|
||||
# Resolves the latest release tag via the /releases/latest redirect (no API,
|
||||
# no rate limits), builds the asset URL from a template, extracts, and copies
|
||||
# the named binary into ~/.local/bin. Args:
|
||||
# $1 owner/repo $2 asset template (%v = version, no leading v)
|
||||
# $3 binary name $4 arch suffix to substitute into %a in the template
|
||||
_gh_latest_tag() {
|
||||
local repo="$1"
|
||||
curl -fsIL --connect-timeout 5 --max-time 20 "https://github.com/$repo/releases/latest" 2>/dev/null \
|
||||
| grep -i '^location:' | tail -1 \
|
||||
| sed -E 's#.*tag/([^[:space:]"/]+).*#\1#'
|
||||
}
|
||||
|
||||
_gh_install_bin() {
|
||||
local repo="$1" tmpl="$2" bin="$3"
|
||||
local rawtag ver asset ext tmp found
|
||||
rawtag=$(_gh_latest_tag "$repo")
|
||||
if [[ -z "$rawtag" ]]; then
|
||||
echo " FAIL could not resolve latest tag for $repo"; return 1
|
||||
fi
|
||||
ver="${rawtag#v}" # strip optional leading v for asset-filename substitution
|
||||
asset="${tmpl//\%v/$ver}"
|
||||
ext="${asset##*.}"
|
||||
echo " gh installing $bin ($rawtag from $repo)"
|
||||
tmp=$(mktemp -d)
|
||||
if curl -fsSL --connect-timeout 5 --max-time 60 -o "$tmp/pkg.$ext" \
|
||||
"https://github.com/$repo/releases/download/$rawtag/$asset" \
|
||||
&& { case "$ext" in
|
||||
gz) tar -xzf "$tmp/pkg.$ext" -C "$tmp" 2>/dev/null ;;
|
||||
*) return 1 ;;
|
||||
esac; }; then
|
||||
found=$(find "$tmp" -type f -name "$bin" ! -name "*.*" | head -1)
|
||||
if [[ -n "$found" ]]; then
|
||||
cp "$found" "$HOME/.local/bin/$bin"
|
||||
chmod +x "$HOME/.local/bin/$bin"
|
||||
rm -rf "$tmp"; return 0
|
||||
fi
|
||||
fi
|
||||
rm -rf "$tmp"
|
||||
echo " FAIL could not download/extract $bin from $repo"; return 1
|
||||
}
|
||||
|
||||
touch ~/.hushlogin
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
mkdir -p "$ZSH_CONFIG"
|
||||
path+=("$HOME/.local/bin")
|
||||
|
||||
for p in git starship fzf eza rsync lazydocker fastfetch tmux; do
|
||||
command -v $p >/dev/null && continue
|
||||
# Package availability checks can hang on slow/unreachable mirrors (cold dnf
|
||||
# metadata, apt update, pacman -Sy). Wrap them in a 20s timeout so the bootstrap
|
||||
# never stalls the shell; a timed-out check just falls through to the next PM
|
||||
# or to the binary installers below. Verbose output so real builds (cargo) and
|
||||
# slow installs are visible as progress instead of an apparent hang.
|
||||
for p in git starship fzf eza rsync lazydocker fastfetch tmux bat zoxide; do
|
||||
if command -v $p >/dev/null; then
|
||||
echo " ok $p (already installed)"
|
||||
continue
|
||||
fi
|
||||
echo " ... $p (checking package managers)"
|
||||
if command -v brew >/dev/null; then
|
||||
echo "Installing $p using brew" && brew install $p && continue
|
||||
echo " brew installing $p" && brew install $p && continue
|
||||
fi
|
||||
if command -v apt >/dev/null && apt-cache show $p >/dev/null 2>&1; then
|
||||
echo "Installing $p using Apt" && sudo apt install $p -y && continue
|
||||
if command -v apt >/dev/null; then
|
||||
if timeout 20 apt-cache show $p >/dev/null 2>&1; then
|
||||
echo " apt installing $p" && sudo apt install -y $p && continue
|
||||
else
|
||||
echo " apt $p not available (or metadata check timed out)"
|
||||
fi
|
||||
fi
|
||||
if command -v dnf >/dev/null && dnf list available $p >/dev/null 2>&1; then
|
||||
echo "Installing $p using dnf" && sudo dnf install $p -y && continue
|
||||
if command -v dnf >/dev/null; then
|
||||
if timeout 20 dnf list available $p >/dev/null 2>&1; then
|
||||
echo " dnf installing $p" && sudo dnf install -y $p && continue
|
||||
else
|
||||
echo " dnf $p not available (or metadata check timed out)"
|
||||
fi
|
||||
fi
|
||||
if command -v pacman >/dev/null && pacman -Si $p >/dev/null 2>&1; then
|
||||
echo "Installing $p using pacman" && sudo pacman -Sy --noconfirm $p && continue
|
||||
if command -v pacman >/dev/null; then
|
||||
if timeout 20 pacman -Si $p >/dev/null 2>&1; then
|
||||
echo " pacman installing $p" && sudo pacman -Sy --noconfirm $p && continue
|
||||
else
|
||||
echo " pacman $p not available (or metadata check timed out)"
|
||||
fi
|
||||
fi
|
||||
if command -v cargo >/dev/null; then
|
||||
echo "Installing $p using cargo" && cargo install $p --locked && continue
|
||||
echo " cargo installing $p (may take a few minutes to compile)" && cargo install $p --locked && continue
|
||||
fi
|
||||
echo " FAIL could not install $p by any method; skipping"
|
||||
done
|
||||
|
||||
if ! command -v starship >/dev/null; then
|
||||
echo "Installing starship binary..." && \
|
||||
curl -fsSL --connect-timeout 5 --max-time 30 https://starship.rs/install.sh | sh -s -- -b ~/.local/bin -y >/dev/null 2>&1
|
||||
echo " curl installing starship binary"
|
||||
curl -fsSL --connect-timeout 5 --max-time 30 https://starship.rs/install.sh | sh -s -- -b ~/.local/bin -y
|
||||
fi
|
||||
|
||||
if ! command -v lazydocker >/dev/null; then
|
||||
echo "Installing lazydocker binary..." && \
|
||||
echo " curl installing lazydocker binary"
|
||||
curl -fsSL --connect-timeout 5 --max-time 30 https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | \
|
||||
DIR=$HOME/.local/bin bash >/dev/null 2>&1
|
||||
DIR=$HOME/.local/bin bash
|
||||
fi
|
||||
|
||||
# Debian/Ubuntu ships bat as 'batcat' and fd as 'fdfind'; symlink to the
|
||||
# canonical names so the zshrc integrations (which call bat/fd) work everywhere.
|
||||
if ! command -v bat >/dev/null && command -v batcat >/dev/null; then
|
||||
ln -sf "$(command -v batcat)" "$HOME/.local/bin/bat"
|
||||
fi
|
||||
if ! command -v fd >/dev/null && command -v fdfind >/dev/null; then
|
||||
ln -sf "$(command -v fdfind)" "$HOME/.local/bin/fd"
|
||||
fi
|
||||
|
||||
# git-delta: the package is named 'git-delta' on every package manager but the
|
||||
# binary is 'delta' (name mismatch vs the install loop above), so install via the
|
||||
# same PM cascade using the real package name. Used by zshrc as GIT_PAGER.
|
||||
if ! command -v delta >/dev/null; then
|
||||
echo " ... delta (checking package managers, package name is 'git-delta')"
|
||||
if command -v brew >/dev/null; then
|
||||
echo " brew installing git-delta" && brew install git-delta
|
||||
elif command -v apt >/dev/null && timeout 20 apt-cache show git-delta >/dev/null 2>&1; then
|
||||
echo " apt installing git-delta" && sudo apt install -y git-delta
|
||||
elif command -v dnf >/dev/null && timeout 20 dnf list available git-delta >/dev/null 2>&1; then
|
||||
echo " dnf installing git-delta" && sudo dnf install -y git-delta
|
||||
elif command -v pacman >/dev/null && timeout 20 pacman -Si git-delta >/dev/null 2>&1; then
|
||||
echo " pacman installing git-delta" && sudo pacman -Sy --noconfirm git-delta
|
||||
elif command -v cargo >/dev/null; then
|
||||
echo " cargo installing git-delta (may take a few minutes to compile)" && cargo install git-delta --locked
|
||||
else
|
||||
echo " FAIL could not install delta; skipping"
|
||||
fi
|
||||
fi
|
||||
|
||||
arch=$(uname -m)
|
||||
target_musl=""; target_gnu=""
|
||||
case "$arch" in
|
||||
x86_64) target_fast="linux-amd64" && target_eza="x86_64-unknown-linux-gnu" ;;
|
||||
aarch64|arm64) target_fast="linux-aarch64" && target_eza="aarch64-unknown-linux-gnu" ;;
|
||||
armv7l) target_fast="linux-armv7l" && target_eza="armv7-unknown-linux-gnueabihf" ;;
|
||||
x86_64) target_fast="linux-amd64" && target_eza="x86_64-unknown-linux-gnu" \
|
||||
&& target_musl="x86_64-unknown-linux-musl" && target_gnu="x86_64-unknown-linux-gnu" ;;
|
||||
aarch64|arm64) target_fast="linux-aarch64" && target_eza="aarch64-unknown-linux-gnu" \
|
||||
&& target_musl="aarch64-unknown-linux-musl" && target_gnu="aarch64-unknown-linux-gnu" ;;
|
||||
armv7l) target_fast="linux-armv7l" && target_eza="armv7-unknown-linux-gnueabihf" \
|
||||
&& target_musl="armv7-unknown-linux-musleabihf" && target_gnu="armv7-unknown-linux-gnueabihf" ;;
|
||||
esac
|
||||
|
||||
if [[ -n $target_fast && -n $target_eza ]]; then
|
||||
if ! command -v fastfetch >/dev/null; then
|
||||
echo " curl installing fastfetch (github release tarball)"
|
||||
if curl -fsSL --connect-timeout 5 --max-time 30 -o /tmp/fastfetch.tar.gz \
|
||||
"https://github.com/fastfetch-cli/fastfetch/releases/latest/download/fastfetch-${target_fast}.tar.gz" \
|
||||
&& tar -xzf /tmp/fastfetch.tar.gz -C /tmp 2>/dev/null; then
|
||||
@@ -59,26 +192,48 @@ if [[ -n $target_fast && -n $target_eza ]]; then
|
||||
rm -rf "$ffdir" /tmp/fastfetch.tar.gz
|
||||
else
|
||||
rm -f /tmp/fastfetch.tar.gz
|
||||
echo " FAIL could not download/extract fastfetch; skipping"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v eza >/dev/null; then
|
||||
echo " curl installing eza (github release tarball)"
|
||||
if curl -fsSL --connect-timeout 5 --max-time 30 -o /tmp/eza.tar.gz \
|
||||
"https://github.com/eza-community/eza/releases/latest/download/eza_${target_eza}.tar.gz" \
|
||||
&& tar -xzf /tmp/eza.tar.gz -C "$HOME/.local/bin/" 2>/dev/null; then
|
||||
chmod +x "$HOME/.local/bin/eza"
|
||||
else
|
||||
echo "Warning: failed to download eza" >&2
|
||||
rm -f /tmp/eza.tar.gz
|
||||
echo " FAIL could not download/extract eza; skipping"
|
||||
fi
|
||||
rm -f /tmp/eza.tar.gz
|
||||
fi
|
||||
fi
|
||||
|
||||
# GitHub-release binary fallbacks for tools often missing from distro repos
|
||||
# (e.g. bat/zoxide/git-delta on RHEL/Alma without EPEL, or boxes with no cargo).
|
||||
# Tried after the PM/cargo loop above already failed for these. Static musl
|
||||
# builds preferred so there are no glibc dependencies; delta ships gnu-only.
|
||||
if ! command -v bat >/dev/null && [[ -n "$target_musl" ]]; then
|
||||
_gh_install_bin "sharkdp/bat" "bat-v%v-$target_musl.tar.gz" "bat"
|
||||
fi
|
||||
|
||||
if ! command -v zoxide >/dev/null && [[ -n "$target_musl" ]]; then
|
||||
_gh_install_bin "ajeetdsouza/zoxide" "zoxide-%v-$target_musl.tar.gz" "zoxide"
|
||||
fi
|
||||
|
||||
if ! command -v delta >/dev/null && [[ -n "$target_gnu" ]]; then
|
||||
_gh_install_bin "dandavison/delta" "delta-%v-$target_gnu.tar.gz" "delta"
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.config/zsh"
|
||||
|
||||
[ ! -d $OHMYZSH ] && \
|
||||
if [ ! -d "$OHMYZSH" ]; then
|
||||
echo " git cloning oh-my-zsh"
|
||||
GIT_TERMINAL_PROMPT=0 git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=10 \
|
||||
clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git $OHMYZSH
|
||||
clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git $OHMYZSH || \
|
||||
echo " FAIL could not clone oh-my-zsh"
|
||||
fi
|
||||
|
||||
for plug in \
|
||||
"zsh-autosuggestions|https://github.com/zsh-users/zsh-autosuggestions" \
|
||||
@@ -88,9 +243,11 @@ do
|
||||
name="${plug%%|*}"
|
||||
url="${plug##*|}"
|
||||
dir="$OHMYZSH_CUSTOM/plugins/$name"
|
||||
[[ ! -d "$dir" ]] && \
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
echo " git cloning $name"
|
||||
GIT_TERMINAL_PROMPT=0 git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=10 \
|
||||
clone "$url" "$dir" 2>/dev/null
|
||||
clone "$url" "$dir" 2>/dev/null || echo " FAIL could not clone $name"
|
||||
fi
|
||||
done
|
||||
|
||||
# Cleanup
|
||||
|
||||
Reference in New Issue
Block a user