Welcome to the second part of the Guix deep dive! Today we will be looking a bit deeper into the operating-system
record, mostly into system services,
how they are implemented and how we can configure them.
Before you read further I strongly recommend reading the first part as it provides some context to what we will be doing now. I'll also be referring to the system configuration from that other post.
This time we'll look at the surface level of how Guix handles services and their configuration.
The default services
Yet again we can find the default service list by peeking into the operating-system
record.
(services operating-system-user-services ; list of services (thunked) ;allow for system-dependent services (default %base-services))
This time, let's actually find the service
record first. Finding the scheme definition for services was really straightforward - it's at gnu/services.scm
.
Once inside the file we can look for the service
record with a simple search and immediately the following definition is found.
;; Services of a given type. (define-record-type <service> (make-service type value) service? (type service-kind) (value service-value))
As you can see services are actually only defined as 2 variables - the service type and value. Take note of the service-kind
accessor, we're going to use it later.
Service type
Let's first take a look at the service type.
Yet again we can look at the record definition.
(define-record-type* <service-type> service-type make-service-type service-type? (name service-type-name) ;symbol (for debugging) ;; Things extended by services of this type. (extensions service-type-extensions) ;list of <service-extensions> ;; Given a list of extensions, "compose" them. (compose service-type-compose ;list of Any -> Any (default #f)) ;; Extend the services' own parameters with the extension composition. (extend service-type-extend ;list of Any -> parameters (default #f)) ;; Optional default value for instances of this type. (default-value service-type-default-value ;Any (default &no-default-value)) ;; Meta-data. (description service-type-description) ;string (location service-type-location ;<location> (default (and=> (current-source-location) source-properties->location)) (innate)))
Most slots here are pretty self-explanatory, aside from compose
, extend
and extensions
.
I'm going to refer to the Guix documentation as it provides a short and clear explanation of what these do - my explanation effort here would be redundant.
Here's the link.
With this we can go ahead and dissect the %desktop-services
which I use in my config. Let's start with printing their names, left in for debugging purposes
scheme@(guile-user)> (map (lambda (service) (service-type-name (service-kind service))) %desktop-services) $9 = (gdm screen-locker screen-locker mtp sane polkit-wheel mount-setuid-helpers gdm-file-system fontconfig-file-system network-manager wpa-supplicant network-manager-applet modem-manager usb-modeswitch avahi udisks upower accountsservice cups-pk-helper colord geoclue polkit elogind dbus ntp x11-socket-directory-service pulseaudio alsa login virtual-terminal console-fonts syslog agetty mingetty mingetty mingetty mingetty mingetty mingetty static-networking urandom-seed guix nscd rottlog log-cleanup udev sysctl special-files)
We first extract the service type with service-kind
, followed by extracting that types name with service-type-name
.
This can already give us some ideas for how service lists can be modified, let's remove the gdm
service and then print the service list.
scheme@(guile-user)> (map (lambda (service) (service-type-name (service-kind service))) (remove (lambda (service) (equal? (service-type-name (service-kind service)) 'gdm)) %desktop-services)) $14 = (screen-locker screen-locker mtp sane polkit-wheel mount-setuid-helpers gdm-file-system fontconfig-file-system network-manager wpa-supplicant network-manager-applet modem-manager usb-modeswitch avahi udisks upower accountsservice cups-pk-helper colord geoclue polkit elogind dbus ntp x11-socket-directory-service pulseaudio alsa login virtual-terminal console-fonts syslog agetty mingetty mingetty mingetty mingetty mingetty mingetty static-networking urandom-seed guix nscd rottlog log-cleanup udev sysctl special-files)
Again we simply extract the service type name, then filter it by comparing it against the quoted gdm
symbol. The map
here is the same as above, used to print the list in a readable format.
We can also remove services by comparing service types instead.
If we take a quick peek at the config we can see something like this:
(define %my-services (modify-services %desktop-services (delete gdm-service-type) (guix-service-type config => (guix-configuration (inherit config) (substitute-urls (append (list "https://substitutes.nonguix.org") %default-substitute-urls)) (authorized-keys (append (list (local-file "./signing-key.pub")) %default-authorized-guix-keys)))) (dbus-root-service-type config => (dbus-configuration (inherit config) (services (list libratbag blueman))))))
As you can see Guix provides a modify-services
macro, which abstracts service deletion away, making our effort kind of unnecessary…
but at least we now understand how that part of the macro works under the hood and we learned a little about service types along the way.
Looking into actual services
I think now is a good time to look at how an actual service is implemented. I'll use the gdm
service again.
First of all let's find the definition of gdm-service-type
. I'll use M-.
to get there right away.
(define gdm-service-type (handle-xorg-configuration gdm-configuration (service-type (name 'gdm) (extensions (list (service-extension shepherd-root-service-type gdm-shepherd-service) (service-extension account-service-type (const %gdm-accounts)) (service-extension dconf-service-type gdm-dconf-profiles) (service-extension pam-root-service-type gdm-pam-service) (service-extension polkit-service-type gdm-polkit-rules) (service-extension profile-service-type gdm-configuration-gnome-shell-assets) (service-extension dbus-root-service-type (compose list gdm-configuration-gdm)) (service-extension localed-service-type (compose xorg-configuration-keyboard-layout gdm-configuration-xorg)))) (default-value (gdm-configuration)) (description "Run the GNOME Desktop Manager (GDM), a program that allows you to log in in a graphical session, whether or not you use GNOME."))))
Here we can see which services are extended by GDM.
The logical next step is the gdm-configuration
which is another record that looks as follows:
(define-record-type* <gdm-configuration> gdm-configuration make-gdm-configuration gdm-configuration? (gdm gdm-configuration-gdm (default gdm)) (allow-empty-passwords? gdm-configuration-allow-empty-passwords? (default #t)) (auto-login? gdm-configuration-auto-login? (default #f)) (auto-suspend? gdm-configuration-auto-suspend? (default #t)) (dbus-daemon gdm-configuration-dbus-daemon (default dbus-daemon-wrapper)) (debug? gdm-configuration-debug? (default #f)) (default-user gdm-configuration-default-user (default #f)) (gnome-shell-assets gdm-configuration-gnome-shell-assets (default (list adwaita-icon-theme font-abattis-cantarell))) (xorg-configuration gdm-configuration-xorg (default (xorg-configuration))) (x-session gdm-configuration-x-session (default (xinitrc))) (xdmcp? gdm-configuration-xdmcp? (default #f)) (wayland? gdm-configuration-wayland? (default #f)) (wayland-session gdm-configuration-wayland-session (default gdm-wayland-session-wrapper)))
This gives us more insight into how the service can be configured.
The gdm
slot is rather interesting here as it defines which package the service is going to use. We're going to apply this knowledge practically later.
If we want to look into how the service configuration is defined then gdm-shepherd-service
is what we're looking for.
(define (gdm-shepherd-service config) (define config-file (gdm-configuration-file config)) (list (shepherd-service (documentation "Xorg display server (GDM)") (provision '(xorg-server)) (requirement '(dbus-system pam user-processes host-name udev elogind)) (start #~(lambda () (fork+exec-command (list #$(file-append (gdm-configuration-gdm config) "/bin/gdm")) #:environment-variables (list #$@(if (gdm-configuration-auto-suspend? config) #~() #~("DCONF_PROFILE=/etc/dconf/profile/gdm")) (string-append "GDM_CUSTOM_CONF=" #$config-file) (string-append "GDM_DBUS_DAEMON=" #$(gdm-configuration-dbus-daemon config)) (string-append "GDM_X_SERVER=" #$(xorg-wrapper (gdm-configuration-xorg config))) (string-append "GDM_X_SESSION=" #$(gdm-configuration-x-session config)) (string-append "XDG_DATA_DIRS=" ((lambda (ls) (string-join ls ":")) (map (lambda (path) (string-append path "/share")) ;; XXX: Remove gnome-shell below when GDM ;; can depend on GNOME Shell directly. (cons #$gnome-shell '#$(gdm-configuration-gnome-shell-assets config))))) ;; Add XCURSOR_PATH so that mutter can find its ;; cursors. gdm doesn't login so doesn't source ;; the corresponding line in /etc/profile. "XCURSOR_PATH=/run/current-system/profile/share/icons" (string-append "GDK_PIXBUF_MODULE_FILE=" #$gnome-shell "/" #$%gdk-pixbuf-loaders-cache-file) (string-append "GDM_WAYLAND_SESSION=" #$(gdm-configuration-wayland-session config)))))) (stop #~(make-kill-destructor)) (actions (list (shepherd-configuration-action config-file))) (respawn? #t))))
We can see how Guix composes the GDM configuration file and writes the configuration, as well as the start
process for the service - in this case a simple invocation of the gdm
command.
It's also seen here that the GDM service provides the xorg-server
service. This means if we added another instance of that to our config we would actually get a duplicate which is no good.
shepherd-service
is a whole another can of worms - we'll learn about that some other time.
A practical usage example
I think we have already learned enough for now. We can use the newfound knowledge in all sorts of practical and interesting ways.
One of the common questions I've seen asked by Guix users is how one would go about stripping their desktop environment of unnecessary packages - so let's try that!
I'll be modifying Gnome as it ships with a lot of unnecessary stuff by default. Let's start with the gnome-desktop-service-type
.
(define gnome-desktop-service-type (service-type (name 'gnome-desktop) (extensions (list (service-extension udev-service-type gnome-udev-rules) (service-extension polkit-service-type gnome-polkit-settings) (service-extension setuid-program-service-type gnome-setuid-programs) (service-extension profile-service-type (compose list gnome-desktop-configuration-gnome)))) (default-value (gnome-desktop-configuration)) (description "Run the GNOME desktop environment.")))
The gnome-desktop-configuration
is what we want here, let's pull up its definition.
(define-record-type* <gnome-desktop-configuration> gnome-desktop-configuration make-gnome-desktop-configuration gnome-desktop-configuration? (gnome gnome-desktop-configuration-gnome (default gnome)))
And here we can see what we're looking for - the gnome
slot. This slot defines which Gnome package is used for the service. If we replace this package with a stripped-down version, we can remove all the bloat we don't want.
So let's make that stripped-down version of Gnome and remove all the stuff we don't want. We haven't been through defining packages yet, so this here is a bit of magic we will investigate some other time.
(define my-gnome (package (inherit gnome) (name "my-gnome") (propagated-inputs (modify-inputs (package-propagated-inputs gnome) (delete "epiphany") (delete "gnome-weather") (delete "simple-scan") (delete "gnome-maps") (delete "cheese") (delete "gnome-boxes") (delete "gnome-console") (delete "gnome-contacts") (append gnome-terminal)))))
We add this package definition to our system config, then move on to defining the service.
Now we construct service type such that is uses a custom gnome-configuration
- which we tell to use our custom my-gnome
package.
This is practically all just playing around with the records we looked at above.
(operating-system ... (services (cons* (service gnome-desktop-service-type (gnome-configuration (gnome my-gnome))) %desktop-services)) ... )
End of part 2
By now we can find almost every detail about a service without ever opening its documentation - a good example of how Guix is self-documenting. With this knowledge we have great power in customizing the system services but we unfortunately can't write our own yet.
Next time I'd like to look a little deeper into service definition and even define some basic services. After that we can move onto one of the most exciting parts - defining and building our own packages!