Shell Environment - uhop/dotfiles GitHub Wiki

Shell Environment

.bashrc

Executed for every bash session (interactive and non-interactive). The file is organized into two phases: environment setup (always) and interactive setup (gated).

Interactive detection

The internal variable __INTERACTIVE (yes or empty) gates interactive-only setup. Non-interactive shells (e.g., ssh host some-command) get PATH, exports, and aliases but skip completions, tool initialization, keybindings, and fastfetch. VISUAL is exported as micro independently.

Prompt

  • Color prompt enabled for xterm-color, xterm-256color, screen-256color
  • Uses bash-git-prompt (via brew) with Single_line theme
  • xterm title set to user@host: dir

Brew

Brew shell environment is initialized early. On Linux it uses the standard /home/linuxbrew/.linuxbrew path; on macOS it checks /opt/homebrew (Apple Silicon) then /usr/local (Intel).

PATH additions

Path Purpose
~/bin User scripts
~/.local/bin Custom CLI utilities
$(brew --prefix)/opt/util-linux/bin util-linux tools
$(brew --prefix)/opt/util-linux/sbin util-linux admin tools
$PYENV_ROOT/bin, $PYENV_ROOT/shims pyenv
$BUN_INSTALL/bin Bun runtime
~/.lmstudio/bin LM Studio

PATH is deduplicated at the end of .bashrc using a bash function _dedup_path() that also ensures the nvm path comes first.

SSH agent (Linux)

On Linux, ssh-agent runs as a systemd user service at ~/.config/systemd/user/ssh-agent.service. The service binds the well-known path %t/ssh-agent.socket (%t = $XDG_RUNTIME_DIR) via ExecStart=/usr/bin/ssh-agent -D -a %t/ssh-agent.socket and publishes SSH_AUTH_SOCK into the systemd user environment via ExecStartPost=systemctl --user set-environment.

