summaryrefslogtreecommitdiff
path: root/config/profile
blob: e3843ede80e9913656a232c735f532d50ea10ac6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# shellcheck shell=bash
# profiling helpers
__profile_enabled=false
__timestamp() {
  # date +%s
  perl -MTime::HiRes=time -e 'printf "%d\n", time * 1000'
}
 if [ $__profile_enabled = true ]; then
 __previous_timestamp=$(__timestamp)
 fi
_profile() {
 if [ $__profile_enabled = true ]; then
   _new_timestamp=$(__timestamp)
   _diff=$((_new_timestamp - __previous_timestamp))
  echo "$_diff $1"
   __previous_timestamp=$_new_timestamp
 fi
}

_profile entry

# The INTERACTIVE layer.
#
# Plain environment (PATH, XDG, locale, tool homes) lives in config/env, which
# is POSIX sh and is loaded by *every* shell — see its header. This file holds
# only what must NOT run in every shell: aliases, history, terminal setup, and
# anything expensive (theme, jenv, and whatever profile.d brings in).
#
# Reached via ~/.zshrc (interactive zsh) and ~/.profile (login bash). Sourcing
# config/env here too, because a login bash reaches ~/.profile without ever
# passing through ~/.zshenv. It is idempotent, so the double load is harmless.
if [ -r "$HOME/.env" ]; then
  # shellcheck source=/dev/null
  . "$HOME/.env"
else
  # No ~/.env yet. A pull that does not run link.sh (by hand, or a sync without
  # --link) can leave this file updated and the ~/.env symlink still absent
  # - which would silently cost it PATH, XDG, locale and EDITOR. Fall back to
  # the copy next to this file, found through the ~/.profile symlink that got
  # us here. Harmless once link.sh has run.
  __dotfiles_self=$(readlink "$HOME/.profile" 2>/dev/null)
  if [ -n "$__dotfiles_self" ] && [ -r "${__dotfiles_self%/*}/env" ]; then
    # shellcheck source=/dev/null
    . "${__dotfiles_self%/*}/env"
  fi
  unset __dotfiles_self
fi
_profile env

# Everything below this point is bash/zsh syntax, and this file is not read
# only by bash and zsh: Ubuntu's /bin/sh is dash, and `sh -l` sources
# ~/.profile. dash cannot parse `[[ ]]` (noisy but survivable) and refuses a
# function named `exit`, which is a POSIX special builtin - that one is a fatal
# parse error, so dash dies here and never runs the command it was given. That
# silently broke every `sh -lc '...'` on every Linux host; it surfaced as a
# background daemon spawned that way failing before it could even create the
# log file its error message pointed at. macOS is unaffected: its /bin/sh is
# bash, so BASH_VERSION is set and the rest of the file still runs there.
#
# config/env above is POSIX and has already been sourced, so a plain sh keeps
# PATH, XDG, locale and EDITOR. It just stops before the interactive layer,
# which it had no use for anyway.
if [ -z "${BASH_VERSION-}" ] && [ -z "${ZSH_VERSION-}" ]; then
  return 0 2>/dev/null || exit 0
fi

# Marks that this file is already running, so ~/.bashrc (which we may source at
# the very end, for login bash) doesn't source us straight back. See the header
# of config/bashrc for the full two-way handshake.
__dotfiles_profile=1

if [[ "$(uname)" == "Darwin" ]]
then
  # Sometimes, when invoked via Karabiner, the HOME variable
  # will not be defined, even with a login shell
  : "${HOME:="/Users/$(whoami)"}"
elif [[ "$(uname)" == "Linux" ]]
then
  if command -v wl-copy >/dev/null && command -v wl-paste >/dev/null; then
    alias pbcopy='wl-copy'
    alias pbpaste='wl-paste'
  else
    alias pbcopy='xclip -selection clipboard'
    alias pbpaste='xclip -selection clipboard -o'
  fi
fi
_profile clipboard_tools

# ripgrep
# Remind to use rg instead of ag
# ag() {
#   >&2 echo "ERROR: You decided to switch to 'rg' (ripgrep)"
#   return 1
#   # command ag "$@"
# }
# if [[ -r "$HOME/.config/ripgrep/config" ]]; then
#   export RIPGREP_CONFIG_PATH="$HOME/.config/ripgrep/config"
# fi

# XDG_{CONFIG,CACHE,DATA}_HOME are set in config/env.

# Set history file location and size.
SHELL_NAME="$(basename "$SHELL")"
SHELL_CACHE="$XDG_CACHE_HOME/$SHELL_NAME"
mkdir -p "$SHELL_CACHE"
HISTFILE="$SHELL_CACHE/history"
HISTSIZE=1000000
# SAVEHIST is read by zsh itself, which shellcheck can't lint for.
# shellcheck disable=SC2034
SAVEHIST=1000000

