Something no one does anymore, apparently.
January 19, 2023 AD
I manage multiple Rocky Linux workstations that automount users’ home directories via kerberized NFS. Unfortunately, I don’t think this is a common setup anymore–I encountered a few bugs and performance issues that needed non-obvious workarounds.
If you can somehow restrict your users to a single GNOME session at any given time, you’ll probably be fine. However, as soon as someone leaves his desktop running and logs into another workstation, strange things begin to happen. Here are some oddities I’ve observed:
GNOME settings on one machine are clobbered by the other (this may or may not be desirable).
Firefox refuses to run, because the profile directory is already in use.
gnome-keyring
freaks out and creates many login keyrings under ~/.local/share/keyrings
, losing previously stored secrets in the process!
Sound quits working (I suspect this is due to ~/.config/pulse/cookie
being clobbered).
Flatpak apps completely blow up (each app stores its state in ~/.var
, and this is nonconfigurable). Running multiple instances of signal-dekstop
instantly corrupts the sqlite database.
goa-daemon
generates thousands of syslog messages per minute (I am unsure if this is due to ~/.config/goa-1.0/accounts.conf
getting clobbered, or a symptom of this bug). I have no idea what goa-daemon
does, nor do I want to. I have been victimized by the bazaar enough for one lifetime.
I/O-heavy tasks, like compiling and grepping, will be much slower over NFS than the local disk. Browser profiles stored on NFS (~/.mozilla
, ~/.cache/chromium
, etc.) provide a noticeably poor experience.
File browsing is also painful if you have lots of images or videos. Thumbnails for files stored on NFS will be cached in ~/.cache/thumbnails
, which is also stored on NFS!
The XDG Base Directory Specification lets you change the default locations of ~/.cache
, ~/.config
, and the like by setting some environment variables in the user’s session. We can solve most of these problems by moving the various XDG directories to the local disk.
First, let’s write a script that automatically provisions a local home directory whenever someone logs in:
#!/bin/bash
# /usr/local/sbin/create-local-homedir.sh
# Log all output to syslog.
exec 1> >(logger -s -t $(basename "$0")) 2>&1
PAM_UID=$(id -u "${PAM_USER}")
if (( PAM_UID >= 1000 )); then
install -o "${PAM_USER}" -g "${PAM_USER}" -m 0700 -d "/usr/local/home/${PAM_USER}"
fi
Of course, it needs to be executable:
chmod 755 /usr/local/sbin/create-local-homedir.sh
Next, we modify the PAM configuration to execute our script whenever anyone logs in via GDM or SSH:
--- /etc/pam.d/gdm-password
+++ /etc/pam.d/gdm-password
@@ -1,5 +1,6 @@
auth [success=done ignore=ignore default=bad] pam_selinux_permit.so
auth substack password-auth+auth optional pam_exec.so /usr/local/sbin/create-local-homedir.sh
auth optional pam_gnome_keyring.so
auth include postlogin
--- /etc/pam.d/sshd
+++ /etc/pam.d/sshd
@@ -15,3 +15,4 @@
session optional pam_motd.so
session include password-auth
session include postlogin+session optional pam_exec.so /usr/local/sbin/create-local-homedir.sh
If you’re using SELinux, you’ll need a separate copy of the create-local-homedir
script for use with GDM, labeled with xdm_unconfined_exec_t
:
ln /usr/local/sbin/create-local-homedir{,-gdm}.sh
semanage fcontext -a -t xdm_unconfined_exec_t /usr/local/sbin/create-local-homedir-gdm.sh
restorecon -v /usr/local/sbin/create-local-homedir-gdm.sh
Be sure to modify /etc/pam.d/gdm-password
appropriately.
We need to tell the user’s applications to use the new local home directory for storage. We have to do this early in the PAM stack for GDM, because $XDG_DATA_HOME
must be set before gnome-keyring
gets executed.
Edit your PAM files again, adding one more line:
--- /etc/pam.d/gdm-password
+++ /etc/pam.d/gdm-password
@@ -1,6 +1,7 @@
auth [success=done ignore=ignore default=bad] pam_selinux_permit.so
auth substack password-auth
auth optional pam_exec.so /usr/local/sbin/create-local-homedir.sh+auth optional pam_env.so conffile=/etc/security/pam_env_xdg.conf
auth optional pam_gnome_keyring.so
auth include postlogin
--- /etc/pam.d/sshd
+++ /etc/pam.d/sshd
@@ -16,3 +16,4 @@
session include password-auth
session include postlogin
session optional pam_exec.so /usr/local/sbin/create-local-homedir.sh+session optional pam_env.so conffile=/etc/security/pam_env_xdg.conf
Then, create the corresponding pam_env.conf(5)
file:
# /etc/security/pam_env_xdg.conf
XDG_DATA_HOME DEFAULT=/usr/local/home/@{PAM_USER}/.local/share
XDG_STATE_HOME DEFAULT=/usr/local/home/@{PAM_USER}/.local/state
XDG_CACHE_HOME DEFAULT=/usr/local/home/@{PAM_USER}/.cache XDG_CONFIG_HOME DEFAULT=/usr/local/home/@{PAM_USER}/.config
Unfortunately, since a majority of open source developers follow the CADT model, there are many apps that ignore the XDG specification. Sometimes these apps have their own environment variables for specifying their storage locations. Otherwise, symlinks can provide us with an escape hatch.
Create a script in /etc/profile.d
for these workarounds. Scripts in this directory are executed within the context of the user’s session, so we can freely write inside his NFS home directory using his UID (and kerberos ticket, if applicable).
# /etc/profile.d/local-homedirs.sh
if (( UID >= 1000 )); then
# Building code is *much* faster on the local disk. Modify as needed:
export PYTHONUSERBASE="/usr/local/home/${USER}/.local" # python
export npm_config_cache="/usr/local/home/${USER}/.npm" # nodejs
export CARGO_HOME="/usr/local/home/${USER}/.cargo" # rust
export GOPATH="/usr/local/home/${USER}/go" # golang
# Firefox doesn't provide an environment variable for setting the default profile
# path, so we'll just symlink it to /usr/local/home.
mkdir -p "/usr/local/home/${USER}/.mozilla"
ln -sfn "/usr/local/home/${USER}/.mozilla" "${HOME}/.mozilla"
# Flatpak hardcodes ~/.var, so symlink it to /opt/flatpak.
ln -sfn "/opt/flatpak/${USER}" "${HOME}/.var"
fi
If you use any Flatpak apps, each user will need his own local Flatpak directory. The Flatpak runtime appears to shadow the entire /usr
using mount namespaces, so any /usr/local/home
symlinks will disappear into the abyss. Luckily, /opt
appears to be undefiled. Modify your original script like so:
--- /usr/local/sbin/create-local-homedir.sh
+++ /usr/local/sbin/create-local-homedir.sh
@@ -6,4 +6,5 @@
if (( PAM_UID >= 1000 )); then
install -o "${PAM_USER}" -g "${PAM_USER}" -m 0700 -d "/usr/local/home/${PAM_USER}"+ install -o "${PAM_USER}" -g "${PAM_USER}" -m 0700 -d "/opt/flatpak/${PAM_USER}"
fi
Most of my users are nontechnical, so I’m pleased that these workarounds do not require any manual intervention on their part.
I am sad that $XDG_CONFIG_HOME
can’t be shared between multiple workstations reliably. When I change my desktop background or add a new password to gnome-keyring
, it only affects the local machine.
Initially, I tried symlinking various subdirectories of ~/.config
to the local disk individually as I encountered different bugs (e.g. ~/.config/pulse
). Unfortunately this proved brittle, as I was constantly playing whack-a-mole with apps that abused $XDG_CONFIG_HOME
for storing local state. In the end, it was less of a headache to just dump the whole thing onto the local disk.
I suppose if you verified an app behaved properly with multiple simultaneous NFS clients, you could always symlink /usr/local/home/$USER/.config/$APP
back onto NFS!