Hello, Ian Eure writes: > Hi folks, > > I’m trying to make my home configuration more modular, so I can better support > system variances. For example, I have a laptop I use interactively, and a > headless machine that runs Cuirass. It’s advantageous to share certain aspects > of the home configuration between the two machines (shell prompt/environment, > GPG agent, etc), but not others (anything X11/graphical stuff shouldn’t be on > the build machine). One approach to this is to define packages and services and > reference them in the home configuration. What I dislike about this is that > many things require both packages and services, and I’d prefer to have a way to > completely encapsulate that -- for example, the mpd-mpc package to control my > music server, plus a home-environment-variables-service-type to set MPD_HOST. > > I attempted to solve this by writing a procedure: > > (define (+mpd-client home-config) > (home-environment > (inherit home-config) > (packages (cons mpd-mpc (home-environment-packages home-config))) > (services > (cons > (simple-service > 'mpd-environment-service > home-environment-variables-service-type > '(("MPD_HOST" . "audio.box"))) > (home-environment-services home-config))))) > > Which I can then wrap around a home-environment to add the mpd-mpc package and > environment variable it needs to work: > > (+mpc-client (home-environment ...)) > > Surprisingly, this doesn’t work -- it complains that there’s more than one > "home" service type. I’m not sure why that is, and I haven’t been able to see > anything obviously wrong in the REPL -- though I haven’t been able to get my > actual home configuration up in the Emacs-Guix REPL, due to #67290. It is the same as with operating-system. While you are setting the (services) field, the accessor is (home-environment-user-services). (home-environment-services home) returns (at least as far I understand it) something like (append (home-environment-user-services home) (home-environment-essential-services home)). Hence the "more that one home services type", you get it twice, once via the (services) you set in +mpc-client (because it includes essential-services), and once by the essential-services directly. > > Does anyone have a suggestion for a workaround for this issue, explanation of > how two home services are ending up in the config, See above. > or a better approach for building modular home configs? Not sure if better, but different. I am using two variations of the same approach. One is to define a new service type that takes care of adding all necessary bits into the home environment. The following for example defines home-keychain-service-type for starting keychain[0]. --8<---------------cut here---------------start------------->8--- (define-configuration/no-serialization home-keychain-configuration (keychain-package (package keychain) "Package to use keychain from.") (ssh-package (package openssh) "SSH package to add into the profile.") (gpg-package (package gnupg) "GPG package to add into the profile.") (ssh-keys (list-of-strings '()) "Ensure the ssh-agent is started and register the listed keys into it.") (gpg-keys (list-of-strings '()) "Ensure the gpg-agent is started and register the listed keys into it.")) (define (q s) "Quote string into a form suitable for shell." (string-append "'" (string-replace-substring s "'" "'\\'") "'")) (define (home-keychain-configuration->file config) (let* ((ssh-keys (home-keychain-configuration-ssh-keys config)) (ssh? (pair? ssh-keys)) (gpg-keys (home-keychain-configuration-gpg-keys config)) (gpg? (pair? gpg-keys)) (agents (string-join (append (if ssh? '("ssh") '()) (if gpg? '("gpg") '())) ",")) (keys (string-append (string-join (map q ssh-keys) " " 'suffix) (string-join (map q gpg-keys) " " 'suffix)))) (mixed-text-file "keychain-init" "eval $(" keychain "/bin/keychain" " --agents " agents " --eval " " --quiet " " -Q " keys ")"))) (define (home-keychain-bash config) (home-bash-extension (bash-profile (list (home-keychain-configuration->file config))))) (define (home-keychain-profile config) (list (home-keychain-configuration-ssh-package config) (home-keychain-configuration-gpg-package config))) (define home-keychain-service-type (service-type (name 'home-keychain) (extensions (list (service-extension home-bash-service-type home-keychain-bash) (service-extension home-profile-service-type home-keychain-profile))) (description "Start a keychain on login."))) --8<---------------cut here---------------end--------------->8--- The interesting piece here is the `home-profile-service-type', which allows a service to add additional packages into the home environment (same as you would do with (package) field). However the limitation is that some services do not support extensions, so for those case I just have procedure or list (depending on whether configuration is required) with the required services, and use (append). Following is an example from my configuration: --8<---------------cut here---------------start------------->8--- (define* (home-desktop-services #:key (mpv-config %default-mpv-config)) (list (simple-service 'pkgs-desktop home-profile-service-type %desktop-packages) (simple-service 'home-files home-files-service-type `((".xinitrc" ,file/xinitrc) (".Xresources" ,file/Xresources))) (simple-service 'im-env-vars home-environment-variables-service-type '(("GTK_IM_MODULE" . "ibus") ("QT_IM_MODULE" . "ibus") ("XMODIFIERS" . "@im=ibus") ("GTK2_RC_FILES" . "$HOME/.config/gtk-2.0/gtkrc") ;; TODO: Are these still required? If yes, try to get rid of them. ("GUIX_GTK2_IM_MODULE_FILE" . "$HOME/.guix-home/profile/lib/gtk-2.0/2.10.0/immodules-gtk2.cache") ("GUIX_GTK3_IM_MODULE_FILE" . "$HOME/.guix-home/profile/lib/gtk-3.0/3.0.0/immodules-gtk3.cache"))) (simple-service 'desktop-xdg-config-files home-xdg-configuration-files-service-type `(("i3/config" ,file/i3/config) ("gtk-2.0/gtkrc" ,file/gtk-2/gtkrc) ("gtk-3.0/settings.ini" ,file/gtk-3/settings.ini) ("gtk-3.0/gtk.css" ,file/gtk-3/gtk.css) ("mpv/mpv.conf" ,(mpv-config->file mpv-config)))) (service home-startx-command-service-type) (service home-dbus-service-type))) --8<---------------cut here---------------end--------------->8--- And then my in my home environment: --8<---------------cut here---------------start------------->8--- (let ((home %basic-home)) (home-environment (inherit home) (packages ...) (services (append (list ...) (home-desktop-services) (home-environment-user-services home))))) --8<---------------cut here---------------end--------------->8--- I am not convinced this approach is the best, originally I had it the same way you had in a functional style with a helper procedure, but rewrote it to this model since Guix already has a concept of "services", so I wanted to try to mirror it. Maybe I will switch back to the functional style. Not sure yet. Hope this helps, Tomas 0: https://www.funtoo.org/Funtoo:Keychain -- There are only two hard things in Computer Science: cache invalidation, naming things and off-by-one errors.