Table of Contents
I recently met a Guix user in the wild, and they mentioned how much they are chomping at the bit to get their hands dirty with goblins-enabled shepherd. I responded without hesitation something to the affect of: if you really want to hack on goblins shepherd, it's on a public dev branch. You can hack on it now. Then it hit me. Why wasn't I hacking on goblins shepherd? Like right now.
1. Obtaining an Engoblined Shepherd
Poking around I eventually found a definition for how to build the development goblins https://codeberg.org/shepherd/shepherd/src/commit/4ea2f1fb0eeb11f2cb91535ef9d284468eb7c11c/.guix/modules/shepherd-package.scm. I then adapted this definition into a single-file repo here: https://codeberg.org/eikcaz/give-me-goblins-shepherd. This allows one to get the package as simply as:
(use-modules (guix inferior) (guix channels)) (define goblins-shepherd (first (lookup-inferior-packages (inferior-for-channels (list (channel (name 'give-me-shepherd-now) (url "https://codeberg.org/eikcaz/give-me-goblins-shepherd.git") (branch "master") (commit "4f29aa202699f2a9b27d4c80664d7591e06e8776") (introduction (make-channel-introduction "4f29aa202699f2a9b27d4c80664d7591e06e8776" (openpgp-fingerprint "29E5 9602 FDB5 ECCD B867 2DA0 FC9C 2642 94D8 E825")))) (channel (name 'guix) (url "https://git.guix.gnu.org/guix.git") (branch "master") (commit "6c6e7ada010b1f7195253967dfbd8419549f6997") (introduction (make-channel-introduction "9edb3f66fd807b096b48283debdcddccfea34bad" (openpgp-fingerprint "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))) "shepherd")))
I pinned the guix version as well because at some point a test case started failing. Maybe that test case isn't important, but this seemed safer.
2. The Shaman service guides
These are terms that will likely evaporate once goblins in shepherd becomes mainline. However, in the meantime, the engoblined shepherd is not expected to be stable, so we shouldn't use it as the main shepherd instance. Instead, we are going to make a sub-shepherd. Surely a the shepherd of goblins is their shaman who guides them. Here's a bit of code golf to get a repl up and running (80 column rule respected except long strings):
(use-modules (srfi srfi-1) (guix inferior) (guix channels) (guix gexp) (gnu services) (gnu home services shepherd)) (define goblins-shepherd (first (lookup-inferior-packages (inferior-for-channels (list (channel (name 'give-me-shepherd-now) (url "https://codeberg.org/eikcaz/give-me-goblins-shepherd.git") (branch "master") (commit "4f29aa202699f2a9b27d4c80664d7591e06e8776") (introduction (make-channel-introduction "4f29aa202699f2a9b27d4c80664d7591e06e8776" (openpgp-fingerprint "29E5 9602 FDB5 ECCD B867 2DA0 FC9C 2642 94D8 E825")))) (channel (name 'guix) (url "https://git.guix.gnu.org/guix.git") (branch "master") (commit "6c6e7ada010b1f7195253967dfbd8419549f6997") (introduction (make-channel-introduction "9edb3f66fd807b096b48283debdcddccfea34bad" (openpgp-fingerprint "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA")))))) "shepherd"))) (define shaman-service (shepherd-service (provision '(shaman)) (documentation "") (stop #~(make-kill-destructor)) (start #~(make-forkexec-constructor `(#$(file-append goblins-shepherd "/bin/shepherd") "--socket=.config/shepherd/goblins-shepherd-socket" ,(format #f "--config=~a" #$((@@ (gnu home services shepherd) home-shepherd-configuration-file) (home-shepherd-configuration (daemonize? #f) (services (list (shepherd-service (provision '(repl)) (modules '((shepherd service repl))) (free-form #~(repl-service ".config/shepherd/goblins-repl-socket"))))))))))))) (home-environment (services (list (simple-service 'goblins-shepherd home-shepherd-service-type (list shaman-service)))))
Then you can connect to the repl from Geiser with:
M-x geiser-connect-local RET .config/shepherd/goblins-repl-socket RET.
Or if you haven't accepted emacs+geiser into your heart (yet), you can use netcat
nc -U .config/shepherd/goblins-shepherd-socket
Now, if you want to interact with the shaman service in the way you are used to as an end user, you will need to add a service to configure your shell:
(service home-zsh-service-type
(home-zsh-configuration
(zshrc (list
(mixed-text-file "goblins-herd"
"alias guide='"
goblins-shepherd "/bin/herd"
" --socket=.config/shepherd/goblins-shepherd-socket'")))))
Then you can verify the nested shepherd with
guide status
If you don't want to mess with your home environment, you can always do things in a container (changing paths accordingly):
mkdir -p .config/shepherd-test-config/shepherd chmod go-xr .config/shepherd-test-config/shepherd guix home container --share=/home/zacchae/.config/shepherd-test-config=/home/zacchae/.config goblins-home-minimal.scm
(then you would geiser-connect-local or nc to
.config/shepherd-test-config/shepherd/goblins-repl-socket)
3. Playing at the REPL
Reading through the Spritely blog post and poking around, I realize
I'm at least two levels of abstraction removed from whatever's going
on there. All I know is Guix, so I know of guix records that
represent shepherd services. One level deeper, the guix records get
serialized into shepherd service definitions that are read by
shepherd. A second level deeper, those services are registered in a
"registry" (don't ask, I don't know), which seems to have some other
representation. Then somewhere, goblins got hacked in but I'm already
lost. The blog post writes code that makes sense to a goblins user,
but how do I get there from the things I'd find in guix's shepherd
glue code, or better yet the shepherd documentation? I want to start
with register-services and lookup-service, and to end with being
able to start and stop a service with goblins. Here's my minimal
reproducer proof-of-concept of another vat being able to stop and
restart a service passing along a parameter:
(use-modules (shepherd service) (shepherd goblins-support) (shepherd comm) ; %current-client-socket (goblins)) (define test-service (service '(test-service) #:start identity #:one-shot? #t #:stop identity)) (register-services (list test-service)) (define (engoblin-service service) ((@@ (shepherd service) service-control) service)) (define launched-service (engoblin-servce (lookup-service 'test-service))) ;; adapted from start-service and stop-service defs in (shepherd service) (define resetter-cap (let ((shepherd ((@@ (shepherd service) current-shepherd))) (port (current-output-port)) (socket (%current-client-socket))) (define (^service-resetter bcom service*) (lambda (parameter) ;; stop service (on (<- shepherd 'stop service* (spawn ^writer port socket)) (lambda _ ;; start service with parameter (on (<- shepherd 'start service* (spawn ^writer port socket) parameter) (lambda _ parameter) #:promise? #t)) #:promise? #t))) (with-shepherd-vat (spawn ^service-resetter launched-service)))) ;; returns 'foo (resolve (with-vat (spawn-vat) (<- resetter-cap 'foo)))
4. Next steps
The main next steps are networking/netlayers/captp (those all mean different things apparently), and then hacking that solution into a guix configuration.
5. legal
🄯 Copyheart Zacchaeus 2026 CC-BY-SA 4.0