;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2019,2020 L p R n d n ;;; ;;; This file is part of GNU Guix. ;;; ;;; GNU Guix is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or (at ;;; your option) any later version. ;;; ;;; GNU Guix is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with GNU Guix. If not, see . (define-module (gnu services lightdm) #:use-module (srfi srfi-1) #:use-module (ice-9 match) #:use-module (ice-9 receive) #:use-module (guix gexp) #:use-module (guix records) #:use-module (gnu artwork) #:use-module (gnu system pam) #:use-module (gnu system shadow) #:use-module (gnu services) #:use-module (gnu services dbus) #:use-module (gnu services desktop) #:use-module (gnu services shepherd) #:use-module (gnu services xorg) #:use-module (gnu packages admin) #:use-module (gnu packages display-managers) #:use-module (gnu packages freedesktop) #:use-module (gnu packages gnome) #:use-module (gnu packages xorg) #:export (lightdm-seat-configuration lightdm-configuration lightdm-configuration? lightdm-service-type lightdm-gtk-greeter-configuration lightdm-gtk-greeter-configuration? lightdm-gtk-greeter-service-type)) ;; LIGHTDM (define-record-type* lightdm-seat-configuration make-lightdm-seat-configuration lightdm-seat-configuration? (name-glob lightdm-seat-configuration-name-glob (default "*")) (type lightdm-seat-configuration-type (default "local")) (xorg-configuration lightdm-seat-configuration-xorg-configuration (default #f)) (session-wrapper lightdm-seat-configuration-session-wrapper (default (xinitrc))) (greeter-session lightdm-seat-configuration-greeter-session (default "lightdm-gtk-greeter")) (default-user-session lightdm-seat-configuration-default-user-session (default "")) (autologin-user lightdm-seat-configuration-autologin-user (default "")) (extra-config lightdm-seat-configuration-extra-config (default '()))) (define (lightdm-seat-configuration->list seat default-xorg-configuration) "Given a seat, outputs a list to be used by mixed-text-file through `apply." (match-record seat (name-glob type xorg-configuration session-wrapper greeter-session default-user-session autologin-user extra-config) (list " [Seat:" name-glob "] type = " type ;; If no xorg-configuration is set by the seat use the one provided ;; by the lightdm service " xserver-command = " (xorg-start-command (or xorg-configuration default-xorg-configuration)) " session-wrapper = " session-wrapper " greeter-session = " greeter-session (if (string-null? default-user-session) "" (string-append " user-session = " default-user-session)) " " ;; Turn autologin ON if autologin-user is set (if (string-null? autologin-user) "" (string-append " autologin-user = " autologin-user " autologin-session = " default-user-session)) " " (if (null? extra-config) "" (string-join extra-config "\n")) " "))) (define-record-type* lightdm-configuration make-lightdm-configuration lightdm-configuration? ;; General configuration (lightdm lightdm-configuration-lightdm (default lightdm)) (allow-empty-passwords? lightdm-configuration-allow-empty-passwords? (default #f)) (sessions-directory lightdm-configuration-sessions-directory (default (string-append "/run/current-system/profile/share/xsessions" ":/run/current-system/profile/share/wayland-sessions"))) (greeters-directory lightdm-configuration-greeters-directory (default "/run/current-system/profile/share/xgreeters")) (remote-sessions-directory lightdm-configuration-remote-sessions-directory (default (string-append "/run/current-system/profile/" "share/remote-sessions"))) ;; Having a xorg-configuration field here allows us ;; to benefit from set-xorg-configuration. (xorg-configuration lightdm-configuration-xorg-configuration (default (xorg-configuration))) (seats lightdm-configuration-seats (default '())) (extra-config lightdm-configuration-extra-config (default '()))) (define (lightdm-configuration-file config) (match-record config (allow-empty-passwords? sessions-directory greeters-directory remote-sessions-directory xorg-configuration seats extra-config) ;; Little trick to allow unquote-splicing of seats (apply mixed-text-file `("lightdm.conf" " [LightDM] greeter-user = lightdm greeters-directory = " ,greeters-directory " sessions-directory = " ,sessions-directory " remote-sessions-directory = " ,remote-sessions-directory " #Seats " ,@(if (null? seats) (lightdm-seat-configuration->list (lightdm-seat-configuration) xorg-configuration) (concatenate (map (lambda (seat) (lightdm-seat-configuration->list seat xorg-configuration)) seats))) " #Extra config " ,(if (null? extra-config) "" (string-join extra-config "\n")) " ")))) (define %lightdm-accounts (list (user-group (name "lightdm") (system? #t)) (user-account (name "lightdm") (group "lightdm") (system? #t) (comment "LightDM user") (home-directory "/var/lib/lightdm") (shell (file-append shadow "/sbin/nologin"))))) (define %lightdm-activation ;; Ensure /var/lib/lightdm is owned by the "lightdm" user. ;; Mimics what is done for gdm ;; see a43e9157ef479e94c19951cc9d228cf153bf78ee (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils)) (define (ensure-ownership directory) (let* ((lightdm (getpwnam "lightdm")) (uid (passwd:uid lightdm)) (gid (passwd:gid lightdm)) (st (stat directory #f))) ;; Recurse into directory only if it has wrong ownership. (when (and st (or (not (= uid (stat:uid st))) (not (= gid (stat:gid st))))) (for-each (lambda (file) (chown file uid gid)) (find-files "directory" #:directories? #t))))) (when (not (stat "/var/lib/lightdm-data" #f)) (mkdir-p "/var/lib/lightdm-data")) (for-each ensure-ownership '("/var/lib/lightdm" "/var/lib/lightdm-data"))))) (define (lightdm-pam-service config) "Return a PAM service for @command{lightdm}." (unix-pam-service "lightdm" #:login-uid? #t #:allow-empty-passwords? (lightdm-configuration-allow-empty-passwords? config))) (define (lightdm-greeter-pam-service) "Return a PAM service for @command{lightdm-greeter}}." (pam-service (name "lightdm-greeter") (auth (list ;; Load environment from /etc/environment and ~/.pam_environment (pam-entry (control "required") (module "pam_env.so")) ;; Always let the greeter start without authentication (pam-entry (control "required") (module "pam_permit.so")))) ;; No action required for account management (account (list (pam-entry (control "required") (module "pam_permit.so")))) ;; Can't change password (password (list (pam-entry (control "required") (module "pam_deny.so")))) ;; Setup session (session (list (pam-entry (control "required") (module "pam_unix.so")))))) (define (lightdm-autologin-pam-service) "Return a PAM service for @command{lightdm-autologin}}." (pam-service (name "lightdm-autologin") (auth (list ;; Block login if they are globally disabled (pam-entry (control "required") (module "pam_nologin.so")) (pam-entry (control "required") (module "pam_succeed_if.so") (arguments (list "uid >= 1000"))) ;; Allow access without authentication (pam-entry (control "required") (module "pam_permit.so")))) ;; Stop autologin if account requires action (account (list (pam-entry (control "required") (module "pam_unix.so")))) ;; Can't change password (password (list (pam-entry (control "required") (module "pam_deny.so")))) ;; Setup session (session (list (pam-entry (control "required") (module "pam_unix.so")))))) (define (lightdm-pam-services config) (list (lightdm-pam-service config) (lightdm-greeter-pam-service) (lightdm-autologin-pam-service))) (define (lightdm-shepherd-service config) "Return a for LightDM with CONFIG." (define lightdm-command #~(list #$(file-append (lightdm-configuration-lightdm config) "/sbin/lightdm"))) (list (shepherd-service (documentation "LightDM display manager.") (requirement '(dbus-system user-processes host-name)) (provision '(display-manager xorg-server)) (respawn? #f) (start #~(lambda () (fork+exec-command #$lightdm-command #:environment-variables (list (string-append "PATH=/run/current-system/profile/sbin" ":/run/current-system/profile/bin"))))) (stop #~(make-kill-destructor))))) (define (lightdm-etc-service config) `(("xdg/lightdm/lightdm.conf.d/lightdm.conf" ,(lightdm-configuration-file config)))) (define (lightdm-profile-service config) ;; In case no seats are provided, we fall back on a default one ;; with lightdm-gtk-greeter. Add necessary packages. (let ((seats (lightdm-configuration-seats config)) (lightdm (lightdm-configuration-lightdm config))) (if (null? seats) (list lightdm lightdm-gtk-greeter) (list lightdm)))) (define lightdm-service-type ;; (handle-xorg-configuration lightdm-configuration) (service-type (name 'lightdm) (extensions (list (service-extension shepherd-root-service-type lightdm-shepherd-service) (service-extension activation-service-type (const %lightdm-activation)) (service-extension pam-root-service-type lightdm-pam-services) (service-extension dbus-root-service-type (compose list lightdm-configuration-lightdm)) (service-extension account-service-type (const %lightdm-accounts)) (service-extension etc-service-type lightdm-etc-service) (service-extension profile-service-type lightdm-profile-service))) ;; LightDM is extended with lists of seats ;; or with a xorg-configuration by set-xorg-configuration ;; Deal with both cases. (compose (lambda (extensions) (receive (xorg-configuration-extensions seats-extensions) (partition xorg-configuration? extensions) (list `("xorg-configuration" . ;; Mimic handle-xorg-configuration ,(match xorg-configuration-extensions (() #f) ((config . _) config))) `("seats" . ,(concatenate seats-extensions)))))) (extend (lambda (config extensions) (lightdm-configuration (inherit config) (xorg-configuration (or (assoc-ref extensions "xorg-configuration") (lightdm-configuration-xorg-configuration config))) (seats (append (assoc-ref extensions "seats") (lightdm-configuration-seats config)))))) (default-value (lightdm-configuration)) (description "Return a service that spawns the LightDM graphical login manager."))) ;; GREETERS (define-record-type* lightdm-gtk-greeter-configuration make-lightdm-gtk-greeter-configuration lightdm-gtk-greeter-configuration? (lightdm-gtk-greeter lightdm-gtk-greeter-configuration-lightdm-gtk-greeter (default lightdm-gtk-greeter)) (assets lightdm-gtk-greeter-configuration-assets (default (list adwaita-icon-theme gnome-themes-standard))) (theme-name lightdm-gtk-greeter-configuration-theme-name (default "Adwaita")) (icon-theme-name lightdm-gtk-greeter-configuration-icon-theme-name (default "Adwaita")) (cursor-theme-name lightdm-gtk-greeter-configuration-cursor-theme-name (default "Adwaita")) (cursor-theme-size lightdm-gtk-greeter-configuration-cursor-theme-size (default 16)) (background lightdm-gtk-greeter-configuration-background (default (file-append %artwork-repository "/grub/GuixSD-fully-black-16-9.svg"))) (a11y-states lightdm-gtk-greeter-a11y-states (default "contrast; font; keyboard; reader")) (reader lightdm-gtk-greeter-reader (default #f)) (seats lightdm-gtk-greeter-configuration-seats (default (list (lightdm-seat-configuration)))) (extra-config lightdm-gtk-greeter-configuration-extra-config (default '()))) (define (lightdm-gtk-greeter-configuration-file config) (match-record config (theme-name icon-theme-name cursor-theme-name cursor-theme-size background a11y-states reader extra-config) (mixed-text-file "lightdm-gtk-greeter.conf" " [greeter] theme-name = " theme-name " icon-theme-name = " icon-theme-name " cursor-theme-name = " cursor-theme-name " cursor-theme-size = " (number->string cursor-theme-size) " background = " background " a11y-states = " a11y-states " " (if reader (string-append "reader = " reader) "") " " (if (null? extra-config) "" (string-join extra-config "\n"))))) (define (lightdm-gtk-greeter-lightdm-service config) ;; Enforce greeter-session field to lightdm-gtk-greeter (map (lambda (seat) (lightdm-seat-configuration (inherit seat) (greeter-session "lightdm-gtk-greeter"))) (lightdm-gtk-greeter-configuration-seats config))) (define (lightdm-gtk-greeter-etc-service config) `(("xdg/lightdm/lightdm-gtk-greeter.conf" ,(lightdm-gtk-greeter-configuration-file config)))) (define (lightdm-gtk-greeter-profile-service config) (cons (lightdm-gtk-greeter-configuration-lightdm-gtk-greeter config) (lightdm-gtk-greeter-configuration-assets config))) (define lightdm-gtk-greeter-service-type (service-type (name 'lightdm-gtk-greeter) (extensions (list (service-extension lightdm-service-type lightdm-gtk-greeter-lightdm-service) (service-extension etc-service-type lightdm-gtk-greeter-etc-service) (service-extension profile-service-type lightdm-gtk-greeter-profile-service))) (default-value (lightdm-gtk-greeter-configuration)) (description "Set-up lightdm-gtk-greeter as well as its configuration file and extends LightDM with its seats.")))