GNU Shepherd user services
One of the things which sets Guix apart from other GNU/Linux distributions is that it uses GNU Shepherd instead of the now ubiquitous systemd. A side effect of this is that user systemd units do not work on Guix System. Love, hate or extremely ambivalent toward systemd, this means that users cannot rely on already written systemd unit files for their regular user-level services.
There are a couple of benefits to using GNU Shepherd, and not all of them are due to it already being installed on Guix. Becoming comfortable with using Shepherd and understanding how to write and edit Shepherd service configurations makes the transition from other GNU/Linux distributions to Guix System easier. More complex services with their own logic tree, using the full power of GNU Guile, are also possible. This means you can have one service that behaves differently if it's running on a different system or architecture without needing to call out to shell scripts or using minimally different service definitions.
The GNU Shepherd manual
suggests
putting all the services inside a
monolithic init.scm
file, located by default at
$XDG_CONFIG_DIR/shepherd/init.scm
. While this does make it easy to keep
everything in one place, it does create one glaring issue: any changes
to the file mean that all the services need to be stopped and restarted
in order for any changes to take place.
Luckily there's a nice function called scandir
hiding in ice-9 ftw
which returns a list of all files in a specified directory (with options
for narrowing down the list or sorting it). This means that our init.scm
can contain a minimum of code and all actual services can be loaded from
individual files.
First the minimal init.scm
:
(use-modules (shepherd service)
((ice-9 ftw) #:select (scandir)))
;; Load all the files in the directory 'init.d' with a suffix '.scm'.
(for-each
(lambda (file)
(load (string-append "init.d/" file)))
(scandir (string-append (dirname (current-filename)) "/init.d")
(lambda (file)
(string-suffix? ".scm" file))))
;; Send shepherd into the background
(action 'shepherd 'daemonize)
Let's take a sample service for running syncthing, as defined in
$XDG_CONFIG_DIR/shepherd/init.d/syncthing.scm
:
(define syncthing
(make <service>
#:provides '(syncthing)
#:docstring "Run `syncthing' without calling the browser"
#:start (make-forkexec-constructor
'("syncthing" "-no-browser")
#:log-file (string-append (getenv "HOME")
"/log/syncthing.log"))
#:stop (make-kill-destructor)
#:respawn? #t))
(register-services syncthing)
(start syncthing)
As with any other shepherd service it is defined and registered, and in
this case it will start automatically. When the file is loaded by
shepherd after being discovered by scandir everything works exactly as
though the service definition were located directly inside the init.scm
.
Now lets make a change. Since syncthing already has a -logfile
flag and
it has built-in log rotation that sounds better than using shepherd's
#:log-file
option. First we'll make our changes to the service:
(define syncthing
(make <service>
#:provides '(syncthing)
#:docstring "Run `syncthing' without calling the browser"
#:start (make-forkexec-constructor
'("syncthing" "-no-browser"
"-logflags=3" ; prefix with date & time
"-logfile=/home/user/log/syncthing.log"))
#:stop (make-kill-destructor)
#:respawn? #t))
(register-services syncthing)
(start syncthing)
Now we stop syncthing:
$ herd stop syncthing
And we load the new service:
$ herd load root ~/.config/shepherd/init.d/syncthing.scm
This allows for quickly iterating on services without needing to stop all the services! Let's take a look at another service:
(define fccache
(make <service>
#:provides '(fccache)
#:docstring "Run 'fc-cache -frv'"
#:start (make-forkexec-constructor
'("guix" "environment" "--ad-hoc" "fontconfig" "--"
"fc-cache" "-frv")
#:log-file (string-append (getenv "HOME")
"/log/fccache.log"))
#:one-shot? #t))
(register-services fccache)
In this example I want to refresh my font cache but I don't want to actually install fontconfig either system-wide or in my profile.
$ which fc-cache
which: no fc-cache in (/home/user/.config/guix/current/bin:/home/user/.guix-profile/bin:/home/user/.guix-profile/sbin:/run/setuid-programs:/run/current-system/profile/bin:/run/current-system/profile/sbin)
$ herd start fccache
Service fccache has been started.
Of course we can import other modules and leverage the code already
written there. In this case, instead of using the string "guix
environment --ad-hoc fontutils -- fc-cache -frv" let's use the guix environment
function already available in guix scripts environment
:
(use-modules (guix scripts environment))
(define fccache
(make <service>
#:provides '(fccache)
#:docstring "Run 'fc-cache -frv'"
#:start (lambda () ; Don't run immediately when registered!
(guix-environment "--ad-hoc" "fontconfig" "--" "fc-cache" "-frv"))
#:one-shot? #t))
(register-services fccache)
$ herd load root ~/.config/shepherd/init.d/fccache.scm
Loading /home/user/.config/shepherd/init.d/fccache.scm.
$ herd start fccache
/gnu/store/hbqlzgd8hcf6ndcmx7q7miqrsxb4dmkk-gs-fonts-8.11/share/fonts: caching, new cache contents: 0 fonts, 1 dirs
/gnu/store/hbqlzgd8hcf6ndcmx7q7miqrsxb4dmkk-gs-fonts-8.11/share/fonts/type1: caching, new cache contents: 0 fonts, 1 dirs
/gnu/store/hbqlzgd8hcf6ndcmx7q7miqrsxb4dmkk-gs-fonts-8.11/share/fonts/type1/ghostscript: caching, new cache contents: 35 fonts, 0 dirs
/home/user/.guix-profile/share/fonts: caching, new cache contents: 0 fonts, 7 dirs
/home/user/.guix-profile/share/fonts/opentype: caching, new cache contents: 8 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/otf: caching, new cache contents: 12 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/terminus: caching, new cache contents: 18 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/truetype: caching, new cache contents: 58 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/ttf: caching, new cache contents: 12 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/type1: caching, new cache contents: 0 fonts, 1 dirs
/home/user/.guix-profile/share/fonts/type1/ghostscript: caching, new cache contents: 35 fonts, 0 dirs
/home/user/.guix-profile/share/fonts/woff: caching, new cache contents: 1 fonts, 0 dirs
/run/current-system/profile/share/fonts: skipping, no such directory
/home/user/.local/share/fonts: skipping, no such directory
/home/user/.fonts: skipping, no such directory
/gnu/store/hbqlzgd8hcf6ndcmx7q7miqrsxb4dmkk-gs-fonts-8.11/share/fonts/type1: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/opentype: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/otf: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/terminus: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/truetype: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/ttf: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/type1: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/woff: skipping, looped directory detected
/gnu/store/hbqlzgd8hcf6ndcmx7q7miqrsxb4dmkk-gs-fonts-8.11/share/fonts/type1/ghostscript: skipping, looped directory detected
/home/user/.guix-profile/share/fonts/type1/ghostscript: skipping, looped directory detected
/var/cache/fontconfig: not cleaning unwritable cache directory
/home/user/.cache/fontconfig: cleaning cache directory
/home/user/.fontconfig: not cleaning non-existent cache directory
fc-cache: succeeded
herd: exception caught while executing 'start' on service 'fccache':
Throw to key `quit' with args `(0)'.
The problem with this approach is that guix-environment
returns the
exit
code
of the programs it calls and #:start
expects a
constructor
to return #t
or #f
so there's some work to be done here.
This was just a quick peek into what's possible with GNU Shepherd when run as a user. Next time we'll take a look at integrating mcron to replicate some of systemd's timer functionality.
About GNU Guix
GNU Guix is a transactional package manager and an advanced distribution of the GNU system that respects user freedom. Guix can be used on top of any system running the kernel Linux, or it can be used as a standalone operating system distribution for i686, x86_64, ARMv7, and AArch64 machines.
In addition to standard package management features, Guix supports transactional upgrades and roll-backs, unprivileged package management, per-user profiles, and garbage collection. When used as a standalone GNU/Linux distribution, Guix offers a declarative, stateless approach to operating system configuration management. Guix is highly customizable and hackable through Guile programming interfaces and extensions to the Scheme language.
Unless otherwise stated, blog posts on this site are copyrighted by their respective authors and published under the terms of the CC-BY-SA 4.0 license and those of the GNU Free Documentation License (version 1.3 or later, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts).