services: Create /var/run/utmpx upon activation.
[jackhill/guix/guix.git] / gnu / services.scm
index 8182523..f72d4d5 100644 (file)
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2015 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
   #:use-module (guix monads)
   #:use-module (guix store)
   #:use-module (guix records)
+  #:use-module (guix profiles)
   #:use-module (guix sets)
   #:use-module (guix ui)
+  #:use-module (guix modules)
   #:use-module (gnu packages base)
   #:use-module (gnu packages bash)
   #:use-module (srfi srfi-1)
@@ -35,6 +38,8 @@
   #:use-module (ice-9 match)
   #:export (service-extension
             service-extension?
+            service-extension-target
+            service-extension-compute
 
             service-type
             service-type?
@@ -48,6 +53,7 @@
             service-kind
             service-parameters
 
+            simple-service
             modify-services
             service-back-edges
             fold-services
             ambiguous-target-service-error-service
             ambiguous-target-service-error-target-type
 
+            system-service-type
             boot-service-type
+            cleanup-service-type
             activation-service-type
             activation-service->script
+            %linux-bare-metal-service
             etc-service-type
             etc-directory
             setuid-program-service-type
+            profile-service-type
             firmware-service-type
+            gc-root-service-type
 
             %boot-service
             %activation-service
 ;;; A service type describe how its instances extend instances of other
 ;;; service types.  For instance, some services extend the instance of
 ;;; ACCOUNT-SERVICE-TYPE by providing it with accounts and groups to create;
-;;; others extend DMD-ROOT-SERVICE-TYPE by passing it instances of
-;;; <dmd-service>.
+;;; others extend SHEPHERD-ROOT-SERVICE-TYPE by passing it instances of
+;;; <shepherd-service>.
 ;;;
 ;;; When applicable, the service type defines how it can itself be extended,
 ;;; by providing one procedure to compose extensions, and one procedure to
 ;;; extend itself.
 ;;;
-;;; A notable service type is BOOT-SERVICE-TYPE, which has a single instance,
-;;; %BOOT-SERVICE.  %BOOT-SERVICE constitutes the root of the service DAG.  It
-;;; produces the boot script that the initrd loads.
+;;; A notable service type is SYSTEM-SERVICE-TYPE, which has a single
+;;; instance, which is the root of the service DAG.  Its value is the
+;;; derivation that produces the 'system' directory as returned by
+;;; 'operating-system-derivation'.
 ;;;
 ;;; The 'fold-services' procedure can be passed a list of procedures, which it
 ;;; "folds" by propagating extensions down the graph; it returns the root
   (type       service-kind)
   (parameters service-parameters))
 
