summaryrefslogtreecommitdiff
path: root/config/profile
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-09-17 19:45:23 +0100
committerIgor Soarez <igor@soarez.org>2026-09-17 19:45:23 +0100
commit13cc065ec014b86369dfe4ad60d1a43fa06fd251 (patch)
tree684690c61b7b59758c2aac132c65b81c86de0608 /config/profile
dotfiles
Shell, editor and terminal configuration, the tools that go with them, and szecret, an age-based secret manager whose store lives elsewhere. History restarts here. What came before was not written to be read by anyone else, and carried machine names, addresses and an encrypted secret store; it is kept privately instead. What is left is meant to be safe on any machine, including one whose owner is not me: see CLAUDE.md for the rule it is written to.
Diffstat (limited to 'config/profile')
-rwxr-xr-xconfig/profile357
1 files changed, 357 insertions, 0 deletions
diff --git a/config/profile b/config/profile
new file mode 100755
index 0000000..8f63f9e
--- /dev/null
+++ b/config/profile
@@ -0,0 +1,357 @@
+# 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
+
+# detach from tmux by default
+# Guard at call time, not definition time.
+# functions and sources them in non-interactive tool shells that inherit
+# $TMUX, where a bare `exit` must really exit (not detach the terminal).
+exit() {
+ if [[ -n "${TMUX:-}" ]] && [[ $- == *i* ]]; then
+ tmux detach
+ else
+ builtin exit "$@"
+ fi
+}
+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