ssh-agent has no LISTEN_FDS support, so a separate .socket unit would be a dead listener on OpenSSH <10 โ€” every client would hang. (OpenSSH 10.0p2+ added a fallback that reads $SSH_AUTH_SOCK from the service's environment, but the explicit -a works on every version.) The service also overrides distro-shipped /usr/lib/systemd/user/ssh-agent.{service,socket} โ€” notably Ubuntu 25.10's units listening at %t/openssh_agent โ€” unifying the whole fleet on one socket path.

For shells that don't inherit the systemd user environment (e.g. ssh host bash non-interactive), .bashrc defensively exports SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent.socket when XDG_RUNTIME_DIR is set.

One agent per user login, shared by every shell. No eval "$(ssh-agent)" in .bashrc, no orphaned agent processes. ~/.ssh/config sets AddKeysToAgent yes globally, so the first time ssh uses a key it's auto-added to the agent โ€” no explicit ssh-add needed. Keys persist until logout or systemctl --user restart ssh-agent.

On macOS the Keychain-integrated launchd agent handles this โ€” the systemd unit is excluded via .chezmoiignore on non-Linux. On Linux hosts without a user systemd manager (rare: bare containers), the installer script skips gracefully and SSH still works manually.

See Setting Up a New Machine ยง After setup: what's running for the full list of services.

Tool initialization

Tool What it does
bash-completion Loaded from brew prefix
git-completion Loaded from ~/.config/git/git-completion.bash
nvm Node Version Manager โ€” sets NVM_DIR, sources nvm and its completions
pyenv Python version manager
bun JavaScript runtime
doas Completion for doas command
zoxide z command (cd replacement) via zoxide init bash
broot File manager launcher br
fzf Fuzzy finder โ€” sets up keybindings and custom completions (see below)
playbash Bash completion for the playbash runner
pet Snippet manager โ€” ppet, Ctrl+x Ctrl+r search, Ctrl+n next parameter
iterm2 iTerm2 shell integration (if available)

fzf

fzf is a general-purpose fuzzy finder, wired into bash so that common operations become a fuzzy-pick instead of a typed path or a half-remembered command.

Default key bindings (shipped by fzf's key-bindings.bash):

Binding What it does
Ctrl+R Fuzzy-search shell history. Replaces the default reverse-i-search with a full fuzzy picker โ€” type any fragment that appeared anywhere in a previous command line, live-preview matches, and press Enter to drop it at the prompt. The single biggest daily-use win from fzf.
Ctrl+T Paste a fuzzy-picked file path onto the current command line
Alt+C cd into a fuzzy-picked subdirectory

Beyond the key bindings, fzf powers:

  • Per-command completions for directory/path/host arguments on common commands (see fzf completions below) โ€” trigger with Tab after the trigger sequence **.
  • fzf-git.sh key bindings (branches, tags, remotes, stashes, PRs) if the script is present at ~/.local/vendors/fzf-git.sh.
  • pick โ€” the interactive command reference uses fzf to browse and execute curated commands by topic.
  • pet โ€” snippet manager search (Ctrl+x Ctrl+r).

fzf completions

When fzf is available, custom completions are registered:

Completion type Commands
Directory z, tree, ltr, zl, zpush, zp
Path cp, mv, rm, cat, l, ls, ll, la, lla, l., mic, micro, gimme, rcp, rmv, rup, rsy
Host ping, prettyping, sftp, ssht, mosh, mosht

Also sources fzf-git.sh from ~/.local/vendors/ if available.

Exports

Variable Value Purpose
EDITOR micro Default editor
VISUAL micro Visual editor
BASH_SILENCE_DEPRECATION_WARNING 1 Suppress macOS zsh warning
FORCE_COLOR 3 Force 256-color support in tools
GITHUB_USERNAME (rendered by chezmoi from the githubUsername data value) Used by utilities and as the default basis for JOT_AWS_S3_BUCKET. A value in ~/.env overrides the chezmoi-rendered default. See GITHUB_USERNAME below.
JOT_AWS_S3_BUCKET ${GITHUB_USERNAME}-notes S3 bucket for jot. Exported only if not already set and GITHUB_USERNAME is non-empty โ€” so ~/.env can pin a custom bucket, and users without a GitHub identity don't get a bogus -notes value.

Startup display

On interactive terminals (not inside Windsurf), fastfetch is shown at shell level 1 (direct terminal) or level 2 inside tmux.

.env loading

If ~/.env exists, its contents are exported via set -a / . ~/.env / set +a.

Shape. ~/.env is a plain KEY=value file, one variable per line, sourced by bash. No export keyword needed (set -a handles it). Comments start with #. Values containing spaces or special characters should be quoted. Example:

# ~/.env โ€” per-machine environment, not tracked in the dotfiles repo
# Secrets and per-machine overrides. Non-secret values chezmoi already knows
# (e.g. GITHUB_USERNAME) are rendered into .bashrc directly.

EXAMPLE_API_URL=http://localhost:8080
EXAMPLE_API_TOKEN=...

# Optional โ€” override the default JOT_AWS_S3_BUCKET (defaults to
# ${GITHUB_USERNAME}-notes if GITHUB_USERNAME is set)
# JOT_AWS_S3_BUCKET=my-custom-bucket

Why ~/.env and not a template. Secrets and per-machine values stay outside the chezmoi-managed tree. This keeps credentials out of git and makes a machine's setup easy to diverge from the default.

What's not needed in ~/.env. Non-secret values chezmoi already collected at chezmoi init โ€” currently GITHUB_USERNAME โ€” are rendered into .bashrc by chezmoi. You can still put GITHUB_USERNAME=... in ~/.env as an override, but nothing breaks if you don't.

Tools like jot, vault, and other skills document their own required variables; add them to ~/.env as you adopt each tool.

Propagating ~/.env to other machines. It's user-managed, so copy it deliberately โ€” playbash put ~/.env host: (or the planned jot bundle flow) rather than checking it into the repo.

GITHUB_USERNAME

GITHUB_USERNAME is collected once by chezmoi: during chezmoi init it prompts for the githubUsername data value (default uhop, overridable from a GITHUB_USERNAME env var set when running chezmoi init). From there it flows in two places:

  • .bashrc (via dot_bashrc.tmpl): chezmoi renders export GITHUB_USERNAME=<value> near the top of the exports block, before ~/.env is sourced โ€” so ~/.env can override if a machine needs a different identity.
  • Chezmoi run-scripts and templates (via [scriptEnv] in .chezmoi.toml.tmpl): used by private_dot_ssh/create_private_authorized_keys.tmpl to pull GitHub-registered SSH keys, and by any other chezmoi-managed scripts that need it.

JOT_AWS_S3_BUCKET is derived as ${GITHUB_USERNAME}-notes only when (a) it's not already set and (b) GITHUB_USERNAME is non-empty. That means users without a GitHub identity get no bogus -notes bucket, and anyone wanting a custom bucket can just set JOT_AWS_S3_BUCKET=my-bucket in ~/.env. If jot runs with neither set, it prints a warning explaining the relationship and exits.


.bash_aliases

Sourced by .bashrc unconditionally (available to both interactive and non-interactive shells). Contains aliases, functions, and safety nets grouped by category.

Git alias completions are declared in .bash_aliases via the __git_alias_completions associative array (maps alias name to git subcommand), then wired up by .bashrc in the interactive block after bash-completion is loaded.

doas / sudo

If doas is installed and configured (/etc/doas.conf exists), sudo is aliased to doas.

Listing files (eza)

Alias Expansion Description
l eza Plain listing
ls eza --grid --color auto --icons --sort=type Grid with icons
ll eza --long --color always --icons --sort=type Long format
la eza --grid --all ... All files, grid
lla eza --long --all ... All files, long
ltr eza -l -t modified -r Long, sorted by modification time (newest last)

l. โ€” function that lists dotfiles only: l. [dir]

Directory navigation (zoxide)

Alias Expansion
.. z ..
... z ../../
.... z ../../../
.4 z ../../../../
.5 z ../../../../../
~ z ~
- z -

up N โ€” go up N directories: up 3 = cd ../../../

zl [dir] โ€” z into a directory and list it.

mkz / mkcd โ€” create directory and z into it.

zpush / zp โ€” pushd that matches a keyword the way z does: zp proj pushes the directory z proj would go to. pushd's own arguments (-n, +N, -N, -) and an existing directory go to pushd as they are, so zp +2 rotates the stack and zp -n proj adds proj without going there. A directory is in the stack once: when it is already there, zp moves that entry to the top, so zpop still takes you back to where you were.

zpop โ€” popd.

zdirs โ€” the directory stack in fzf, numbered as dirs -v numbers it, with the keys in a one-line legend. Typing filters the list. Enter moves the picked directory to the top and goes there, as zp does. Ctrl-R rotates the stack to it instead (pushd +N). Ctrl-D drops it from the stack (the current directory stays) and reopens the list. Del drops it too, and so does Backspace when the filter is empty. On a Mac keyboard, the key labeled Delete is Backspace, and Fn+Delete is Del. Esc closes it. With any option or argument it is plain dirs.

File safety nets

Alias What it does
rm rm -I --preserve-root (Linux) or rm -I (macOS)
mv mv -iv (interactive, verbose)
cp cp -iv
ln ln -i
chown, chmod, chgrp --preserve-root (Linux only)
mkdir mkdir -pv (create parents, verbose)
wget wget -c (resume downloads)

rsync-based transfers

Available when rsync is installed:

Alias Description
rcp rsync copy with aggregate progress (--info=progress2 --no-inc-recursive --stats)
rmv rsync move with aggregate progress (same flags + --remove-source-files)
rup rsync update โ€” skip newer files on receiver (-u)
rsy rsync sync โ€” update + delete extraneous files on receiver (-u --delete)

Git shell aliases

Shortcuts that wrap git commands with colored output via echoRun:

Alias Git command Completion
gst git status โœ“
gco git checkout โœ“
gcob git checkout -b โœ“
gcm git commit โœ“
gbr git branch โœ“
gpull git pull โœ“
gpush git push โœ“
gsw git switch (defaults to main branch) โœ“
gme git merge (defaults to main branch) โœ“
gg git gui
gk gitk --all

Colorized output

Alias What it does
grep grep --color=auto
egrep egrep --color=auto
fgrep fgrep --color=auto
diff diff --color=auto

Better replacements

Alias Replacement Notes
cat bat or batcat Syntax highlighting
ping sudo prettyping --nolegend Visual ping (only if prettyping installed)
top htop Interactive process viewer
du ncdu (no args) / real du (with args) Interactive disk usage when browsing, standard du otherwise
help tldr Short command examples
duf duf -only local,fuse Disk usage overview
bc bc -l Calculator with math library
mount Pretty-print (no args) / real mount (with args) Shows column-formatted table when listing, passes through for actual mounts

System shortcuts

Alias Description
h pick โ€” interactive command reference (see Utilities ยง pick)
j jobs -l
df df -kh
dd dd status=progress
free free -m
env env | sort (no args only; with args falls through to standard env)
path Print $PATH entries one per line
mic micro
lzd lazydocker
lzg lazygit

Power management

Alias Description
poweroff / shutdown / poff Power off (systemctl poweroff or shutdown -h now)
reboot Reboot (systemctl reboot or shutdown -r now)

Process inspection

Alias Description
psmem Processes sorted by memory (Linux)
psm10 Top 10 memory consumers (Linux)
psr Top CPU-intensive processes
psm Top memory-intensive processes
pse Process tree (Linux)
psg PATTERN Search running processes
pst Processes with start time and elapsed
oports List open listening ports
oports2 Open ports via ss (Linux)

Network / ownership

Alias/Function Description
gimme chown $USER:$USER โ€” take ownership (Linux)
dirty Watch dirty memory pages (Linux)

Search functions

where "pattern" [path/wildcard] โ€” recursive grep with glob support, e.g., where "TODO" "src/*.js". Uses noGlob to prevent shell expansion.

upfind [args] โ€” run find in current directory and each parent up to /. Use -h flag to stop at $HOME.

upfd [args] โ€” same as upfind but uses fd instead of find.

upsearch pattern โ€” shortcut for upfd -gH pattern (glob, hidden files).

Tree / grep filtered

Alias Description
tre tree excluding common junk dirs (node_modules, .git, venv, etc.)
gre grep -r excluding common dirs

Column truncation

nowrap [start] [-end] โ€” pipe filter to truncate output to terminal width. Positive arg sets start column, negative sets offset from right edge.

SSH + tmux helpers

Function Description
ssht host [session] SSH into host and attach/create tmux session
mosht host [session] Same via mosh
ett host [session] Same via et (Eternal Terminal)
kssh kitten ssh (Kitty)
kssht host [session] Kitty SSH + tmux

Default session name is ssh_tmux.


.bash_profile / .profile

Both files are identical: they conditionally source .bashrc only when $BASH_VERSION is set.

  • .bash_profile โ€” sourced by bash login shells. Sources .bashrc to get the full interactive environment.
  • .profile โ€” sourced by non-bash login shells (bash prefers .bash_profile). Also sources .bashrc when running under bash.
โš ๏ธ **GitHub.com Fallback** โš ๏ธ