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
|
# Login shell configuration — sourced once per session by zsh.
# Sets environment variables, XDG paths, tool config, and host-specific overrides.
# Guard against double-sourcing (e.g. nested login shells)
[[ -n $__ZPROFILE_SOURCED ]] && return
__ZPROFILE_SOURCED=1
# ── PATH ──────────────────────────────────────────────────────────────────────
typeset -U path # deduplicate PATH entries
# shellcheck disable=SC2206 # zsh tied array; no word-splitting concerns
# Order: nix-profile (Home-Manager-provisioned tools) wins over the system
# package manager so the same HM flake delivers the same tool versions on
# both host (Arch) and VM (Ubuntu). ~/.local/bin keeps room for ad-hoc
# user scripts.
path=("$HOME/.nix-profile/bin" "$HOME/.local/bin" $path)
# ── XDG Base Directories ─────────────────────────────────────────────────────
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
export XDG_STATE_HOME="$HOME/.local/state"
export XDG_CACHE_HOME="$HOME/.cache"
# Pull in /etc/profile + /etc/profile.d/*.sh. The Arch zsh package used
# to ship /etc/zsh/zprofile doing exactly this, but we removed system
# zsh in favour of nix's zsh, so we replicate it here. This is how
# flatpak (XDG_DATA_DIRS for app launchers), nix-daemon, locale, etc.
# inject themselves into login shells. Sourced AFTER our PATH setup so
# `typeset -U path` keeps nix-profile/bin + ~/.local/bin at the front
# even if /etc/profile.d snippets try to prepend duplicates.
[[ -r /etc/profile ]] && emulate sh -c 'source /etc/profile'
# ── Locale ────────────────────────────────────────────────────────────────────
export LANG=en_US.UTF-8
# ── Terminal ──────────────────────────────────────────────────────────────────
case $TERM in
*256color|*truecolor) export COLORTERM=24bit ;;
esac
export TERMINAL='ghostty'
export BROWSER='linkhandler'
export OPENER='xdg-open'
# ── Editors ───────────────────────────────────────────────────────────────────
export EDITOR='nvim'
export VISUAL='nvim'
export DIFFPROG='nvim -d'
export MANPAGER='nvim +Man!'
export MANWIDTH=999
# ── less ──────────────────────────────────────────────────────────────────────
export LESS="-F --RAW-CONTROL-CHARS"
[[ -r /usr/bin/source-highlight-esc.sh ]] && export LESSOPEN="| /usr/bin/source-highlight-esc.sh %s"
# ── GPG / SSH ─────────────────────────────────────────────────────────────────
unset SSH_AGENT_PID
# Forwarded ssh-agent sockets live at /tmp/ssh-XXX/agent.NNN — a path
# that disappears the moment the originating ssh connection drops,
# leaving any long-running zellij pane (and its children: claude,
# nvim, etc.) pointing at a dead socket. Keep a stable
# ~/.ssh/agent.sock symlink that we re-aim on every login, and export
# the stable path so processes inherit a value that survives
# reconnects. Reattaching a zellij session after `ssh` → signing /
# git-fetch keep working without any per-pane re-export.
if [[ -n "$SSH_CONNECTION" && -S "$SSH_AUTH_SOCK" ]]; then
stable_sock="$HOME/.ssh/agent.sock"
# Only retarget if the current symlink target is dead. Sshd unlinks
# the per-connection socket file on disconnect, so [[ -S ]] on the
# resolved path is a reliable liveness probe. Avoiding gratuitous
# retargets keeps multi-connection setups stable: the first
# connection seeds the symlink, subsequent logins keep using it,
# and only if that connection drops does the next login retarget.
current_target="$(readlink "$stable_sock" 2>/dev/null)"
if [[ ! -S "$current_target" ]]; then
ln -sfn "$SSH_AUTH_SOCK" "$stable_sock"
fi
export SSH_AUTH_SOCK="$stable_sock"
unset stable_sock current_target
else
# Local login: route ssh auth through gpg-agent.
SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
export SSH_AUTH_SOCK
fi
# ── FZF ───────────────────────────────────────────────────────────────────────
export FZF_DEFAULT_COMMAND="fd --type file --follow --hidden --exclude .git --color=always"
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_DIRS_COMMAND="fd --type d --follow --hidden --exclude .git --color=always"
export FZF_DEFAULT_OPTS="--ansi --layout=reverse --inline-info --cycle --color=dark --color=fg:-1,bg:-1,hl:#5fff87,fg+:-1,bg+:-1,hl+:#ffaf5f --color=info:#af87ff,prompt:#5fff87,pointer:#ff87d7,marker:#ff87d7,spinner:#ff87d7"
export FZF_CTRL_T_OPTS="--preview 'bat --color=always --style=numbers --line-range=:500 {}' --select-1 --exit-0"
export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:hidden:wrap --bind '?:toggle-preview' --sort --exact"
export FZF_ALT_C_OPTS="--preview 'tree -C {} | head -200'"
# ── Git prompt ────────────────────────────────────────────────────────────────
export GIT_PS1_SHOWDIRTYSTATE=1
export GIT_PS1_SHOWSTASHSTATE=1
unset GIT_PS1_SHOWUNTRACKEDFILES
export GIT_PS1_SHOWUPSTREAM="verbose"
export GIT_PS1_SHOWCONFLICTSTATE="yes"
export GIT_PS1_DESCRIBE_STYLE="branch"
export GIT_PS1_SHOWCOLORHINTS=1
export GIT_PS1_HIDE_IF_PWD_IGNORED=1
# ── GCC ───────────────────────────────────────────────────────────────────────
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# ── Java ──────────────────────────────────────────────────────────────────────
# System AA fonts, GTK L&F, XDG prefs dir, GTK2 for compatibility
export _JAVA_OPTIONS="-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel -Djava.util.prefs.userRoot=$XDG_CONFIG_HOME/java -Djdk.gtk.version=2"
# Fix for non-reparenting WMs (sway, dwm, etc.)
export _JAVA_AWT_WM_NONREPARENTING=1
# ── Miscellaneous ─────────────────────────────────────────────────────────────
export QT_QPA_PLATFORMTHEME=qt6ct
export NO_AT_BRIDGE=1 # suppress GTK accessibility bus warnings
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
export INPUTRC="$XDG_CONFIG_HOME/sh/inputrc"
# ── Wayland ───────────────────────────────────────────────────────────────────
export XDG_CURRENT_DESKTOP=sway
# Prefer Wayland for Qt; fall back to xcb for apps without qt{5,6}-wayland.
export QT_QPA_PLATFORM="wayland;xcb"
# SDL2 defaults to X11; force Wayland with X11 fallback. SDL3 ignores this.
export SDL_VIDEODRIVER="wayland,x11"
# ── XDG cleanup: keep $HOME tidy ─────────────────────────────────────────────
# https://wiki.archlinux.org/title/XDG_Base_Directory#Partial
export CARGO_HOME="$XDG_DATA_HOME/cargo"
export CUDA_CACHE_PATH="$XDG_CACHE_HOME/nv"
export GOPATH="$XDG_DATA_HOME/go"
export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
export NODE_REPL_HISTORY="$XDG_DATA_HOME/node_repl_history"
export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
export RUFF_CACHE_DIR="$XDG_CACHE_HOME/ruff"
export RUSTUP_HOME="$XDG_DATA_HOME/rustup"
export WGETRC="$XDG_CONFIG_HOME/wget/wgetrc"
export WINEPREFIX="$XDG_DATA_HOME/wineprefixes/default"
# ── Host-specific ─────────────────────────────────────────────────────────────
case $(uname -n) in
halley2)
export LIBVA_DRIVER_NAME="iHD"
export MESA_LOADER_DRIVER_OVERRIDE="iris"
export VAAPI_MPEG4_ENABLED=true
;;
hercules)
export OCL_ICD_VENDORS=nvidia
# shellcheck disable=SC1091 # optional, loaded only on hosts that have it
[[ -r "$XDG_CONFIG_HOME/sh/work-envrc" ]] && source "$XDG_CONFIG_HOME/sh/work-envrc"
;;
esac
# ── SSH: inhibit suspend/idle while connected ────────────────────────────────
# Backstop for SSH sessions that don't use zellij. (The dedicated
# zellij-inhibit-suspend.path user unit already covers any host that has
# at least one live zellij session — that one survives detach/disconnect,
# which this in-shell inhibitor does not.)
#
# Wrap the login shell in `systemd-inhibit` so a lock is held for the
# entire SSH session lifetime; the lock is released the instant the
# shell exits.
if [[ -n $SSH_CONNECTION && -z $__SSH_SUSPEND_INHIBITED ]] \
&& command -v systemd-inhibit >/dev/null 2>&1; then
export __SSH_SUSPEND_INHIBITED=1
exec systemd-inhibit \
--what=sleep:idle:handle-lid-switch \
--who="ssh:${USER}@${HOST}" \
--why="active SSH session from ${SSH_CONNECTION%% *}" \
--mode=block \
"$SHELL" -l
fi
# ── Auto-start sway on VT1 ────────────────────────────────────────────────────
if [[ -z $WAYLAND_DISPLAY && $XDG_VTNR == 1 ]]; then
export XDG_SESSION_TYPE=wayland
exec sway
fi
|