]> git.sev.monster Git - dotfiles.git/blob - etc/zsh/.zprofile
Revert "replace modeline with editorconfig; small fixes"
[dotfiles.git] / etc / zsh / .zprofile
1 # NOTE:
2 # our .zprofile can be expensive, so we keep track of what has been run
3 # already, and only set up what is necessary. additionally, we want to ensure
4 # that our environment is set up as early as possible, so we also source
5 # .zprofile in .zshenv for new non-login shells.
6 #
7 # these issues are handled by using these methods:
8 #   * the parent shell that starts the user's session after logging in to some
9 #     graphical environments may not be a login shell—due to misconfiguration
10 #     or otherwise—which means .zprofile is not ran and the environment is not
11 #     properly configured for any child processes.
12 #   * some desktop environments/graphical terminal emulators will start new
13 #     terminal windows with login shells, which runs .zprofile every time and
14 #     leads to noticably slow startup times if we have not already ran it.
15
16 ### cleanup
17 # XXX: only call after relevant vars have been set up, defined early so that
18 #      below code can utilize it after they do so
19 function _sev_zcleanup {
20     ## gpg forwarding
21     if [[ -d $_sev_gpg_forward_dir && ( -z $1 || $1 == 'gpg-forward' ) ]] {
22         # clean up forward dirs if its session is dead or we ask for it
23         find $_sev_gpg_forward_dir -mindepth 1 -maxdepth 1 -type d |
24           while {read -r x} {
25             # NOTE: the only way we can get here is if we have not been
26             #       forwarded before, if the user asks for it, or during
27             #       logout. if our own pid already has a dir, it is most likely
28             #       stale, the user wants it removed, or something is very
29             #       broken—in all 3 of these cases the best choice is remove it.
30             p=$(basename $x)
31             if {[[ -v _sev_gpg_forward_clean || $$ == $p ]] ||
32                     ! kill -0 $p 2>/dev/null} {
33                 find $x -mindepth 1 -maxdepth 1 | while {read -r y} {
34                     # XXX: real dirs will stop unlink, consider it a feature
35                     unlink $y
36                 }
37                 # don't force in case something important is still there
38                 rmdir $x
39             }
40         }
41         # reset GNUPGHOME if we removed our own dir
42         if [[ $GNUPGHOME =~ '/.ssh_forward/\d+/*$' && ! -e $GNUPGHOME ]]
43             GNUPGHOME=${GNUPGHOME%$MATCH}
44     }
45
46     ## tmp
47     # NOTE: _sev_tmp is not unset so session dirs will not be recreated
48     # NOTE: XDG dirs that use our tmp are not unset here, they are in zlogout
49     if [[ -d $_sev_tmp && ( -z $1 || $1 == 'tmp' ) ]] {
50         # clean up tmp dirs if its session is dead or we ask for it
51         find $_sev_tmp -mindepth 1 -maxdepth 1 -name '.session.*' -type d |
52           while {read -r x} {
53             # NOTE: same rationale as above
54             p=${$(basename $x)#.session.}
55             if {[[ -v _sev_tmp_clean || $$ == $p ]] ||
56                     ! kill -0 $p 2>/dev/null} {
57                 rm -rf $x
58             }
59         }
60     }
61
62     unset x p y
63 }
64
65 ### lang
66 export CHARSET=${CHARSET:-UTF-8}
67 export LANG=${LANG:-en_US.UTF-8}
68
69 ### path
70 # NOTE: we utilize the fact that unique arrays keep the first occurrence and
71 #       remove any further occurences to capture elements from the old PATH
72 #       that we did not anticipate and shift them to the front, since they are
73 #       probably important to the system
74 if [[ ! -v _sev_setup_path || -o login ]] {
75     typeset -U path fpath
76     # add as many generic paths as possible to keep the order we want
77     # NOTE: /usr/{local,pkg,games} are unix/bsdisms
78     # XXX: PREFIX not validated, non-posix but Termux uses it, maybe others
79     # XXX: XDG specifies ~/.local/bin as the only user-writable dir for
80     #      executables, but we specify more; technically this is against spec
81     syspath=("$path[@]")
82     path=({{${_sev_home:-~},~}{/.local,},{$PREFIX,}{,/opt,/usr{,/local,/pkg}}}/{s,}bin
83           /usr/{X11R{7,6}/bin,games})
84     ((len=$#path))
85     path=("$path[@]" "$syspath[@]")
86     # remove nonexistent and duplicate paths
87     for (( i = 1; i <= $#path; i++ )) {
88         if [[ ! -e $path[$i] ]] {
89             path[$i]=()
90             ((i <= len)) && ((len--))
91             ((i--))
92             continue
93         }
94     }
95     # shift valid system paths to the front if there are any left
96     ((len > 0 && len < $#path)) && path=("${(@)path[len + 1, -1]}" "${(@)path[1, len]}")
97     unset syspath len i
98     # include our zsh dir in fpath. unlike above, we always prefer our paths
99     fpath=(${ZDOTDIR:-~/.zsh}/functions/{*,Completions/*}(N) "$fpath[@]")
100     # FPATH is not exported by default
101     export FPATH
102     typeset +U path fpath
103     export _sev_setup_path=
104 }
105
106 ### xdg local dir
107 # NOTE: need this for tmp, so confirm it exists.
108 # XXX: perms are not specified for XDG dirs except runtime, but I think 760
109 #      makes the most sense. shouldn't break anything since no one else should
110 #      be poking around in our dir.
111 [[ -e ${_sev_home:-~}/.local ]] || mkdir -m760 ${_sev_home:-~}/.local
112
113 ### tmp
114 # NOTE: specs say that POSIX tmp and XDG runtime directories should exist
115 #       until the last session is logged out (POSIX can exist for longer).
116 #       since we can't reliably keep track of sessions in a cross-platform
117 #       manner, the current implementation should use a separate directory per
118 #       toplevel session (i.e. SHLVL=1). this should placate most applications,
119 #       though it is not expressly spec compliant.
120 if [[ ! -v _sev_tmp ]] {
121     _sev_tmp=${_sev_home:-~}/.local/tmp
122     # NOTE: race condition/remove in use files
123     [[ -h $_sev_tmp ]] && unlink $_sev_tmp 2>/dev/null
124     t=${TMPDIR:-${TEMP:-${TMP:-/tmp}}}/.home-$LOGNAME
125     # create personal tmp dir under system tmp
126     [[ -e $t ]] || mkdir -m700 $t 2>/dev/null
127     if [[ ! -d $t ]] {
128         [[ -o interactive ]] &&
129           print -P "%F{orange}*** Can't create TMPDIR $t, using $_sev_tmp%f"
130         # fallback bare directory
131         [[ -e $_sev_tmp ]] || mkdir -m700 $_sev_tmp 2>/dev/null
132         if [[ ! -d $_sev_tmp ]] {
133             [[ -o interactive ]] &&
134               print -P "%F{red}!!! No usable TMPDIR%f"
135             unset _sev_tmp
136         } else {
137             t=$_sev_tmp
138         }
139     } elif [[ -e $_sev_tmp ]] {
140         [[ -o interactive ]] &&
141           print -P "%F{orange}*** $_sev_tmp occluded, can't link to TMPDIR $t%f"
142         _sev_tmp=$t
143     } else {
144         ln -s $t $_sev_tmp 2>/dev/null
145     }
146     if [[ -v _sev_tmp ]] {
147         # ensure dir is clean
148         _sev_zcleanup tmp
149         # finally create our subdir for this session
150         t=$_sev_tmp/.session.$$
151         if ! mkdir -m700 $t 2>/dev/null; then
152             [[ -o interactive ]] &&
153               print -P "%F{red}!!! Can't create session subdir $t, using $_sev_tmp%f"
154             t=$_sev_tmp
155         fi
156         export _sev_tmp TMPDIR=$t TEMP=$t TMP=$t
157         unset t
158     }
159 }
160
161 ### xdg
162 if [[ ! -v _sev_setup_xdg ]] {
163     ## merge with any existing dirs and remove duplicates using unique arrays
164     # NOTE: we are accepting whatever value might be set for CONFIG and DATA;
165     #       if it wasn't set, we just use default and leave it unset
166     # NOTE: include and then remove CONFIG_HOME and DATA_HOME to ensure they
167     #       are not present in the array if it was added before we got to it
168     typeset -UT XDG_DATA_DIRS xdg_data_dirs
169     if [[ -v XDG_DATA_HOME ]] {
170         export XDG_DATA_HOME
171     } elif [[ ! -e ~/.local/share ]] {
172         mkdir -m760 ~/.local/share
173     }
174     xdg_data_dirs=($XDG_DATA_HOME /{opt,usr/local,usr/pkg,usr}/share
175       "${XDG_DATA_DIRS:+${xdg_data_dirs[@]}}")
176     # XXX: if colons are not escaped, could remove unintended part of string
177     export XDG_DATA_DIRS=${XDG_DATA_DIRS#$XDG_DATA_HOME:}
178
179     typeset -UT XDG_CONFIG_DIRS xdg_config_dirs
180     if [[ -v XDG_CONFIG_HOME ]] {
181         export XDG_CONFIG_HOME
182     } elif [[ ! -e ~/.config ]] {
183         mkdir -m760 ~/.config
184     }
185     # I am of the belief .local should follow FHS /usr/local...
186     [[ -e ~/.local/etc ]] || ln -s ~/.config ~/.local/etc
187     xdg_config_dirs=($XDG_CONFIG_HOME ${XDG_CONFIG_DIRS:+"$xdg_config_dirs[@]"}
188       {/opt,/usr/local,/usr/pkg,}/etc/xdg)
189     # XXX: if colons are not escaped, could remove unintended part of string
190     export XDG_CONFIG_DIRS=${XDG_CONFIG_DIRS#$XDG_CONFIG_HOME:}
191
192     if [[ -v XDG_STATE_HOME ]] {
193         export XDG_STATE_HOME
194     } elif [[ ! -e ~/.local/state ]] {
195         mkdir -m760 ~/.local/state
196     }
197
198     if [[ ! -v XDG_CACHE_HOME ]] {
199         if [[ -v _sev_tmp ]] {
200             export XDG_CACHE_HOME=$_sev_tmp/.xdg.cache
201             [[ -e $XDG_CACHE_HOME ]] || mkdir -m700 $XDG_CACHE_HOME
202         } elif [[ ! -e ~/.cache ]] {
203             mkdir -m700 ~/.cache
204         }
205     }
206
207     if [[ -v XDG_RUNTIME_DIR ]] {
208         export XDG_RUNTIME_DIR
209     } else {
210         # make runtime dir in our session-specific tmpdir
211         export XDG_RUNTIME_DIR=$TMPDIR/.xdg.runtime
212         # same as in tmpdir creation, ensure dir doesn't exist
213         if [[ -h $XDG_RUNTIME_DIR ]] {
214             unlink $XDG_RUNTIME_DIR 2>/dev/null
215         } elif [[ -e $XDG_RUNTIME_DIR ]] {
216             rm -rf $XDG_RUNTIME_DIR 2>/dev/null
217         }
218         mkdir -m700 $XDG_RUNTIME_DIR 2>/dev/null
219     }
220
221     # source user dirs after other vars
222     [[ -e $XDG_CONFIG_HOME/user-dirs.dirs ]] &&
223       emulate sh -c "source $XDG_CONFIG_HOME/user-dirs.dirs"
224     export _sev_setup_xdg=
225 }
226
227 ### dbus
228 if [[ ! -v DBUS_SESSION_BUS_ADDRESS && -v commands[dbus-launch] ]] {
229     eval $(dbus-launch)
230     export DBUS_SESSION_BUS_ADDRESS DBUS_SESSION_BUS_PID
231 }
232
233 ### gpg home
234 if [[ ! -v GNUPGHOME ]] {
235     export GNUPGHOME=${XDG_CONFIG_HOME:-~/.config}/gnupg
236     if [[ -d ~/.gnupg ]] {
237         mv ~/.gnupg ${XDG_CONFIG_HOME:-~/.config}/gnupg
238     }
239 }
240
241 ### gpg agent + forwarding
242 # NOTE: while ssh manages its auth sock in its protocol when ForwardSsh is
243 #       enabled, GPG must be forwarded manually over Unix socket. to support
244 #       this, we forward the restricted gpg-agent extra socket to the remote
245 #       host with a RemoteForward rule in ~/.ssh/config that uses the
246 #       _GNUPG_SOCK_* env vars. to avoid conflicts with other ssh sessions
247 #       where the same user is connecting to the same host from different
248 #       machines, gpg in each environment should utilize its own forwarded
249 #       socket, rather than replace the sockets in GNUPGHOME which will be
250 #       overridden on the next connection. previously, you could provide a path
251 #       to the agent socket in GPG_AGENT_INFO, but that was deprecated in GPG
252 #       v2.1. instead, we must clone GNUPGHOME with links and replace the agent
253 #       sockets there with the forwarded one.
254 # NOTE: since Unix sockets are not supported under Windows, this will not work
255 #       under msys, cygwin, mingw, etc., but may work under wsl2.
256 # HACK: without SendEnv, which is disabled by default in most sshd configs,
257 #       there is no foolproof way to prevent race conditions via filename
258 #       collisions or to pass the desired forward path to the remote host
259 #       environment. we just have to guess the path we choose is good on the
260 #       desination, and assume the newest matching socket is the correct one
261 #       after connecting. in theory, we could occlude the ssh binary on PATH
262 #       with an alias or script that would allow us to communicate with the
263 #       remote host before opening a shell, so that we can have the host
264 #       communicate back to the client where it wants a socket created or ask
265 #       the host if the path the client wants to use is writable. however, this
266 #       would open up too many edge cases where it wouldn't work or be too
267 #       clunky (e.g. asking for password twice) to make it worth it.
268 function _gpg_socketpath {
269     # dirs are percent-encoded: https://stackoverflow.com/a/64312099
270     echo ${1//(#b)%([[:xdigit:]](#c2))/${(#):-0x$match[1]}}
271 }
272 if [[ ! -v _sev_setup_gpg_forward && -v commands[gpg] ]] {
273     # XXX: assuming /tmo exists and is writable on destination
274     export _GNUPG_SOCK_DEST_BASE=/tmp/.gpg-agent-forward
275     export _GNUPG_SOCK_DEST_EXT=$(date +%s).$RANDOM
276     export _GNUPG_SOCK_DEST=$_GNUPG_SOCK_DEST_BASE.$_GNUPG_SOCK_DEST_EXT
277     export _sev_gpg_forward_dir=${GNUPGHOME:-~/.gnupg}/.ssh_forward
278     _sev_zcleanup gpg-forward
279
280     # find our forwarded socket
281     s=($_GNUPG_SOCK_DEST_BASE*(N=oc[1]))
282     if [[ -n $s && -v SSH_CLIENT ]] {
283         # create new forward dir
284         export _sev_setup_gpg_forward=
285         h=$_sev_gpg_forward_dir/$$
286         mkdir -pm700 $h
287         # XXX: is it safe to link scdaemon socket? can its name be changed?
288         for x (S.scdaemon gpg.conf gpg-agent.conf sshcontrol random_seed
289                pubring.kbx{,~} trustdb.gpg private-keys-v1.d crls.d) {
290             ln -s ${GNUPGHOME:-~/.gnupg}/$x $h
291         }
292         export GNUPGHOME=$h
293         unset h
294         for x in $(gpgconf --list-dirs | grep 'agent-.*-\?socket:'); do
295             x=$(_gpg_socketpath ${x/#agent-*socket:})
296             if [[ ! -v orig ]] {
297                 # move forwarded socket to first valid agent socket path
298                 # XXX: if tmp is on different filesystem this may not work
299                 mv $s $x
300                 orig=$x
301             } else {
302                 # make links to forwarded socket for any others
303                 ln -s $orig $x
304             }
305         done
306         unset x orig
307     }
308     unset s
309
310     # what we will forward if we start a new ssh connection
311     # NOTE: do this after setting up GNUPGHOME to pick up new socket path;
312     #       if already connected over SSH, extra should be the remote one
313     export _GNUPG_SOCK_SRC=$(_gpg_socketpath \
314       $(gpgconf --list-dirs agent-extra-socket))
315 } elif [[ ! -v _sev_setup_gpg_forward ]] {
316     # required for RemoteForward to not error out if the vars are unset
317     [[ ! -v _GNUPG_SOCK_SRC ]] && export _GNUPG_SOCK_SRC=/nonexistent
318     [[ ! -v _GNUPG_SOCK_DEST ]] && export _GNUPG_SOCK_DEST=/nonexistent
319 }
320
321 ### gpg agent
322 if [[ -v commands[gpg-connect-agent] &&
323         ( ! -v _sev_setup_gpgagent || -v _sev_refresh_gpgagent ) ]] {
324     # avoid printing if we have already set up tty before
325     [[ ! -v _sev_setup_gpgagent && -o interactive ]] && p=true || p=false
326     if {$p} {
327         print -nP '%F{blue}>>>%f GPG: '
328         if [[ -v _sev_setup_gpg_forward ]] {
329             print -nP '%F{yellow}Forwarded agent '
330         } else {
331             print -nP '%F{green}Agent '
332         }
333     }
334     gpg-connect-agent /bye >/dev/null 2>&1
335     if [[ $? -ne 0 ]] {
336         $p && print -P '%F{red}communication error'
337     } else {
338         if [[ ! -v _sev_setup_gpg_forward ]] {
339             if [[ ${+GPG_TTY} -eq 0 && -o interactive ]]
340                 export GPG_TTY=$(tty)
341             if [[ ( -v DISPLAY || -v WAYLAND_DISPLAY ) &&
342                   ${PINENTRY_USER_DATA/USE_TTY=0} == $PINENTRY_USER_DATA ]]
343                 export PINENTRY_USER_DATA=USE_TTY=$((
344                   ${+DISPLAY} + ${+WAYLAND_DISPLAY} == 0))
345             # XXX: don't know if gpg-agent supports comments after directives
346             # XXX: path could have #
347             # XXX: we are assuming this is our pinentry from .local/bin
348             sed -Ei 's#^([[:space:]]*pinentry-program[[:space:]]).*$#\1'${commands[pinentry]:-/dev/null}'#' \
349               ${GNUPGHOME:-~/.gnupg}/gpg-agent.conf 2>/dev/null
350             # XXX: could check for changes before doing this to save perf
351             gpg-connect-agent RELOADAGENT UPDATESTARTUPTTY /bye >/dev/null 2>&1
352             if {$p} {
353                 gpg-connect-agent /subst /serverpid \
354                   "/echo pid \${get serverpid} on $GPG_TTY" /bye 2>/dev/null
355                 print -nP '%f'
356             }
357         } elif {$p} {
358             print -P '%f'
359         }
360         export _sev_setup_gpgagent=
361     }
362     unset p _sev_refresh_gpgagent
363 }
364
365 ### ssh agent
366 if [[ ! -v _sev_setup_ssh ]] {
367     # NOTE: preferred order of agents to check: okcagent, gnupg, openssh
368     #       first block takes care of okcagent and openssh, second gnupg
369     # XXX: doesn't actually check if ssh is enabled in gpg
370     [[ -o interactive ]] && print -nP '%F{blue}>>>%f SSH: %F{green}'
371     if [[ ! -v SSH_AUTH_SOCK && ( -v commands[okc-ssh-agent] ||
372           ( -v commands[ssh-agent] && ! -v commands[gpg] ) ) ]] {
373         okc=${commands[okc-ssh-agent]:+okc-}
374         e=$_sev_tmp/${okc}ssh-agent-exports
375         typeset sock=
376         typeset -i pid=
377         if [[ -f $e ]] {
378             IFS=$'\0' read -r sock pid <$e
379         }
380         if [[ -S $sock && $pid > 0 ]] && kill -0 $pid; then
381             [[ -o interactive ]] && print -P "Reusing agent PID $pid%f"
382             export SSH_AUTH_SOCK=$sock
383             export SSH_AGENT_PID=$pid
384         else
385             # TODO: ensure ssh-agent path looks legit to avoid unsafe eval?
386             # XXX: doesn't appear to be any other way to handle redirection.
387             #      because eval needs to write to current scope environment
388             #      subshells can't be used to capture output and print.
389             c='TMPDIR=$_sev_tmp ${okc}ssh-agent'
390             if [[ -o interactive ]] {
391                 eval $(eval $=c)
392                 print -nP '%f'
393             } else {
394                 eval $(eval $=c) >/dev/null 2>&1
395             }
396             echo -n $SSH_AUTH_SOCK$'\0'$SSH_AGENT_PID >!$e
397             unset c
398         fi
399         unset okc e sock pid
400     } elif [[ ! -v SSH_AUTH_SOCK && -v commands[gpg] ]] {
401         # since gpg should have been started above, just export and notify
402         if [[ -o interactive ]] {
403             if [[ -v _sev_setup_gpg_forward ]] {
404                 echo 'Forwarded GPG agent'
405             } else {
406                 gpg-connect-agent /subst /serverpid \
407                   '/echo GPG agent pid ${get serverpid}' /bye
408             }
409             print -nP '%f'
410         }
411         export SSH_AUTH_SOCK=$(_gpg_socketpath \
412           $(gpgconf --list-dirs agent-ssh-socket))
413     } elif [[ -v SSH_AUTH_SOCK ]] {
414         [[ -o interactive ]] && print -P 'Preconfigured agent%f'
415     } else {
416         [[ -o interactive ]] && print -P '%F{red}No agent available%f'
417     }
418
419     export _sev_setup_ssh=
420 }
421 unfunction _gpg_socketpath
422
423 ### perl local lib
424 [[ -v commands[perl] && -d $XDG_DATA_HOME/perl5/lib/perl5 &&
425    ! -v PERL_LOCAL_LIB_ROOT ]] &&
426   eval $(perl -I$XDG_DATA_HOME/perl5/lib/perl5 \
427               -Mlocal::lib=$XDG_DATA_HOME/perl5 2>/dev/null)
428
429
430 ### load site-specific
431 if [[ -f ${ZDOTDIR:-~}/.zprofile.local ]] { source ${ZDOTDIR:-~}/.zprofile.local }
This page took 0.064906 seconds and 4 git commands to generate.