exists() { command -v "$1" >/dev/null 2>&1; } # Check if command exists (is in path).
lspath() { echo "$PATH" | tr ':' '\n'; } # list PATH entries
pathadd() { [[ -d "$1" ]] && PATH="$1"$(echo ":$PATH:" | sed "s|:$1:|:|" | sed 's|:$||'); }
pathapp() { [[ -d "$1" ]] && PATH=$(echo ":$PATH:" | sed "s|:$1:|:|" | sed 's|^:||')"$1"; }

# GPG
GPG_TTY=$(tty)
export GPG_TTY
pbsign() { pbpaste | gpg --clearsign | pbcopy; }
pbverify() { pbpaste | gpg --verify; }

# Show processes listening on TCP sockets
listening() {
  lsof -n -P -i4TCP -sTCP:LISTEN \
    | awk '{ print $9,$1,$2 }' \
    | awk -F '\:' '{ print $2 }' \
    | grep -E -v '^$' \
    | column -t \
    | sort -n
}

# https://sites.google.com/site/jimmyxu101/testing/use-tcpdump-to-monitor-http-traffic
httpdump() {
  sudo tcpdump -A -i "${2:-lo0}" -s 0 -q "tcp port $1 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)"
}

alias clc="fc -ln -1 | sed -e 's/\\\\n/\\n/g' -e 's/\\\\t/\\t/g' | pbcopy"

# bash

# DISABLE_AUTO_TITLE, the core PATH entries and the less/PAGER settings are all
# in config/env.
# use bash like vi !
# set -o vi
_profile bash

# when using sudo, use alias expansion (otherwise sudo ignores your aliases)
alias sudo='sudo '

# color
export CLICOLOR=1
THEME_COLORS="$(theme --colors)"
export THEME_COLORS
if [ "$(theme --mode)" = "light" ]; then
  export LSCOLORS=GxFxCxDxBxegedabagaced
else
  export LSCOLORS=GxFxBxDxCxegedabagacad
fi
if exists bat ; then
  # best for light: Coldark-Cold, Github, Monokai Extended Light, OneHalfLight, gruvbox-light
  # best for dark: 1337, Coldark-Dark, Monokai Extended, OneHalfDark, gruvbox-dark
  # config file in $XDG_CONFIG_HOME/bat/config takes precedence to this env var
  export BAT_THEME='ansi' # works well with both light and dark themes
  export BAT_STYLE='plain'
  export MANPAGER="sh -c 'col -bx | bat -l man -p'"
  export FZF_COMPLETION_OPTS='--preview '\''bat --color=always --style=numbers --line-range=:500 {}'\'
fi
if exists lsd
then
  alias ls='lsd'
fi
_profile color

# VISUAL/EDITOR and the node environment are set in config/env.

# # ruby
# _profile ruby-start
# export RBENV_ROOT="${RBENV_ROOT:-"$XDG_CACHE_HOME/rbenv"}" # Set rbenv location.
# activate-rbenv() {
#   if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi
# }
# activate-rbenv
# if [ -d "$HOME/.rbenv/bin" ] ; then
#   pathapp "$HOME/.rbenv/bin"
#   eval "$(rbenv init -)"
# fi

# # add user installs bin to path
# if which ruby >/dev/null && which gem >/dev/null; then
#   pathapp "$(ruby -rubygems -e 'puts Gem.user_dir')/bin"
# fi
# _profile ruby-done

# python
# export VIRTUAL_ENV_DISABLE_PROMPT=1 # Add the virtualenv prompt myself.
# LANG/LC_* are set in config/env — mosh needs a UTF-8 locale in shells that
# never reach this file.
# If you want to use pyenv
# if [ -x "$(command -v pyenv)" ] ; then
#   export PYENV_ROOT="$XDG_DATA_HOME/pyenv"
#   pathadd "$PYENV_ROOT/bin"
#   eval "$(pyenv init --path)"
# fi
# Instead of virtualenvwrapper's:
#   mkvirtualenv -p <path/to/python> <name>
# I prefer to use:
#   virtualenv --python=<path/to/python> .venv
# If you want to use virtualenvwrapper, uncomment this:
# if [ -r "$HOME/.local/pipx/venvs" ] ; then
#   export WORKON_HOME="$XDG_DATA_HOME/virtualenvs"
#   export VIRTUALENVWRAPPER_PYTHON="$HOME/.local/pipx/venvs/virtualenvwrapper/bin/python3.9"
#   source "$HOME/.local/bin/virtualenvwrapper.sh"
# fi
export PYENV_VIRTUALENV_DISABLE_PROMPT=1
# The Python user bin is on PATH via config/env.
_profile python


# go — GOPATH and the go bin dirs are in config/env.
godocserver() { godoc "-http=localhost:6060"; }
_profile go

# rust — CARGO_HOME/RUSTUP_HOME and the cargo bin dir are in config/env, which
# also replaces what rustup's own $CARGO_HOME/env does (it only prepends PATH).

