]> git.sev.monster Git - dotfiles.git/blob - etc/zsh/.zshenv
zsh: path logic fixes, better duplicate detection
[dotfiles.git] / etc / zsh / .zshenv
1 ### functions
2 ## cleanup
3 # XXX: only call after relevant vars have been set up, defined early so that
4 #      below code can utilize it after they do so
5 function _sev_zcleanup {
6     local x y
7
8     function _sev_checkpid {
9         # return 1 if pid is a zsh process that isn't us
10         # NOTE: would love to use ps -p but busybox doesnt support it :DDD
11         [[ $$ -eq $1 ]] || { ! kill -0 $1 2>/dev/null ||
12           [[ ${$(ps -Aopid=,comm= | awk '$1 == '$1' {print $2}'):t} != zsh ]] }
13     }
14     # gpg forwarding
15     if [[ -d $_sev_gpg_forward_dir && ( -z $1 || $1 == 'gpg-forward' ) ]] {
16         # clean up forward dirs if its session is dead or we ask for it
17         find $_sev_gpg_forward_dir -mindepth 1 -maxdepth 1 -type d |
18           while {read -r x} {
19             # NOTE: called before setup and on logout: remove the dir we
20             #       will be using (it's stale) or the dir we did use, and any
21             #       dead sessions if present
22             if {_sev_checkpid ${x:t}} {
23                 find $x -mindepth 1 -maxdepth 1 | while {read -r y} {
24                     # XXX: real dirs will stop unlink, consider it a feature
25                     unlink $y
26                 }
27                 # don't force in case something important is still there
28                 rmdir $x
29             }
30         }
31         # reset GNUPGHOME if we removed our own dir
32         if [[ $GNUPGHOME =~ '/.ssh_forward/\d+/*$' && ! -d $GNUPGHOME ]]
33             GNUPGHOME=${GNUPGHOME%$MATCH}
34     }
35
36     # custom tmp
37     # NOTE: _sev_tmp is not unset so session dirs will not be recreated if
38     #       called during runtime; unset _sev_tmp and re-source to fix
39     # NOTE: XDG dirs that use our tmp are not unset here, they are in zlogout
40     #       after this function is called
41     if [[ -d $_sev_tmp && ( -z $1 || $1 == 'tmp' ) ]] {
42         # clean up tmp dirs if its session is dead or we ask for it
43         find -L $_sev_tmp -mindepth 1 -maxdepth 1 -name '.session.*' -type d |
44           while {read -r x} {
45             # NOTE: same rationale as above
46             if {_sev_checkpid ${${x:t}#.session.}} {
47                 rm -rf $x
48             }
49         }
50     }
51
52     unfunction _sev_checkpid
53 }
54
55 function _sev_setpath {
56     # add as many generic paths as possible to keep the order we want
57     # NOTE: tied arrays path and fpath already exist, but are not unique (-U);
58     #       we utilize the fact that unique arrays keep the first occurrence
59     #       and remove any further occurrences to check for elements from the
60     #       old PATH that we did not anticipate and shift them to the front,
61     #       since they are probably important to the system
62     typeset -gU path fpath
63     local -a syspath=("$path[@]%%/")
64     # NOTE: /usr/{pkg,games} are unix/bsdisms
65     # NOTE: some systems (esp. research machines) may have multiple versions of
66     #       packages installed in /opt/[pkg]/[ver]/bin or other dirs, managed
67     #       with something like Environment Modules. this code does not account
68     #       for this type of usage and may end up adding multiple versions of
69     #       the same program to PATH. any undesired paths can be removed using
70     #       .zshenv.local. (if you're crazy enough to be using this code on a
71     #       research machine and are debugging your PATH, do send me an email,
72     #       I'd love to hear about your situation!)
73     # NOTE: on systems using unexpected paths for binaries (we call them
74     #       "system paths" here), such paths will be shunted to the beginning
75     #       of the list before the expected paths. this can cause problems if a
76     #       system path contains programs that elide programs from an expected
77     #       path that do not run properly. for example, under Termux, there is
78     #       a wrapper for /bin/pm under $PREFIX/bin/pm that allows pm to be
79     #       used by the Termux unprivileged user, where pm can normally only be
80     #       run by a privileged user such as shell. (N.B.: modern Termux does
81     #       not add /system/bin to PATH by default, this is just an example—
82     #       and we add $PREFIX/bin before /bin (link to /system/bin) anyway).
83     # XXX: PREFIX not validated—it's non-POSIX but Termux uses it, maybe others
84     # XXX: XDG specifies ~/.local/bin as the only user-writable dir for
85     #      executables, but we specify more; technically this is against spec
86     path=(
87         {{${_sev_home:-~},~}{/.local,},{${PREFIX%%/},}{,/usr{,/local,/pkg},/opt{,/*{/*,}}}}/{s,}bin(/N^M)
88         {${PREFIX%%/},}/usr/{X11R{7,6}/bin,games}(/N^M)
89         # emulate Arch Linux flatpak-bindir.sh for use on other systems
90         {${${XDG_DATA_HOME:-~/.local/share}%%/},{${PREFIX%%/},}/var/lib}/flatpak/exports/bin(/N^M)
91     )
92     local -i i len=$#path
93     path+=("${syspath[@]%%/}")
94     # remove bad paths... after having combined the arrays to remove duplicates
95     for (( i = 1; i <= $#path; i++ )) {
96         if [[ ! -d $path[$i] ]] {
97             path[$i]=()
98             ((i <= len)) && ((len--))
99             ((i--))
100             continue
101         }
102     }
103     # shift valid system paths to the front if there are any left
104     ((len > 0 && len < $#path)) && path=("${(@)path[len + 1, -1]}" "${(@)path[1, len]}")
105
106     # include our zsh dir in fpath. unlike above, we always prefer our paths
107     fpath=(
108         {${ZDOTDIR:-{${_sev_home:-~},~}/.zsh},{${_sev_home:-~},~}/.zsh}/functions/**/*(/N^M)
109         "${fpath[@]%%/}"
110     )
111     # remove bad paths
112     for (( i = 1; i <= $#fpath; i++ )) {
113         if [[ ! -d $fpath[$i] ]] {
114             fpath[$i]=()
115             ((i--))
116             continue
117         }
118     }
119     # FPATH is not exported by default
120     export FPATH
121
122     # un-unique system arrays as they are by default
123     typeset +U path fpath
124 }
125
126 ### common exports
127 export CHARSET=${CHARSET:-UTF-8}
128 export LANG=${LANG:-en_US.UTF-8}
129
130 ## alternative home for pulling in bin & config, used for zsu
131 [[ -v _sev_home ]] || export _sev_home=$HOME
132
133 ## fix broken term
134 # NOTE: we do this here instead of .zshrc since we might print stuff
135 if [[ -t 1 ]] { # only if stdout is tty
136     [[ ! -v TERM ]] && export TERM=xterm-256color >/dev/null 2>&1
137     if [[ $#terminfo -eq 0 ]] {
138         _oldterm=$TERM
139         export TERM=xterm >/dev/null 2>&1
140         [[ -o interactive ]] &&
141           print -P "%F{red}!!! Can't find terminfo for $_oldterm, using $TERM%f"
142         unset _oldterm
143     }
144 }
145
146 ## path
147 if [[ ! -v _sev_setup_path || -o login ]] {
148     _sev_setpath
149     # NOTE: do not set _sev_setup_path, it is set in zprofile
150 }
151
152 ### home dir setup & additional exports
153 # XXX: traditionally, zshenv should just contain exports, and not touch the
154 #      filesystem. however, our TMPDIR and XDG vars rely on mutable user paths
155 #      that may not exist, and as such need to be set up before the rest of the
156 #      system can use them. this is important as some environments include code
157 #      in the global zprofile, or source scripts of other shells in the global
158 #      zprofile, that may rely on our desired dir structure and vars pointing
159 #      to it. for example, `flatpak-bindir.sh` in the Arch Linux flatpak
160 #      package references $XDG_DATA_HOME with no fallback. since we do special
161 #      handling for these vars before we export them, we're forced to do it all
162 #      here instead of at the top of the zprofile.
163
164 ## xdg local dir
165 # NOTE: need this for tmp, so confirm it exists.
166 # XXX: perms are not specified for XDG dirs except runtime. 760 makes the most
167 #      sense, but we need to be a bit more permissive for zsu.
168 [[ -e ~/.local ]] && chmod 755 ~/.local || mkdir -pm766 ~/.local
169
170 ## tmp
171 # NOTE: specs say that POSIX tmp and XDG runtime directories should exist
172 #       until the last session is logged out (POSIX can exist for longer).
173 #       since we can't reliably keep track of sessions in a cross-platform
174 #       manner, the current implementation should use a separate directory per
175 #       toplevel session (i.e. SHLVL=1). this should placate most applications,
176 #       though it is not expressly spec compliant. this may also cause problems
177 #       with disowned applications that still try to use the directories after
178 #       the toplevel shell has already logged out and the dirs removed, but the
179 #       chances of that are slim. this also needs to be adjusted for usermode
180 #       Xorg, as it requires $PREFIX/tmp/.X11-unix on most installs.
181 if [[ ! -v _sev_tmp ]] {
182     _sev_tmp=~/.local/tmp
183     # create personal TMPDIR under system tmp
184     # NOTE: under proot with uid remapping and shared /tmp, we can reuse old
185     #       dir, without worrying about permission issues; intended for termux.
186     _t=${TMPDIR:-${TEMPDIR:-${TEMP:-${TMP:-${${TMPPREFIX%/zsh}:-/tmp}}}}}/.home-${_sev_proot_real_user:-$LOGNAME}
187     [[ -e $_t ]] || mkdir -m700 $_t 2>/dev/null
188     if [[ ! -d $_t ]] {
189         # fallback TMPDIR to bare local directory or existing softlink
190         [[ -o interactive ]] &&
191           print -P "%F{orange}*** Can't create tmp dir $_t, using $_sev_tmp%f"
192         [[ -h $_sev_tmp && ! -d _sev_tmp ]] && unlink $_sev_tmp 2>/dev/null
193         [[ ! -e $_sev_tmp ]] && mkdir -m700 $_sev_tmp 2>/dev/null
194         if [[ ! -d $_sev_tmp ]] {
195             _sev_tmp=${$(mktemp 2>/dev/null):-/tmp}
196             [[ -o interactive ]] &&
197               print -P "%F{red}!!! Can't create tmp dir, using $_sev_tmp%f"
198         }
199     } elif [[ -e $_sev_tmp && ! -h $_sev_tmp ]] {
200         # non-softlink node is on our local dir
201         [[ -o interactive ]] &&
202           print -P "%F{orange}*** $_sev_tmp exists, can't link to tmp dir $_t, ignoring it%f"
203         _sev_tmp=$_t
204     } else {
205         if [[ ! -v $_sev_tmp_keep_link && -h $_sev_tmp && $_sev_tmp:P != $_t:P ]] {
206             [[ -o interactive ]] &&
207               print -P "%F{orange}*** $_sev_tmp links to ${_sev_tmp:P} and not ${t:P}, unlinking it%f"
208             # NOTE: ln -f doesn't seem to work reliably with softlink
209             #       directories, so explicitly remove the target if it exists
210             # XXX: potential race condition
211             # TODO: handle cleanup of old dir if it doesn't match?
212             unlink $_sev_tmp 2>/dev/null
213         }
214         if [[ ! -e $_sev_tmp ]] {
215             # link local dir to tmp dir
216             ln -s $_t $_sev_tmp 2>/dev/null
217         }
218     }
219     # ensure dir is clean
220     _sev_zcleanup tmp
221     # finally create our subdir for this session
222     _t=$_sev_tmp/.session.$$
223     # XXX: we probably shouldn't allow the use of an existing dir—tmp dir for
224     #      current pid should have been removed by zcleanup
225     if {[[ ! -d $_t ]] && ! mkdir -m700 $_t 2>/dev/null} {
226         [[ -o interactive ]] &&
227           print -P "%F{red}!!! Can't create session tmp subdir $_t, using $_sev_tmp%f"
228         _t=$_sev_tmp
229     }
230     export _sev_tmp TMPDIR=$_t TEMPDIR=$_t TEMP=$_t TMP=$_t TMPPREFIX=$_t/zsh
231     unset _t
232 }
233
234 ## xdg
235 if [[ ! -v _sev_setup_xdg ]] {
236     ## merge with any existing dirs and remove duplicates using unique arrays
237     # NOTE: we are accepting whatever value might be set for CONFIG and DATA;
238     #       if it wasn't set, we just use default and leave it unset
239
240     # source user dirs before other vars; technically it is against spec to
241     # include any of the below dirs there, but you never know what crazy shit
242     # people will do. I rather handle them sanely with our own code than let
243     # them override after the fact.
244     [[ -f $XDG_CONFIG_HOME/user-dirs.dirs ]] &&
245       emulate sh -c "source $XDG_CONFIG_HOME/user-dirs.dirs"
246
247     [[ -v XDG_DATA_HOME ]] && export XDG_DATA_HOME
248     [[ -e ${XDG_DATA_HOME:-~/.local/share} ]] ||
249       mkdir -m760 ${XDG_DATA_HOME:-~/.local/share}
250
251     typeset -xUT XDG_DATA_DIRS xdg_data_dirs
252     xdg_data_dirs=(${XDG_DATA_DIRS:+${xdg_data_dirs%%/}}
253       {${PREFIX%%/},}/{usr{,/local,/pkg},opt{,/*{/*,}}}/share(N^M))
254     xdg_data_dirs=($xdg_data_dirs(/N))
255     ((${#xdg_data_dirs} == 0)) && unset XDG_DATA_DIRS
256
257     [[ -v XDG_CONFIG_HOME ]] && export XDG_CONFIG_HOME
258     [[ -e ${XDG_CONFIG_HOME:-~/.config} ]] ||
259       mkdir -m760 ${XDG_CONFIG_HOME:-~/.config}
260     # I am of the belief .local should follow FHS /usr/local...
261     [[ -e ~/.local/etc ]] || ln -s ${XDG_CONFIG_HOME:-~/.config} ~/.local/etc
262
263     typeset -xUT XDG_CONFIG_DIRS xdg_config_dirs
264     xdg_config_dirs=(${XDG_CONFIG_DIRS:+${xdg_config_dirs%%/}}
265       {${PREFIX%%/},}{,/usr{,/local,/pkg},opt{,/*{/*,}}}/etc/xdg(N^M))
266     xdg_config_dirs=($xdg_config_dirs(/N))
267     ((${#xdg_config_dirs} == 0)) && unset XDG_CONFIG_DIRS
268
269     [[ -v XDG_STATE_HOME ]] && export XDG_STATE_HOME
270     [[ -e ${XDG_STATE_HOME:-~/.local/state} ]] ||
271       mkdir -m760 ${XDG_STATE_HOME:-~/.local/state}
272
273     if [[ -v XDG_CACHE_HOME ]] {
274         export XDG_CACHE_HOME
275     } else {
276         export XDG_CACHE_HOME=$_sev_tmp/.xdg.cache
277     }
278     [[ -e $XDG_CACHE_HOME ]] ||
279       mkdir -m700 $XDG_CACHE_HOME
280
281     # NOTE: this can be set by systemd or other pre-shell supervisor, and if
282     #       any services were started such as pipewire, we need to use the
283     #       existing runtime dir to preserve runtime state
284     if [[ -v XDG_RUNTIME_DIR ]] {
285         export XDG_RUNTIME_DIR
286     } else {
287         # make runtime dir in our session-specific tmpdir
288         export XDG_RUNTIME_DIR=$TMPDIR/.xdg.runtime
289         # should be unique—same as in tmpdir creation, ensure dir doesn't exist
290         if [[ -h $XDG_RUNTIME_DIR ]] {
291             unlink $XDG_RUNTIME_DIR 2>/dev/null
292         } elif [[ -e $XDG_RUNTIME_DIR ]] {
293             rm -rf $XDG_RUNTIME_DIR 2>/dev/null
294         }
295     }
296     [[ -e $XDG_RUNTIME_DIR ]] ||
297       mkdir -m700 $XDG_RUNTIME_DIR 2>/dev/null
298
299     export _sev_setup_xdg=
300 }
301
302 ### app setup & exports
303 # NOTE: we set these up here since some scripts might need them
304 ## gpg home
305 if [[ ! -v GNUPGHOME ]] {
306     export GNUPGHOME=${XDG_CONFIG_HOME:-~/.config}/gnupg
307     # move existing gnupg dir to our new home
308     if [[ -d ~/.gnupg && ! -d $GNUPGHOME ]] {
309         mv ~/.gnupg $GNUPGHOME
310     }
311 }
312
313 ## perl local lib
314 if [[ ! -v PERL_LOCAL_LIB_ROOT && -v commands[perl] ]] {
315     _p5=${XDG_DATA_HOME:-~/.local/share}/perl5
316     [[ -d $_p5 ]] || mkdir -p $_p5
317     if [[ -f $_p5/lib/perl5/local/lib.pm ]] {
318         eval $(perl -I$_p5/lib/perl5 -Mlocal::lib=$_p5 2>/dev/null)
319     } else {
320         # emulate local::lib if not installed
321         path=($_p5/bin "${path[@]}")
322         export \
323           PERL_MB_OPT="--install_base '$_p5'" \
324           PERL_MM_OPT=INSTALL_BASE=$_p5 \
325           PERL5LIB=$_p5/lib/perl5 \
326           PERL_LOCAL_LIB_ROOT=$_p5${PERL_LOCAL_LIB_ROOT:+:$PERL_LOCAL_LIB_ROOT}
327     }
328     unset _p5
329 }
330 ## go
331 if [[ -v commands[go] ]] {
332     [[ ! -v GOPATH ]] && export GOPATH=${XDG_DATA_HOME:-~/.local/share}/go:~/go
333     [[ ! -v GOBIN  ]] && export GOBIN=~/.local/bin
334 }
335
336 ### plugins
337 autoload -Uz load-plugins
338 load-plugins zshenv
339
340 ### load zshenv site-specific
341 autoload -Uz load-site-dotfile
342 load-site-dotfile zshenv
343
344 ### source .zprofile early for non-login shells that should be
345 if [[ ! -v _sev_first_display && ( -v DISPLAY || -v WAYLAND_DISPLAY ) ]] {
346     # most graphical login/session managers will spawn the user's shell as a
347     # parent of all child processes for that session. however, if the parent
348     # shell isn't a login shell for some reason, our .zprofile won't be run,
349     # and the environment won't be configured for child processes.
350     #
351     # XXX: .zprofile will be sourced by every new child shell if zsh is not
352     #      used to start the graphical session and the _sev_first_display var
353     #      isn't exported; for example, this previously happened when using
354     #      sway without a display manager in front of it to run a login shell.
355     #
356     #      this issue is not mitigated by .zprofile only loading what has not
357     #      already been loaded if the env vars preventing the load are not set;
358     #      in that case, every shell will think it is a fresh login shell.
359
360     # update gpgagent to use graphical pinentry
361     # XXX: will steal display from any other logged in graphical sessions, but
362     #      I consider this to be an unlikely scenario
363     _sev_refresh_gpgagent=
364
365     export _sev_first_display=
366     [[ ! -o login ]] && source ${ZDOTDIR:-~}/.zprofile
367 } elif [[ ${+TERMUX_VERSION} -eq 0 && ! -o login && $SHLVL -eq 1 ]] {
368     # Termux first process isn't login shell, so source early
369     source ${ZDOTDIR:-~}/.zprofile
370 }
This page took 0.061589 seconds and 4 git commands to generate.