Extending from shepherd-root-service-type

I’m trying to consolidate some of the references in my source code. I have package names kinda everywhere, so i thought i’d bundle things using service-type. However, I’m running into some weird issues as I do

I thought I could just pass service config values to service-extension. This is just a record. I figured out how to get past the udev issues, but for some reason, nothing I pass as a service extension to shepherd-root-service-type gets past Wrong type to apply

;;; Copyright � 2025 David Conner <aionfork@gmail.com>
(define-module (ellipsis services security-token)
  ;;#:use-module (ellipsis config)
  #:use-module (gnu home services)

  #:use-module (gnu)
  #:use-module (gnu packages)
  #:use-module (gnu packages gnupg)
  #:use-module (gnu packages libusb)
  #:use-module (gnu packages security-token)
  #:use-module (gnu services base)
  #:use-module (gnu services configuration)
  #:use-module (gnu services security-token)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services)

  #:use-module (ellipsis packages golang-crypto)
  #:use-module (ellipsis packages security-token)
  #:use-module (ellipsis packages tls)

  #:use-module (guix gexp)
  #:use-module (guix records)
  #:use-module (srfi srfi-1)
  #:use-module (ice-9 pretty-print)
  #:export (yubikey-udev-rules))

;;; Commentary:
;;;
;;; This module provides a service definition for system services related to
;;; security tokens.


;;;
;;; yubikey-udev-rules
;;;

(define-public yubikey-udev-rules
  ;; needs plugdev, but warns if multiple instantiations create using #:groups
  (list
   (udev-rules-service 'fido2 libfido2 #:groups '("plugdev"))
   (udev-rules-service 'u2f libu2f-host)
   (udev-rules-service 'yubikey yubikey-personalization)))

;; hidapi: HID Devices for FIDO/OTP
(define pkgs-smartcard
  (list opensc pinentry-tty hidapi libu2f-host libfido2))

(define pkgs-yubikey
  (list yubico-piv-tool yubikey-personalization python-yubikey-manager))

(define svc-pcscd
  (service pcscd-service-type))

(define-public ellipsis-smartcard-service-type
  (service-type
   (name 'ellipsis-smartcard)
   (extensions
    (list
     ;; pcsc-lite, ccid provided by service/activation
     (service-extension shepherd-root-service-type svc-pcscd) 
     (service-extension udev-service-type (lambda (config) yubikey-udev-rules))
     (service-extension profile-service-type
                        (lambda (config) (append pkgs-yubikey pkgs-smartcard)))))
   (default-value '())
   (description "Sets up some common services")))
;;; security-token.scm ends here

Here’s the error:

guix/ui.scm:1030:18: Wrong type to apply: #<<service> type: #<service-type pcscd 7ba08bebb800> value: #<<pcscd-configuration> pcsc-lite: #<package pcsc-lite@2.4.1 gnu/packages/security-token.scm:256 7ba090679580> usb-drivers: (#<package ccid@1.7.0 gnu/packages/security-token.scm:105 7ba090679840>)>>

In guix-hpc/guix-hpc-non-free ./hacky/services-gitlab.scm, they use:

(define gitlab-runner-service-type
  (service-type
   (name 'gitlab-runner)
   (description
    "Run gitlab-runner daemon @command{gitlab-runner run}.")
   (extensions
    (list (service-extension account-service-type
                             (const %gitlab-runner-accounts))
          (service-extension shepherd-root-service-type
                             (compose list gitlab-runner-shepherd-service))
          (service-extension activation-service-type
                             %gitlab-runner-activation)

          ;; 'gitlab-runner' wants to run Git, but since it runs its scripts
          ;; with 'bash --login', PATH is essentially limited to
          ;; /run/current-system/profile/bin.  So put Git in there.
          (service-extension profile-service-type
                             (const (list git-minimal)))))
   (default-value (gitlab-runner-configuration))))

Where gitlab-runner-shepherd-service is a match-lambda, but otherwise returns the result of (shepherd-service ...). What does compose do in (compose list gitlab-runner-shepherd-service), specifically?

In other codebases, I often see (lambda (config) ...) passed in. It’s a block that returns something that the service-type definition would know how to compose/apply. Why does that not work here?

Guix services are different from Shepherd services.

shepherd-root-service-type is a type of Guix service, which extends Guix System with the ability to manage daemons.

Take a look at guix system edit shepherd-root:

(define shepherd-root-service-type
  (service-type
   ...
   (compose concatenate)
   (extend (lambda (config extra-services)
             (shepherd-configuration
               (inherit config)
               (services (append (shepherd-configuration-services config)
                                 extra-services)))))
   (default-value (shepherd-configuration))))

The service type accepts a configuration value of shepherd-configuration, and can be extended with a list of shepherd-service (this is what the services field of the <shepherd-configuration> record expects).

(define-record-type* <shepherd-configuration>
  ...
  (services shepherd-configuration-services
            (default '()))) ; list of <shepherd-service>

And the (service pcscd-service-type) you used creates a service instance, which is different from both a service type and the shepherd-service we want.

Then take a look at guix system edit pcscd:

(define pcscd-service-type
  (service-type
   ...
   (extensions
    (list (service-extension shepherd-root-service-type
                             (compose list pcscd-shepherd-service))
          ...))

The service type extends shepherd-root-service-type with a list of shepherd-service created by the pcscd-shepherd-service procedure.

(The pcscd-shepherd-service procedure is not publicly exported so you can’t refer to it normally, and is not very useful for your use case, since it’s closely tied to the implementation of pcscd-service-type)

BTW the udev-rules-service defines service types which are not expected for extending udev-service-type. The service type should be extended by a list of file-like objects, like packages.


To address your issue, you can either

extend shepherd-root-service-type with a list of shepherd-service, then extend udev-service-type and account-service-type (that’s how the #:groups keyword is implemented) like what udev-rules-service does.

I recommend learning it if you have time, the service part of Guix is quite interesting.

or

instantiate service instance without explicitly defining a service type via simple-service:

(service pcscd-service-type)

(udev-rules-service 'fido2 libfido2 #:groups '("plugdev"))
(udev-rules-service 'u2f libu2f-host)
(udev-rules-service 'yubikey yubikey-personalization)

(simple-service 'ellipsis-smartcard-profile-service
                profile-service-type
                (append pkgs-yubikey pkgs-smartcard))

In this approach you can also organize these service instances as a list and append it into your operating system declaration.

thanks, i appreciate the writeup. i think a list of services is about as simple as i can get it.