Switching to .pam_environment for environment variables

My system environment has been quite a mess this past month.

I used to put my environment variables in ~/.profile. However, the variables suddenly stopped being applied one day. I tried to switch my display manager from sddm, to gdm, then to lightdm, to no avail. I know ~/.profile, being essentially a bash script, is somewhat insecure; ~/.pam_environment is also probably the right way going forward, so I started moving variables into it.

Update 2022-10-31: “Right way going forward”, lol. It got deprecated for “problematic security”; Arch Linux disabled it on 2022-10-20.

user_readenv=0|1 Turns on or off the reading of the user specific environment file. 0 is off, 1 is on. By default this option is off as user supplied environment variables in the PAM environment could affect behavior of subsequent modules in the stack without the consent of the system administrator.

Due to problematic security this functionality is deprecated since the 1.5.0 version and will be removed completely at some point in the future.

https://man.archlinux.org/man/pam_env.8

Preprocessing .pam_environment

I had a piece of code in my ~/.profile that checks Racket and Ruby's versions and sets the PATH to point to raco and gem's bin directory. ~/.pam_environment alone can't do that, so I wrote a Pollen version of the file and rendered / compiled it into the final ~/.pam_environment.

(define (defpam_env name . contents)
   (define content (string-join contents ""))
   ◊string-append{◊|name| DEFAULT=◊|content|})

(define (pathlist-string . args)
   (string-join (string-split (string-join args "") "\n") ":"))

(define racket-version (version))
(define ruby-version
   ; this, or sed | cut? I don't know which I prefer really.
   ((compose1 first
              (λ ($1) (string-split $1 "p"))
              second
              (λ ($1) (string-split $1 " ")))
    (with-output-to-string (λ () (system "ruby --version")))))

; == directory shortcuts ==
◊defpam_env["XDG_DESKTOP_DIR"]{◊|HOME|/デスクトップ}
◊defpam_env["XDG_DOWNLOAD_DIR"]{◊|HOME|/ダウンロード}
; ...

◊defpam_env["PATH"]{◊pathlist-string{
◊|HOME|/git/scripts
◊|HOME|/git/Sudocabulary
◊|HOME|/bin
◊|HOME|/.racket/◊|racket-version|/bin
◊|HOME|/.local/share/npm-global/bin
◊|HOME|/.gem/ruby/◊|ruby-version|/bin
$◊"{"PATH◊"}"
/usr/bin ; safety fallback
}}

; ...

◊defpam_env["VISUAL"]{nvim}
◊defpam_env["EDITOR"]{nvim}

I then ask KDE to render the file everytime I log out.

It's really ugly, but it only has one job, which it does fine.

DEFAULT? OVERRIDE? pam_env, what?

All seemed well, until a few days later my PATH config isn't taking effect anymore. For some reason, every other variable is properly set, just not PATH. Trying to see just exactly which file those variables came from, I noticed a variable that is only present in my ~/.pam_environment, telling me that it is indeed being read. I looked up info on PATH not being set with .pam_environment, and found this StackExchange post hinting at something to do with the var DEFAULT=value syntax.

Reading into pam_env's documentation on the config format, it seems that DEFAULT is intended to be used for, well, defaults that could be overridden. Changing it into OVERRIDE seems to tell pam_env to just use the value and don't fiddle around.

(define (defpam_env name . contents)
   (define content (string-join contents ""))
   ; DEFAULT -> OVERRIDE.
   ◊string-append{◊|name| OVERRIDE=◊|content|}

The full configuration file can be found in my dotfiles repo.