services: gpm: Provide a default value and document 'gpm-service-type'.
[jackhill/guix/guix.git] / gnu / services / base.scm
index be30f2d..eb82b2d 100644 (file)
@@ -26,7 +26,6 @@
   #:use-module (guix store)
   #:use-module (gnu services)
   #:use-module (gnu services shepherd)
-  #:use-module (gnu services networking)
   #:use-module (gnu system pam)
   #:use-module (gnu system shadow)                ; 'user-account', etc.
   #:use-module (gnu system uuid)
             %default-console-font
             console-font-service-type
             console-font-service
+            virtual-terminal-service-type
+
+            static-networking
+
+            static-networking?
+            static-networking-interface
+            static-networking-ip
+            static-networking-netmask
+            static-networking-gateway
+            static-networking-requirement
+
+            static-networking-service
+            static-networking-service-type
 
             udev-configuration
             udev-configuration?
@@ -665,22 +677,27 @@ to add @var{device} to the kernel's entropy pool.  The service will fail if
   "Return a service that sets the host name to @var{name}."
   (service host-name-service-type name))
 
-(define (unicode-start tty)
-  "Return a gexp to start Unicode support on @var{tty}."
-  (with-imported-modules '((guix build syscalls))
-    #~(let* ((fd (open-fdes #$tty O_RDWR))
-             (termios (tcgetattr fd)))
-        (define (set-utf8-input termios)
-          (set-field termios (termios-input-flags)
-                     (logior (input-flags IUTF8)
-                             (termios-input-flags termios))))
-
-        (tcsetattr fd (tcsetattr-action TCSAFLUSH)
-                   (set-utf8-input termios))
-
-        ;; TODO: ioctl(fd, KDSKBMODE, K_UNICODE);
-        (close-fdes fd)
-        #t)))
+(define virtual-terminal-service-type
+  ;; Ensure that virtual terminals run in UTF-8 mode.  This is the case by
+  ;; default with recent Linux kernels, but this service allows us to ensure
+  ;; this.  This service must start before any 'term-' service so that newly
+  ;; created terminals inherit this property.  See
+  ;; <https://bugs.gnu.org/30505> for a discussion.
+  (shepherd-service-type
+   'virtual-terminal
+   (lambda (utf8?)
+     (shepherd-service
+      (documentation "Set virtual terminals in UTF-8 module.")
+      (provision '(virtual-terminal))
+      (requirement '(root-file-system))
+      (start #~(lambda _
+                 (call-with-output-file
+                     "/sys/module/vt/parameters/default_utf8"
+                   (lambda (port)
+                     (display 1 port)))
+                 #t))
+      (stop #~(const #f))))
+   #t))                                           ;default to UTF-8
 
 (define console-keymap-service-type
   (shepherd-service-type
@@ -719,8 +736,6 @@ to add @var{device} to the kernel's entropy pool.  The service will fail if
              (requirement (list (symbol-append 'term-
                                                (string->symbol tty))))
 
-             (modules '((guix build syscalls)     ;for 'tcsetattr'
-                        (srfi srfi-9 gnu)))       ;for 'set-field'
              (start #~(lambda _
                         ;; It could be that mingetty is not fully ready yet,
                         ;; which we check by calling 'ttyname'.
@@ -732,16 +747,18 @@ to add @var{device} to the kernel's entropy pool.  The service will fail if
                             (usleep 500)
                             (loop (- i 1))))
 
-                        (and #$(unicode-start device)
-                             ;; 'setfont' returns EX_OSERR (71) when an
-                             ;; KDFONTOP ioctl fails, for example.  Like
-                             ;; systemd's vconsole support, let's not treat
-                             ;; this as an error.
-                             (case (status:exit-val
-                                    (system* #$(file-append kbd "/bin/setfont")
-                                             "-C" #$device #$font))
-                               ((0 71) #t)
-                               (else #f)))))
+                        ;; Assume the VT is already in UTF-8 mode, thanks to
+                        ;; the 'virtual-terminal' service.
+                        ;;
+                        ;; 'setfont' returns EX_OSERR (71) when an
+                        ;; KDFONTOP ioctl fails, for example.  Like
+                        ;; systemd's vconsole support, let's not treat
+                        ;; this as an error.
+                        (case (status:exit-val
+                               (system* #$(file-append kbd "/bin/setfont")
+                                        "-C" #$device #$font))
+                          ((0 71) #t)
+                          (else #f))))
              (stop #~(const #t))
              (respawn? #f)))))
        tty+font))
@@ -937,119 +954,122 @@ to use as the tty.  This is primarily useful for headless systems."
          ;; mingetty-shepherd-service).
          (requirement '(user-processes host-name udev))
 
-         (start #~(let ((tty #$(default-serial-port)))
-                    (if tty
-                        (make-forkexec-constructor
-                         (list #$(file-append util-linux "/sbin/agetty")
-                               #$@extra-options
-                               #$@(if eight-bits?
-                                      #~("--8bits")
-                                      #~())
-                               #$@(if no-reset?
-                                      #~("--noreset")
-                                      #~())
-                               #$@(if remote?
-                                      #~("--remote")
-                                      #~())
-                               #$@(if flow-control?
-                                      #~("--flow-control")
-                                      #~())
-                               #$@(if host
-                                      #~("--host" #$host)
-                                      #~())
-                               #$@(if no-issue?
-                                      #~("--noissue")
-                                      #~())
-                               #$@(if init-string
-                                      #~("--init-string" #$init-string)
-                                      #~())
-                               #$@(if no-clear?
-                                      #~("--noclear")
-                                      #~())
+         (start #~(lambda args
+                    (let ((defaulted-tty #$(or tty (default-serial-port))))
+                      (apply
+                       (if defaulted-tty
+                           (make-forkexec-constructor
+                            (list #$(file-append util-linux "/sbin/agetty")
+                                  #$@extra-options
+                                  #$@(if eight-bits?
+                                         #~("--8bits")
+                                         #~())
+                                  #$@(if no-reset?
+                                         #~("--noreset")
+                                         #~())
+                                  #$@(if remote?
+                                         #~("--remote")
+                                         #~())
+                                  #$@(if flow-control?
+                                         #~("--flow-control")
+                                         #~())
+                                  #$@(if host
+                                         #~("--host" #$host)
+                                         #~())
+                                  #$@(if no-issue?
+                                         #~("--noissue")
+                                         #~())
+                                  #$@(if init-string
+                                         #~("--init-string" #$init-string)
+                                         #~())
+                                  #$@(if no-clear?
+                                         #~("--noclear")
+                                         #~())
 ;;; FIXME This doesn't work as expected. According to agetty(8), if this option
 ;;; is not passed, then the default is 'auto'. However, in my tests, when that
 ;;; option is selected, agetty never presents the login prompt, and the
 ;;; term-ttyS0 service respawns every few seconds.
-                               #$@(if local-line
-                                      #~(#$(match local-line
-                                             ('auto "--local-line=auto")
-                                             ('always "--local-line=always")
-                                             ('never "-local-line=never")))
-                                      #~())
-                               #$@(if tty
-                                      #~()
-                                      #~("--keep-baud"))
-                               #$@(if extract-baud?
-                                      #~("--extract-baud")
-                                      #~())
-                               #$@(if skip-login?
-                                      #~("--skip-login")
-                                      #~())
-                               #$@(if no-newline?
-                                      #~("--nonewline")
-                                      #~())
-                               #$@(if login-options
-                                      #~("--login-options" #$login-options)
-                                      #~())
-                               #$@(if chroot
-                                      #~("--chroot" #$chroot)
-                                      #~())
-                               #$@(if hangup?
-                                      #~("--hangup")
-                                      #~())
-                               #$@(if keep-baud?
-                                      #~("--keep-baud")
-                                      #~())
-                               #$@(if timeout
-                                      #~("--timeout" #$(number->string timeout))
-                                      #~())
-                               #$@(if detect-case?
-                                      #~("--detect-case")
-                                      #~())
-                               #$@(if wait-cr?
-                                      #~("--wait-cr")
-                                      #~())
-                               #$@(if no-hints?
-                                      #~("--nohints?")
-                                      #~())
-                               #$@(if no-hostname?
-                                      #~("--nohostname")
-                                      #~())
-                               #$@(if long-hostname?
-                                      #~("--long-hostname")
-                                      #~())
-                               #$@(if erase-characters
-                                      #~("--erase-chars" #$erase-characters)
-                                      #~())
-                               #$@(if kill-characters
-                                      #~("--kill-chars" #$kill-characters)
-                                      #~())
-                               #$@(if chdir
-                                      #~("--chdir" #$chdir)
-                                      #~())
-                               #$@(if delay
-                                      #~("--delay" #$(number->string delay))
-                                      #~())
-                               #$@(if nice
-                                      #~("--nice" #$(number->string nice))
-                                      #~())
-                               #$@(if auto-login
-                                      (list "--autologin" auto-login)
-                                      '())
-                               #$@(if login-program
-                                      #~("--login-program" #$login-program)
-                                      #~())
-                               #$@(if login-pause?
-                                      #~("--login-pause")
-                                      #~())
-                               #$(or tty (default-serial-port))
-                               #$@(if baud-rate
-                                      #~(#$baud-rate)
-                                      #~())
-                               #$@(if term
-                                      #~(#$term)
-                                      #~()))))
-                        (const #f))) ; never start.
+                                  #$@(if local-line
+                                         #~(#$(match local-line
+                                                     ('auto "--local-line=auto")
+                                                     ('always "--local-line=always")
+                                                     ('never "-local-line=never")))
+                                         #~())
+                                  #$@(if tty
+                                         #~()
+                                         #~("--keep-baud"))
+                                  #$@(if extract-baud?
+                                         #~("--extract-baud")
+                                         #~())
+                                  #$@(if skip-login?
+                                         #~("--skip-login")
+                                         #~())
+                                  #$@(if no-newline?
+                                         #~("--nonewline")
+                                         #~())
+                                  #$@(if login-options
+                                         #~("--login-options" #$login-options)
+                                         #~())
+                                  #$@(if chroot
+                                         #~("--chroot" #$chroot)
+                                         #~())
+                                  #$@(if hangup?
+                                         #~("--hangup")
+                                         #~())
+                                  #$@(if keep-baud?
+                                         #~("--keep-baud")
+                                         #~())
+                                  #$@(if timeout
+                                         #~("--timeout" #$(number->string timeout))
+                                         #~())
+                                  #$@(if detect-case?
+                                         #~("--detect-case")
+                                         #~())
+                                  #$@(if wait-cr?
+                                         #~("--wait-cr")
+                                         #~())
+                                  #$@(if no-hints?
+                                         #~("--nohints?")
+                                         #~())
+                                  #$@(if no-hostname?
+                                         #~("--nohostname")
+                                         #~())
+                                  #$@(if long-hostname?
+                                         #~("--long-hostname")
+                                         #~())
+                                  #$@(if erase-characters
+                                         #~("--erase-chars" #$erase-characters)
+                                         #~())
+                                  #$@(if kill-characters
+                                         #~("--kill-chars" #$kill-characters)
+                                         #~())
+                                  #$@(if chdir
+                                         #~("--chdir" #$chdir)
+                                         #~())
+                                  #$@(if delay
+                                         #~("--delay" #$(number->string delay))
+                                         #~())
+                                  #$@(if nice
+                                         #~("--nice" #$(number->string nice))
+                                         #~())
+                                  #$@(if auto-login
+                                         (list "--autologin" auto-login)
+                                         '())
+                                  #$@(if login-program
+                                         #~("--login-program" #$login-program)
+                                         #~())
+                                  #$@(if login-pause?
+                                         #~("--login-pause")
+                                         #~())
+                                  defaulted-tty
+                                  #$@(if baud-rate
+                                         #~(#$baud-rate)
+                                         #~())
+                                  #$@(if term
+                                         #~(#$term)
+                                         #~())))
+                           (const #f)) ; never start.
+                       args))))
          (stop #~(make-kill-destructor)))))))
 
 (define agetty-service-type
@@ -1090,7 +1110,7 @@ the tty to run, among other things."
        ;; Since the login prompt shows the host name, wait for the 'host-name'
        ;; service to be done.  Also wait for udev essentially so that the tty
        ;; text is not lost in the middle of kernel messages (XXX).
-       (requirement '(user-processes host-name udev))
+       (requirement '(user-processes host-name udev virtual-terminal))
 
        (start  #~(make-forkexec-constructor
                   (list #$(file-append mingetty "/sbin/mingetty")
@@ -1942,10 +1962,16 @@ extra rules from the packages listed in @var{rules}."
   "Return a service that uses @var{device} as a swap device."
   (service swap-service-type device))
 
+(define %default-gpm-options
+  ;; Default options for GPM.
+  '("-m" "/dev/input/mice" "-t" "ps2"))
+
 (define-record-type* <gpm-configuration>
   gpm-configuration make-gpm-configuration gpm-configuration?
-  (gpm      gpm-configuration-gpm)                ;package
-  (options  gpm-configuration-options))           ;list of strings
+  (gpm      gpm-configuration-gpm                 ;package
+            (default gpm))
+  (options  gpm-configuration-options             ;list of strings
+            (default %default-gpm-options)))
 
 (define gpm-shepherd-service
   (match-lambda
@@ -1980,14 +2006,15 @@ extra rules from the packages listed in @var{rules}."
                 (extensions
                  (list (service-extension shepherd-root-service-type
                                           gpm-shepherd-service)))
+                (default-value (gpm-configuration))
                 (description
                  "Run GPM, the general-purpose mouse daemon, with the given
 command-line options.  GPM allows users to use the mouse in the console,
 notably to select, copy, and paste text.  The default options use the
 @code{ps2} protocol, which works for both USB and PS/2 mice.")))
 
-(define* (gpm-service #:key (gpm gpm)
-                      (options '("-m" "/dev/input/mice" "-t" "ps2")))
+(define* (gpm-service #:key (gpm gpm)             ;deprecated
+                      (options %default-gpm-options))
   "Run @var{gpm}, the general-purpose mouse daemon, with the given
 command-line @var{options}.  GPM allows users to use the mouse in the console,
 notably to select, copy, and paste text.  The default value of @var{options}
@@ -2031,16 +2058,164 @@ This service is not part of @var{%base-services}."
 
        (shepherd-service
         (documentation "kmscon virtual terminal")
-        (requirement '(user-processes udev dbus-system))
+        (requirement '(user-processes udev dbus-system virtual-terminal))
         (provision (list (symbol-append 'term- (string->symbol virtual-terminal))))
         (start #~(make-forkexec-constructor #$kmscon-command))
         (stop #~(make-kill-destructor)))))))
 
+(define-record-type* <static-networking>
+  static-networking make-static-networking
+  static-networking?
+  (interface static-networking-interface)
+  (ip static-networking-ip)
+  (netmask static-networking-netmask
+           (default #f))
+  (gateway static-networking-gateway              ;FIXME: doesn't belong here
+           (default #f))
+  (provision static-networking-provision
+             (default #f))
+  (requirement static-networking-requirement
+               (default '()))
+  (name-servers static-networking-name-servers    ;FIXME: doesn't belong here
+                (default '())))
+
+(define static-networking-shepherd-service
+  (match-lambda
+    (($ <static-networking> interface ip netmask gateway provision
+                            requirement name-servers)
+     (let ((loopback? (and provision (memq 'loopback provision))))
+       (shepherd-service
+
+        (documentation
+         "Bring up the networking interface using a static IP address.")
+        (requirement requirement)
+        (provision (or provision
+                       (list (symbol-append 'networking-
+                                            (string->symbol interface)))))
+
+        (start #~(lambda _
+                   ;; Return #t if successfully started.
+                   (let* ((addr     (inet-pton AF_INET #$ip))
+                          (sockaddr (make-socket-address AF_INET addr 0))
+                          (mask     (and #$netmask
+                                         (inet-pton AF_INET #$netmask)))
+                          (maskaddr (and mask
+                                         (make-socket-address AF_INET
+                                                              mask 0)))
+                          (gateway  (and #$gateway
+                                         (inet-pton AF_INET #$gateway)))
+                          (gatewayaddr (and gateway
+                                            (make-socket-address AF_INET
+                                                                 gateway 0))))
+                     (configure-network-interface #$interface sockaddr
+                                                  (logior IFF_UP
+                                                          #$(if loopback?
+                                                                #~IFF_LOOPBACK
+                                                                0))
+                                                  #:netmask maskaddr)
+                     (when gateway
+                       (let ((sock (socket AF_INET SOCK_DGRAM 0)))
+                         (add-network-route/gateway sock gatewayaddr)
+                         (close-port sock))))))
+        (stop #~(lambda _
+                  ;; Return #f is successfully stopped.
+                  (let ((sock (socket AF_INET SOCK_STREAM 0)))
+                    (when #$gateway
+                      (delete-network-route sock
+                                            (make-socket-address
+                                             AF_INET INADDR_ANY 0)))
+                    (set-network-interface-flags sock #$interface 0)
+                    (close-port sock)
+:                    #f)))
+        (respawn? #f))))))
+
+(define (static-networking-etc-files interfaces)
+  "Return a /etc/resolv.conf entry for INTERFACES or the empty list."
+  (match (delete-duplicates
+          (append-map static-networking-name-servers
+                      interfaces))
+    (()
+     '())
+    ((name-servers ...)
+     (let ((content (string-join
+                     (map (cut string-append "nameserver " <>)
+                          name-servers)
+                     "\n" 'suffix)))
+       `(("resolv.conf"
+          ,(plain-file "resolv.conf"
+                       (string-append "\
+# Generated by 'static-networking-service'.\n"
+                                      content))))))))
+
+(define (static-networking-shepherd-services interfaces)
+  "Return the list of Shepherd services to bring up INTERFACES, a list of
+<static-networking> objects."
+  (define (loopback? service)
+    (memq 'loopback (shepherd-service-provision service)))
+
+  (let ((services (map static-networking-shepherd-service interfaces)))
+    (match (remove loopback? services)
+      (()
+       ;; There's no interface other than 'loopback', so we assume that the
+       ;; 'networking' service will be provided by dhclient or similar.
+       services)
+      ((non-loopback ...)
+       ;; Assume we're providing all the interfaces, and thus, provide a
+       ;; 'networking' service.
+       (cons (shepherd-service
+              (provision '(networking))
+              (requirement (append-map shepherd-service-provision
+                                       services))
+              (start #~(const #t))
+              (stop #~(const #f))
+              (documentation "Bring up all the networking interfaces."))
+             services)))))
+
+(define static-networking-service-type
+  ;; The service type for statically-defined network interfaces.
+  (service-type (name 'static-networking)
+                (extensions
+                 (list
+                  (service-extension shepherd-root-service-type
+                                     static-networking-shepherd-services)
+                  (service-extension etc-service-type
+                                     static-networking-etc-files)))
+                (compose concatenate)
+                (extend append)
+                (description
+                 "Turn up the specified network interfaces upon startup,
+with the given IP address, gateway, netmask, and so on.  The value for
+services of this type is a list of @code{static-networking} objects, one per
+network interface.")))
+
+(define* (static-networking-service interface ip
+                                    #:key
+                                    netmask gateway provision
+                                    ;; Most interfaces require udev to be usable.
+                                    (requirement '(udev))
+                                    (name-servers '()))
+  "Return a service that starts @var{interface} with address @var{ip}.  If
+@var{netmask} is true, use it as the network mask.  If @var{gateway} is true,
+it must be a string specifying the default network gateway.
+
+This procedure can be called several times, one for each network
+interface of interest.  Behind the scenes what it does is extend
+@code{static-networking-service-type} with additional network interfaces
+to handle."
+  (simple-service 'static-network-interface
+                  static-networking-service-type
+                  (list (static-networking (interface interface) (ip ip)
+                                           (netmask netmask) (gateway gateway)
+                                           (provision provision)
+                                           (requirement requirement)
+                                           (name-servers name-servers)))))
+
 \f
 (define %base-services
   ;; Convenience variable holding the basic services.
   (list (login-service)
 
+        (service virtual-terminal-service-type)
         (service console-font-service-type
                  (map (lambda (tty)
                         (cons tty %default-console-font))