My Guix System Desktop Setup

Friday, 19 July 2019

My Guix System configuration is stored in a public git repository.

My ~/.config/guix directory layout

In the root of the Guix config directory (~/.config/guix), I have three main parts of the configuration: the Guix System configuration itself (The file you run guix system reconfigure with), the channels specification (Refer to the manual for an explanation), and local mirrors of my personal repositories (Publically available at my Gitlab profile).

Reconfiguring with local repositories

By adding the local mirrors of my personal repositories to the Guile load path, I can make changes to those repositories and instantly build a system that incorporates those changes, without requiring an environment variable specified. Since these additions are specified in the file, I can reconfigure with root and the same load path additions will be applied, so I don't need to pass an environment variable.

I do this by adding a subdirectory of the config file's directory (which is in ~/.config/guix) to Guile's load path:

(use-modules (guix gexp)) ;; for local-file*

(define this-file
  (local-file-absolute-file-name
   (local-file
    (assoc-ref (current-source-location) 'filename))))

(define this-directory
  (dirname this-file))

(define (warncheck-path path)
  (if (file-exists? path)
      #t
      (format (current-error-port) (string-append "WARNING: couldn't find added load-path " path "\n"))))

(define (relative-add-to-load-path path-relative-to-this-file)
  (let* ((absolute-path (string-append
                         this-directory
                         path-relative-to-this-file)))
    (add-to-load-path absolute-path)
    (warncheck-path absolute-path)))

;; Adding these local paths to the load paths overrides the guix pull'd modules,
;;   and allows me to modify the channels and instantly test building a system
;;   with the changes. Since they are added using the file, root also gets
;;   these load paths.
(relative-add-to-load-path
   "/local-channels/pkill9-free")
(relative-add-to-load-path
 "/local-channels/pkill9-nonfree")

Splitting up the configuration into multiple files

I do this by adding the directory of the config file to Guile's load path, which will import the other configuration files which have operating system configurations assigned to a variable, and then inheriting that variable in the operating system configuration.

This part adds the config file's directory to Guile's load path, using the "relative-add-to-load-path" function (seen in the previous section):

(relative-add-to-load-path "")

I use this to import variables from other system configurations from the same directory - This allows me to reconfigure the system to a lightweight installation first, then boot into that, and then iteratively reconfigure with more complexity (e.g. more stuff needed to compile).

I create a configuration that inherits a system configuration from one of the other config files:

(define my-system-configuration
  (operating-system (inherit system-configuration-from-other-file)
    ...))

Guix channels: Local or remote repositories

Guix channels provide a way to add package definitions to your Guix revision that are provided from other sources than the official Guix repository. See the channels section of the Guix manual for more information.

My channels.scm uses local mirrors of my personal repositories. This means I can modify the repositories and pull the changes when i run guix pull. Currently however, the changes need to be committed to the repository as guix pull assumes the URLs are git repositories (Adding the "file://" prefix to the path means that git will use a local path).

I define a new function called "local-or-remote-channel" that uses a local URI for a channel if it's available (This is used in place of (channel ...)):

(define (local-or-remote-url local-channel-name remote-channel-url)
  (let* ((local-channel-path
          (lambda (channel-name)
            (string-append (getenv "HOME")
                           "/.config/guix/local-channels/"
                           channel-name)))
         (local-channel-git-path
          (lambda (channel-name)
            (string-append "file://"
                           (local-channel-path
                            channel-name)))))
    (if (file-exists? (local-channel-path local-channel-name))
        (local-channel-git-path local-channel-name)
        remote-channel-url)))

(define (local-or-remote-channel channel-name remote-channel-url)
  (channel
   (name (string->symbol channel-name))
   (url
    (local-or-remote-url channel-name remote-channel-url))))

I also use a new function called "extra-channels" which simply takes channels as arguments and automatically appends it to %default-channels:

(define extra-channels
  (lambda extra-channels
    (append extra-channels
            %default-channels)))

I then use (local-or-remote-channel ...) in (extra-channels ...), which is what guix pull will evaluate channels.scm to.:

(extra-channels (local-or-remote-channel
                 "pkill9-free"
                 "https://gitlab.com/pkill-9/guix-packages-free.git")
                (local-or-remote-channel
                 "pkill9-nonfree"
                 "https://gitlab.com/pkill-9/guix-packages-nonfree.git"))

Using an inferior for a custom version of the linux kernel

Since Guix produces a new store path if any of a package's inputs change, the package will be compiled again to produce this store path. Since the kernel takes a few hours to compile, I make it an inferior so it's inputs are frozen and thus I can use a newer Guix revision without recompiling the kernel. For more information, see the page in the Guix manual on inferiors.

I created a function in my pkill9-nonfree repository's linux-nonfree.scm module that takes a guix commit, a URL pointing to my pkill9-nonfree repository, and a commit for my pkill9-nonfree repository:

(define-public (get-linux-nonfree-inferior guix-commit
                                           channel-url
                                           channel-commit)
  (first
   (lookup-inferior-packages
    (inferior-for-channels
     (list (channel
            ;; Guix commit previously used to build linux-nonfree
            (name 'guix)
            (url "https://git.savannah.gnu.org/git/guix.git")
            (commit guix-commit))
           (channel
            ;; Linux-nonfree
            (name 'pkill9-nonfree)
            (url
             channel-url)
            (commit
             channel-commit))))
    (package-name linux))))

I use this function in the kernel field of my system configuration:

(define proprietary-desktop-os
  (operating-system (inherit desktop-os)
   ;;(kernel linux) ;; Non-inferior linux-nonfree kernel package.
   (kernel (get-linux-nonfree-inferior
            "48f19e60c4677e392ee2c23f28098cfcaf9d1710"
            (string-append "file:///home/itsme/.config/guix"
                           "/local-channels/pkill9-guix-packages-nonfree")
            "b15512d5dcf19c8aa905d0afb6f1ff631eaa2c06"))
   ...)

The FHS service

I built an FHS service that allows me to run downloaded binaries built for FHS-respecting Linux distributions. See my other post for more information.