Recently, I have uninstalled my display manager for various reasons. One of the reasons for that was to have a better understanding of my login process. Display managers generally do arbitrary stuff, they have their own way/order of sourcing files, sometimes they skip important files and sometimes they source unnecessary stuff etc. So I thought, using no display manager would result in a simpler login model. Oh boy, I was wrong.
After quite a bit of fiddling around I believe I have a clear mental model of in which order my files gets sourced. Before that, here is a quick summary of some terminology:
When you log into a TTY, you get a interactive login shell. When you spawn a new terminal in your DE/WM, you get a non-login interactive shell. When you log in to your DE directly, that is done through a non-interactive login shell. A script that you run is run under a non-login non-interactive shell. These are important because this information will guide you why your PATH
variable is not working as intended in some cases and why sometimes it works.
I use zsh
as my main shell and I also set it to be my login shell. zsh
has a much simpler sourcing hierarchy, I'm not even going to bother myself explaining how bash does it. So here is how it goes:
/etc/zshenv
and then ~/.zshenv
is sourced. This is done no matter what type of shell you are spawning. These files gets sourced every time you are spawning a shell.
/etc/zshenv
/etc/zsh/zshenv
instead of /etc/zsh
even if man zsh
says the exact opposite. Some distributions change this path for zsh
and they forgot to update the manual.~/.zshenv
$PATH
, $PAGER
etc.)./etc/zprofile
gets sourced at this point, and then ~/.zprofile
. Just to make it clear, these files are only sourced at login and never again (if you somehow spawn a login shell again, of course it will be sourced again).
/etc/zprofile
/etc/zsh/zprofile
instead of /etc/zprofile
.
Most of the distributions have the following line inside the /etc/zprofile
file:
emulate sh -c 'source /etc/profile'
/etc/profile
file (which automatically gets sourced if you are using sh
or bash
as your login shell.), which in turn sources files under /etc/profile.d
. These files are generally gets installed when you install a program into your system. For example, if you install nix
package manager, it also installs /etc/profile.d/nix{,-daemon}.sh
files and this files needs to be sourced for nix
to work properly. So if your /etc/zprofile
or /etc/zsh/zprofile
does not contain this line, add it to your ~/.zprofile
file.~/.zprofile
Also this is the file that you want to run startx
, if you are not using a display manager like me. Here is how I do it:
# Following automatically calls "startx" when you login on tty1: if [[ -z ${DISPLAY} && ${XDG_VTNR} -eq 1 ]]; then # Logs can be found in ~/.xorg.log exec startx -- -keeptty -nolisten tcp > ~/.xorg.log 2>&1 fi
After running startx
, it loads ~/.xinitrc
file. This is the file that you want to start your window manager and all the other programs that you want to see at startup. An example file might be:
#!/bin/sh # The following is essential, you need to source # every file under `/etc/X11/xinit/xinitrc.d`. if [[ -d /etc/X11/xinit/xinitrc.d ]] ; then for f in /etc/X11/xinit/xinitrc.d/?*.sh ; do echo "Sourcing $f" [[ -x "$f" ]] && . "$f" done unset f fi sysresources=/etc/X11/xinit/.Xresources sysmodmap=/etc/X11/xinit/.Xmodmap [[ -f $sysresources ]] && xrdb -merge $sysresources [[ -f $sysmodmap ]] && xmodmap $sysmodmap # Load your keymap and all that jazz setxkbmap 'us(intl)' xrdb -merge $HOME/.Xresources xmodmap $HOME/.Xmodmap # Cursor xsetroot -cursor_name left_ptr # Starting programs like your compositor makes sense here picom -b # Some other programs related to X unclutter & redshift & # Run your WM, as an example I run bspwm exec bspwm
/etc/zshrc
(or /etc/zsh/zshrc
) and then ~/.zshrc
gets sourced, if you are spawning an interactive shell.
/etc/zlogin
(or /etc/zsh/zlogin
) and then ~/.zlogin
gets sourced.
~/.zprofile
or move all of your ~/.zprofile
into this one.~/.zshrc
(if it's sourced at all) and it doesn't make much sense (to me).
A problem I was having was related to unison. It's a simple and powerful file synchronization program that I use. One problem with that is, it's quite picky about it's version. It requires absolute same version on both client and server, and not only that, it also wants both binaries to be compiled with same version of the OCaml compiler. To solve this kind of version issue between my rigs, (among other reasons) I use nix package manager. This gets me same binary on every computer I have. But there is one problem, when I install unison
through nix
, it installs it under ~/.nix-profile/bin/unison
, and when I run unison
to synchronize my files, it fails to find unison
on the other computer. But I can ssh
into other computer and run unison
without a problem. Hmm, what is going on here?
nix
gets installed it also installs the following files: /etc/profile.d/nix{,-daemon}.sh
$PATH
variable, so ~/.nix-profile/bin
gets added into $PATH
./etc/profile
, which is sourced by /etc/zsh/zprofile
./etc/zsh/zprofile
gets sourced when a login shell is spawned.
So when I do ssh my-machine
, it spawns an interactive login shell and drops me into it. By this time, /etc/zsh/zprofile
is already sourced and thanks to that I can simply run unison
binary. When I run unison
on client, it connects the server through ssh
but it uses a non-login non-interactive shell while doing that. Same goes for this command ssh my-machine which unison
. This command runs in a non-login non-interactive shell. Because /etc/zsh/zprofile
requires a login shell to get sourced, ~/.nix-profile/bin
never gets added to the $PATH
variable.
So what's the solution? You know it, only ~/.zshenv
gets sourced when non-login non-interactive shell is spawned. So I just update the $PATH
there and everything works as expected now.
That's it. This was just some kind of a braindump. From now on, I'll just try to drop my notes as blog posts like this.