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

Author: Zacchaeus

Created: 2026-05-22 Fri 11:29

Validate