nanuqsaurus/scripts/shell-init.sh
randogoth 9f28569fef shell-init: record the first command of every shell in atuin
bash-preexec arms its DEBUG trap from __bp_interactive_mode at the end of
PROMPT_COMMAND, but its lazy installer runs mid-way through the first prompt
because zoxide and ghostty append their hooks after it. Those hooks disarm the
trap again, so the first interactive command of every session never reached
preexec and atuin silently dropped it.

Arm from PS0 instead, which is expanded once per interactive command before the
DEBUG trap and is therefore independent of PROMPT_COMMAND ordering.

Also guard the script against running twice: it is installed as both
loginShellInit and interactiveShellInit, and /etc/profile sources /etc/bashrc,
which registered starship's precmd/preexec hooks twice in every login shell.
2026-08-28 16:06:09 +03:00

142 lines
4.3 KiB
Bash

# ---- logging ---------------------------------------------------------------
log_shell_init() {
local uid="${UID:-}"
[[ -n "$uid" ]] || uid="$(id -u 2>/dev/null || echo unknown)"
# Override with:
# export NANUQSAURUS_SHELL_INIT_LOG_FILE=/path/to/log
local log_file="${NANUQSAURUS_SHELL_INIT_LOG_FILE:-/tmp/shell-init-${uid}.log}"
printf '%s\n' "shell-init: $*" >>"$log_file" 2>/dev/null || true
}
# ---- helpers ---------------------------------------------------------------
have() { command -v "$1" >/dev/null 2>&1; }
is_interactive() {
[[ -t 1 || -n ${PS1:-} ]]
}
# ---- environment sanity ----------------------------------------------------
if [[ -z ${TERM:-} || $TERM == dumb ]]; then
export TERM=xterm-256color
fi
INTERACTIVE=0
is_interactive && INTERACTIVE=1
log_shell_init "TERM=$TERM PS1=${PS1:-<empty>} SHELL=$SHELL interactive=$INTERACTIVE"
# Ensure PS1 exists so downstream init blocks run
if (( INTERACTIVE )) && [[ -z ${PS1:-} ]]; then
PS1='\\s-\\v\\$ '
fi
(( INTERACTIVE )) || return 0
# ---- run-once guard --------------------------------------------------------
# Installed as both loginShellInit (/etc/profile) and interactiveShellInit
# (/etc/bashrc), and /etc/profile sources /etc/bashrc, so a login shell would run
# this twice and register starship's hooks twice. Not exported: child shells must
# initialise on their own.
if [[ -n ${NANUQSAURUS_SHELL_INIT_DONE:-} ]]; then
log_shell_init "already initialised, skipping"
return 0
fi
NANUQSAURUS_SHELL_INIT_DONE=1
# ---- bash-preexec ----------------------------------------------------------
if [[ -n ${BASH_VERSION:-} && -z ${bash_preexec_imported:-} && -r "@BASHPREEXEC@" ]]; then
# shellcheck disable=SC1090
. "@BASHPREEXEC@"
log_shell_init "bash-preexec loaded"
fi
# bash-preexec arms its DEBUG trap from __bp_interactive_mode, which it appends to
# the END of PROMPT_COMMAND -- but its lazy installer runs in the MIDDLE of the
# first prompt, because zoxide (below) and ghostty (~/.bashrc) append their hooks
# after it. Those hooks then disarm the trap again, so the first command of every
# session never reaches preexec and atuin never records it. PS0 is expanded once
# per interactive command, before the DEBUG trap, so arming there is independent of
# PROMPT_COMMAND ordering. Needs the non-forking ${ ...; } funsub (bash >= 5.3);
# older bash keeps the previous behaviour.
if [[ -n ${bash_preexec_imported:-} ]] &&
((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then
# shellcheck disable=SC2016
__nanuq_bp_arm='${ __bp_preexec_interactive_mode=on; }'
[[ ${PS0-} == *"$__nanuq_bp_arm"* ]] || PS0=$__nanuq_bp_arm${PS0-}
unset __nanuq_bp_arm
log_shell_init "bash-preexec PS0 arming enabled"
fi
# ---- starship --------------------------------------------------------------
if have @STARSHIP@; then
log_shell_init "starship init"
case $SHELL in
*/bash) eval "$(@STARSHIP@ init bash)" ;;
*/zsh) eval "$(@STARSHIP@ init zsh)" ;;
esac
# registers starship_precmd/starship_preexec_all with bash-preexec directly
else
log_shell_init "starship skipped"
fi
# ---- atuin -----------------------------------------------------------------
if have @ATUIN@; then
log_shell_init "atuin init"
if [[ -z ${ATUIN_SESSION:-} ]]; then
export ATUIN_SESSION="$(@ATUIN@ uuid)"
ATUIN_HISTORY_ID=""
fi
case $SHELL in
*/bash) eval "$(@ATUIN@ init bash)" ;;
*/zsh) eval "$(@ATUIN@ init zsh)" ;;
esac
# registers __atuin_preexec/__atuin_precmd with bash-preexec itself
else
log_shell_init "atuin skipped"
fi
# ---- zoxide ---------------------------------------------------------------
if have zoxide; then
log_shell_init "zoxide init"
case $SHELL in
*/bash) eval "$(zoxide init bash)" ;;
*/zsh) eval "$(zoxide init zsh)" ;;
esac
# appends __zoxide_hook to PROMPT_COMMAND; see the PS0 note above
else
log_shell_init "zoxide skipped"
fi
# ---- aliases --------------------------------------------------------------
if have eza; then
alias ls='eza'
alias ll='eza -l --icons=auto --group-directories-first'
alias l1='eza -1'
alias l.='eza -d .*'
fi
if have ug; then
alias grep='ug'
alias egrep='ug -E'
alias fgrep='ug -F'
alias xzgrep='ug -z'
alias xzegrep='ug -zE'
alias xzfgrep='ug -zF'
fi
if have bat; then
alias cat='bat --style=plain --pager=never' 2>/dev/null
fi