+(define (simple-service name target value)
+  "Return a service that extends TARGET with VALUE.  This works by creating a
+singleton service type NAME, of which the returned service is an instance."
+  (let* ((extension (service-extension target identity))
+         (type      (service-type (name name)
+                                  (extensions (list extension)))))
+    (service type value)))
 
 (define-syntax %modify-service
   (syntax-rules (=>)
 
 (define-syntax modify-services
   (syntax-rules ()
-    "Modify the services listed in SERVICES according to CLAUSES.  Each clause
-must have the form:
+    "Modify the services listed in SERVICES according to CLAUSES and return
+the resulting list of services.  Each clause must have the form:
 
   (TYPE VARIABLE => BODY)
 
@@ -181,42 +200,90 @@ This is a shorthand for (map (lambda (svc) ...) %base-services)."
 ;;; Core services.
 ;;;
 
-(define (compute-boot-script mexps)
+(define (system-derivation mentries mextensions)
+  "Return as a monadic value the derivation of the 'system' directory
+containing the given entries."
+  (mlet %store-monad ((entries    mentries)
+                      (extensions (sequence %store-monad mextensions)))
+    (lower-object
+     (file-union "system"
+                 (append entries (concatenate extensions))))))
+
+(define system-service-type
+  ;; This is the ultimate service type, the root of the service DAG.  The
+  ;; service of this type is extended by monadic name/item pairs.  These items
+  ;; end up in the "system directory" as returned by
+  ;; 'operating-system-derivation'.
+  (service-type (name 'system)
+                (extensions '())
+                (compose identity)
+                (extend system-derivation)))
+
+(define (compute-boot-script _ mexps)
   (mlet %store-monad ((gexps (sequence %store-monad mexps)))
     (gexp->file "boot"
-                #~(begin
-                    (use-modules (guix build utils))
-
-                    ;; Clean out /tmp and /var/run.
-                    ;;
-                    ;; XXX This needs to happen before service activations, so
-                    ;; it has to be here, but this also implicitly assumes
-                    ;; that /tmp and /var/run are on the root partition.
-                    (false-if-exception (delete-file-recursively "/tmp"))
-                    (false-if-exception (delete-file-recursively "/var/run"))
-                    (false-if-exception (mkdir "/tmp"))
-                    (false-if-exception (chmod "/tmp" #o1777))
-                    (false-if-exception (mkdir "/var/run"))
-                    (false-if-exception (chmod "/var/run" #o755))
-
-                    ;; Activate the system and spawn dmd.
-                    #$@gexps))))
+                ;; Clean up and activate the system, then spawn shepherd.
+                #~(begin #$@gexps))))
 
-(define (second-argument a b) b)
+(define (boot-script-entry mboot)
+  "Return, as a monadic value, an entry for the boot script in the system
+directory."
+  (mlet %store-monad ((boot mboot))
+    (return `(("boot" ,boot)))))
 
 (define boot-service-type
   ;; The service of this type is extended by being passed gexps as monadic
   ;; values.  It aggregates them in a single script, as a monadic value, which
   ;; becomes its 'parameters'.  It is the only service that extends nothing.
   (service-type (name 'boot)
-                (extensions '())
-                (compose compute-boot-script)
-                (extend second-argument)))
+                (extensions
+                 (list (service-extension system-service-type
+                                          boot-script-entry)))
+                (compose append)
+                (extend compute-boot-script)))
 
 (define %boot-service
-  ;; This is the ultimate service, the root of the service DAG.
+  ;; The service that produces the boot script.
   (service boot-service-type #t))
 
+(define (cleanup-gexp _)
+  "Return as a monadic value a gexp to clean up /tmp and similar places upon
+boot."
+  (with-monad %store-monad
+    (with-imported-modules '((guix build utils))
+      (return #~(begin
+                  (use-modules (guix build utils))
+
+                  ;; Clean out /tmp and /var/run.
+                  ;;
+                  ;; XXX This needs to happen before service activations, so it
+                  ;; has to be here, but this also implicitly assumes that /tmp
+                  ;; and /var/run are on the root partition.
+                  (letrec-syntax ((fail-safe (syntax-rules ()
+                                               ((_ exp rest ...)
+                                                (begin
+                                                  (catch 'system-error
+                                                    (lambda () exp)
+                                                    (const #f))
+                                                  (fail-safe rest ...)))
+                                               ((_)
+                                                #t))))
+                    ;; Ignore I/O errors so the system can boot.
+                    (fail-safe
+                     (delete-file-recursively "/tmp")
+                     (delete-file-recursively "/var/run")
+                     (mkdir "/tmp")
+                     (chmod "/tmp" #o1777)
+                     (mkdir "/var/run")
+                     (chmod "/var/run" #o755))))))))
+
+(define cleanup-service-type
+  ;; Service that cleans things up in /tmp and similar.
+  (service-type (name 'cleanup)
+                (extensions
+                 (list (service-extension boot-service-type
+                                          cleanup-gexp)))))
+
 (define* (file-union name files)                  ;FIXME: Factorize.
   "Return a <computed-file> that builds a directory containing all of FILES.
 Each item in FILES must be a list where the first element is the file name to
@@ -228,7 +295,12 @@ file."
                      (chdir #$output)
                      #$@(map (match-lambda
                                ((target source)
-                                #~(symlink #$source #$target)))
+                                #~(begin
+                                    ;; Stat the source to abort early if it
+                                    ;; does not exist.
+                                    (stat #$source)
+
+                                    (symlink #$source #$target))))
                              files))))
 
 (define (directory-union name things)
@@ -239,24 +311,10 @@ file."
      one)
     (_
      (computed-file name
-                    #~(begin
-                        (use-modules (guix build union))
-                        (union-build #$output '#$things))
-                    #:modules '((guix build union))))))
-
-(define (modprobe-wrapper)
-  "Return a wrapper for the 'modprobe' command that knows where modules live.
-
-This wrapper is typically invoked by the Linux kernel ('call_modprobe', in
-kernel/kmod.c), a situation where the 'LINUX_MODULE_DIRECTORY' environment
-variable is not set---hence the need for this wrapper."
-  (let ((modprobe "/run/current-system/profile/bin/modprobe"))
-    (gexp->script "modprobe"
-                  #~(begin
-                      (setenv "LINUX_MODULE_DIRECTORY"
-                              "/run/booted-system/kernel/lib/modules")
-                      (apply execl #$modprobe
-                             (cons #$modprobe (cdr (command-line))))))))
+                    (with-imported-modules '((guix build union))
+                      #~(begin
+                          (use-modules (guix build union))
+                          (union-build #$output '#$things)))))))
 
 (define* (activation-service->script service)
   "Return as a monadic value the activation script for SERVICE, a service of
@@ -265,57 +323,44 @@ ACTIVATION-SCRIPT-TYPE."
 
 (define (activation-script gexps)
   "Return the system's activation script, which evaluates GEXPS."
-  (define %modules
-    '((gnu build activation)
-      (gnu build linux-boot)
-      (gnu build linux-modules)
-      (gnu build file-systems)
-      (guix build utils)
-      (guix build syscalls)
-      (guix elf)))
-
   (define (service-activations)
     ;; Return the activation scripts for SERVICES.
     (mapm %store-monad
           (cut gexp->file "activate-service" <>)
           gexps))
 
-  (mlet* %store-monad ((actions  (service-activations))
-                       (modules  (imported-modules %modules))
-                       (compiled (compiled-modules %modules))
-                       (modprobe (modprobe-wrapper)))
+  (mlet* %store-monad ((actions (service-activations)))
     (gexp->file "activate"
-                #~(begin
-                    (eval-when (expand load eval)
-                      ;; Make sure 'use-modules' below succeeds.
-                      (set! %load-path (cons #$modules %load-path))
-                      (set! %load-compiled-path
-                        (cons #$compiled %load-compiled-path)))
-
-                    (use-modules (gnu build activation))
-
-                    ;; Make sure /bin/sh is valid and current.
-                    (activate-/bin/sh
-                     (string-append #$(canonical-package bash) "/bin/sh"))
+                (with-imported-modules (source-module-closure
+                                        '((gnu build activation)))
+                  #~(begin
+                      (use-modules (gnu build activation))
 
-                    ;; Tell the kernel to use our 'modprobe' command.
-                    (activate-modprobe #$modprobe)
+                      ;; Make sure /bin/sh is valid and current.
+                      (activate-/bin/sh
+                       (string-append #$(canonical-package bash) "/bin/sh"))
 
-                    ;; Let users debug their own processes!
-                    (activate-ptrace-attach)
+                      ;; Make sure the user accounting database exists.  If it
+                      ;; does not exist, 'setutxent' does not create it and
+                      ;; thus there is no accounting at all.
+                      (close-port (open-file "/var/run/utmpx" "a0"))
 
-                    ;; Run the services' activation snippets.
-                    ;; TODO: Use 'load-compiled'.
-                    (for-each primitive-load '#$actions)
+                      ;; Set up /run/current-system.  Among other things this
+                      ;; sets up locales, which the activation snippets
+                      ;; executed below may expect.
+                      (activate-current-system)
 
-                    ;; Set up /run/current-system.
-                    (activate-current-system)))))
+                      ;; Run the services' activation snippets.
+                      ;; TODO: Use 'load-compiled'.
+                      (for-each primitive-load '#$actions))))))
 
 (define (gexps->activation-gexp gexps)
   "Return a gexp that runs the activation script containing GEXPS."
   (mlet %store-monad ((script (activation-script gexps)))
     (return #~(primitive-load #$script))))
 
+(define (second-argument a b) b)
+
 (define activation-service-type
   (service-type (name 'activate)
                 (extensions
@@ -329,6 +374,41 @@ ACTIVATION-SCRIPT-TYPE."
   ;; receives.
   (service activation-service-type #t))
 
+(define %modprobe-wrapper
+  ;; Wrapper for the 'modprobe' command that knows where modules live.
+  ;;
+  ;; This wrapper is typically invoked by the Linux kernel ('call_modprobe',
+  ;; in kernel/kmod.c), a situation where the 'LINUX_MODULE_DIRECTORY'
+  ;; environment variable is not set---hence the need for this wrapper.
+  (let ((modprobe "/run/current-system/profile/bin/modprobe"))
+    (program-file "modprobe"
+                  #~(begin
+                      (setenv "LINUX_MODULE_DIRECTORY"
+                              "/run/booted-system/kernel/lib/modules")
+                      (apply execl #$modprobe
+                             (cons #$modprobe (cdr (command-line))))))))
+
+(define %linux-kernel-activation
+  ;; Activation of the Linux kernel running on the bare metal (as opposed to
+  ;; running in a container.)
+  #~(begin
+      ;; Tell the kernel to use our 'modprobe' command.
+      (activate-modprobe #$%modprobe-wrapper)
+
+      ;; Let users debug their own processes!
+      (activate-ptrace-attach)))
+
+(define linux-bare-metal-service-type
+  (service-type (name 'linux-bare-metal)
+                (extensions
+                 (list (service-extension activation-service-type
+                                          (const %linux-kernel-activation))))))
+
+(define %linux-bare-metal-service
+  ;; The service that does things that are needed on the "bare metal", but not
+  ;; necessary or impossible in a container.
+  (service linux-bare-metal-service-type #f))
+
 (define (etc-directory service)
   "Return the directory for SERVICE, a service of type ETC-SERVICE-TYPE."
   (files->etc-directory (service-parameters service)))
@@ -336,6 +416,12 @@ ACTIVATION-SCRIPT-TYPE."
 (define (files->etc-directory files)
   (file-union "etc" files))
 
+(define (etc-entry files)
+  "Return an entry for the /etc directory consisting of FILES in the system
+directory."
+  (with-monad %store-monad
+    (return `(("etc" ,(files->etc-directory files))))))
+
 (define etc-service-type
   (service-type (name 'etc)
                 (extensions
@@ -344,7 +430,8 @@ ACTIVATION-SCRIPT-TYPE."
                                      (lambda (files)
                                        (let ((etc
                                               (files->etc-directory files)))
-                                         #~(activate-etc #$etc))))))
+                                         #~(activate-etc #$etc))))
+                  (service-extension system-service-type etc-entry)))
                 (compose concatenate)
                 (extend append)))
 
@@ -363,6 +450,23 @@ FILES must be a list of name/file-like object pairs."
                 (compose concatenate)
                 (extend append)))
 
+(define (packages->profile-entry packages)
+  "Return a system entry for the profile containing PACKAGES."
+  (mlet %store-monad ((profile (profile-derivation
+                                (packages->manifest
+                                 (delete-duplicates packages eq?)))))
+    (return `(("profile" ,profile)))))
+
+(define profile-service-type
+  ;; The service that populates the system's profile---i.e.,
+  ;; /run/current-system/profile.  It is extended by package lists.
+  (service-type (name 'profile)
+                (extensions
+                 (list (service-extension system-service-type
+                                          packages->profile-entry)))
+                (compose concatenate)
+                (extend append)))
+
 (define (firmware->activation-gexp firmware)
   "Return a gexp to make the packages listed in FIRMWARE loadable by the
 kernel."
@@ -379,6 +483,33 @@ kernel."
                 (compose concatenate)
                 (extend append)))
 
+(define (gc-roots->system-entry roots)
+  "Return an entry in the system's output containing symlinks to ROOTS."
+  (mlet %store-monad ((entry (gexp->derivation
+                              "gc-roots"
+                              #~(let ((roots '#$roots))
+                                  (mkdir #$output)
+                                  (chdir #$output)
+                                  (for-each symlink
+                                            roots
+                                            (map number->string
+                                                 (iota (length roots))))))))
+    (return (if (null? roots)
+                '()
+                `(("gc-roots" ,entry))))))
+
+(define gc-root-service-type
+  ;; A service to associate extra garbage-collector roots to the system.  This
+  ;; is a simple hack that guarantees that the system retains references to
+  ;; the given list of roots.  Roots must be "lowerable" objects like
+  ;; packages, or derivations.
+  (service-type (name 'gc-roots)
+                (extensions
+                 (list (service-extension system-service-type
+                                          gc-roots->system-entry)))
+                (compose concatenate)
+                (extend append)))
+
 \f
 ;;;
 ;;; Service folding.
@@ -435,7 +566,8 @@ kernel."
     (lambda (node)
       (reverse (vhash-foldq* cons '() node edges)))))
 
-(define* (fold-services services #:key (target-type boot-service-type))
+(define* (fold-services services
+                        #:key (target-type system-service-type))
   "Fold SERVICES by propagating their extensions down to the root of type
 TARGET-TYPE; return the root service adjusted accordingly."
   (define dependents