# GNU Arm Embedded Toolchain, used to build joeycastillo/Sensor-Watch, is on
# PATH via config/env.

# java
if exists jenv > /dev/null && [[ -z "$JENV_LOADED" ]]; then
  eval "$(jenv init - --no-rehash)";
  # export JAVA_HOME="${JAVA_HOME:-"$(jenv javahome)"}"
  # export IDEA_JDK=$JAVA_HOME
  # to make sure JAVA_HOME is set, enable this plugin - just need to run once:
  # jenv enable-plugin export
fi
_profile jenv-init

# A gradlew / gradle wrapper.
# https://github.com/gdubw/gng
# This would be nice but it breaks existing IntelliJ projects
# export GRADLE_USER_HOME="$XDG_CACHE_HOME/gradle" # Also contains gradle.properties (symlink from XDG_CONFIG_HOME).
if exists gw > /dev/null; then
  alias gradle=gw
fi

_profile java

# ANDROID_HOME and the colima/testcontainers docker vars are in config/env.

# Interactive setup linked in from elsewhere, one file per concern, in name
# order. This repo ships none: it is where a machine's own layer plugs in, with
# whatever needs a secret or names a host -- a token wrapper, API keys.
# ~/.localprofile, below, is for hand-written per-host setup and still has the
# last word.
#
# A function only to scope nullglob: zsh aborts on a glob matching nothing, and
# bash would hand the loop the literal pattern. The files are sourced inside it,
# so their plain assignments, exports and functions are global, but `local` or
# `typeset` in them would not be -- and under zsh neither would a `setopt`,
# which local_options reverts when the function returns.
__dotfiles_source_dir() {
  if [ -n "${ZSH_VERSION-}" ]; then
    setopt local_options null_glob
  else
    # Not `$(shopt -p nullglob)`: that exits 1 when the option is off, and a
    # caller that sources this under `set -e` would die here without a word.
    local __dotfiles_nullglob=''
    shopt -q nullglob || __dotfiles_nullglob='shopt -u nullglob'
    shopt -s nullglob
  fi
  local __dotfiles_f
  for __dotfiles_f in "$1"/*.sh; do
    # shellcheck source=/dev/null
    . "$__dotfiles_f"
  done
  if [ -z "${ZSH_VERSION-}" ] && [ -n "$__dotfiles_nullglob" ]; then
    eval "$__dotfiles_nullglob"
  fi
}
__dotfiles_source_dir "${XDG_CONFIG_HOME:-$HOME/.config}/shell/profile.d"
unset -f __dotfiles_source_dir
_profile profile.d

macbook_chassis_regulatory_model() {
  ioreg -l \
    | grep '"regulatory-model-number"' \
    | sed -E 's/.*<([^>]+).*/\1/' \
    | cut -c1-10 \
    | xxd -r -p \
    | tr -d '\000'
}
_profile misc

if [[ -r /proc/cpuinfo ]] && \
  grep -qi 'raspberry pi' /proc/cpuinfo
then
  system_temperature() {
    paste \
      <(cat /sys/class/thermal/thermal_zone*/type) \
      <(cat /sys/class/thermal/thermal_zone*/temp | sed 's/\(.\)..$/.\1°C/' ) \
      | column -s $'\t' -t
    if [[ -f /sys/class/thermal/cooling_device0/cur_state ]]
    then
      echo "Fan state: $(cat /sys/class/thermal/cooling_device0/cur_state)"
    else
      echo "No fan"
    fi
  }
fi
_profile raspberry_pi

# The kimi / cursor-agent / claude env vars are all in config/env.

if [ -n "$ZSH_VERSION" ]; then
  [[ -d /opt/homebrew/share/zsh/site-functions/ ]] && fpath+=(/opt/homebrew/share/zsh/site-functions/)
fi
_profile zsh_specific

# Disable XON/XOFF flow control so Ctrl-Q/Ctrl-S work in apps like nvim
stty -ixon 2>/dev/null
# The Ghostty bin dir is on PATH via config/env.
_profile misc

# Interactive host-local setup. Plain host-local *environment* goes in
# ~/.localenv, which config/env sources for every shell.
# shellcheck source=/dev/null
[ -r "$HOME/.localprofile" ] && source "$HOME/.localprofile"
_profile local

# A *login* bash reads this file but never ~/.bashrc, so login and non-login
# interactive bash would otherwise diverge. Source it here to converge them.
# Skipped when ~/.bashrc is what sourced us, and for zsh, which has no such gap
# (a login interactive zsh reads both ~/.zprofile and ~/.zshrc).
if [ -n "${BASH_VERSION:-}" ] && [ -z "${__dotfiles_bashrc:-}" ] && [ -r "$HOME/.bashrc" ]; then
  # shellcheck source=/dev/null
  . "$HOME/.bashrc"
fi
_profile bashrc

_profile exit

# ~/bin is prepended to PATH in config/env.

# vim: foldmethod=marker filetype=sh tw=100