Subject: [PATCH v3 1/3] services: Add hosts-service-type.

From: Bruno Victal <[email-masked]>
Date: Fri, 27 Jan 2023 21:06:11 +0000

* gnu/services/base.scm
(host, %host, host-address, host-canonical-name, host-aliases)
(hosts-service-type): New variable.
(host?): New predicate.
* doc/guix.texi: Document it.
---
 doc/guix.texi         | 75 +++++++++++++++++++++++++++++++++++++++++++
 gnu/services/base.scm | 70 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 145 insertions(+)

diff --git a/doc/guix.texi b/doc/guix.texi
index 2b1ad77ba5..e38c2c4b9b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -112,6 +112,7 @@
 Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@*
 Copyright @copyright{} 2023 Giacomo Leidi@*
 Copyright @copyright{} 2022 Antero Mejr@*
+Copyright @copyright{} 2023 Bruno Victal@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -40193,6 +40194,80 @@ Service Reference
 pointing to the given file.
 @end defvar
 
+@defvar hosts-service-type
+Type of the service that populates the entries for (@file{/etc/hosts}).
+This service type can be extended by passing it a list of
+@code{host} records.
+
+@c TRANSLATORS: The domain names below SHOULD NOT be translated.
+@c They're domains reserved for use in documentation. (RFC6761 Section 6.5)
+@c The addresses used are explained in RFC3849 and RFC5737.
+@lisp
+(simple-service 'add-extra-hosts
+                hosts-service-type
+                (list (host "192.0.2.1" "example.com"
+                            '("example.net" "example.org"))
+                      (host "2001:db8::1" "example.com"
+                            '("example.net" "example.org"))))
+@end lisp
+
+@quotation Note
+@cindex @file{/etc/host} default entries
+By default @file{/etc/host} comes with the following entries:
+@example
+127.0.0.1 localhost @var{host-name}
+::1       localhost @var{host-name}
+@end example
+
+For most setups this is what you want though if you find yourself in
+the situation where you want to change the default entries, you can
+do so in @code{operating-system}.@pxref{operating-system Reference,@code{essential-services}}
+
+The following example shows how one would unset @var{host-name}
+from being an alias of @code{localhost}.
+@lisp
+(operating-system
+  ;; @dots{}
+
+  (essential-services
+   (modify-services
+     (operation-system-default-essential-services this-operating-system)
+     (hosts-service-type config => (list
+                                     (host "127.0.0.1" "localhost")
+                                     (host "::1"       "localhost")))))
+
+   ;; @dots{}
+)
+@end lisp
+@end quotation
+
+@deftp {Data Type} host
+Available @code{host} fields are:
+
+@table @asis
+@item @code{address} (type: string)
+IP address.
+
+@item @code{canonical-name} (type: string)
+Hostname.
+
+@item @code{aliases} (default: @code{'()}) (type: list-of-string)
+Additional aliases that map to the same @code{canonical-name}.
+
+@end table
+@end deftp
+
+@defun host address canonical-name [aliases]
+Procedure for creating @code{host} records.
+@end defun
+
+@quotation Note
+The @code{host} data type constructor is @code{%host} though it is
+tiresome to create multiple records with it so in practice the procedure
+@code{host} (which wraps around @code{%host}) is used instead.
+@end quotation
+@end defvar
+
 @defvar setuid-program-service-type
 Type for the ``setuid-program service''.  This service collects lists of
 executable file names, passed as gexps, and adds them to the set of
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index 9e799445d2..53eda9ea1e 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -20,6 +20,7 @@
 ;;; Copyright © 2022 Guillaume Le Vaillant <[email-masked]>
 ;;; Copyright © 2022 Justin Veilleux <[email-masked]>
 ;;; Copyright © 2022 ( <[email-masked]>
+;;; Copyright © 2023 Bruno Victal <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -103,6 +104,14 @@ (define-module (gnu services base)
             console-font-service
             virtual-terminal-service-type
 
+            host
+            %host
+            host?
+            host-address
+            host-canonical-name
+            host-aliases
+            hosts-service-type
+
             static-networking
             static-networking?
             static-networking-addresses
@@ -685,6 +694,67 @@ (define* (rngd-service #:key
            (rngd-configuration
             (rng-tools rng-tools)
             (device device))))
+
+;;;
+;;; /etc/hosts
+;;;
+
+(define (valid-name? name)
+  "Return true if @var{name} is likely to be a valid hostname."
+  (false-if-exception (not (string-any char-set:whitespace name))))
+
+(define-compile-time-procedure (assert-valid-name (name valid-name?))
+  "Ensure @var{name} is likely to be a valid hostname."
+  ;; TODO: RFC compliant implementation.
+  (unless (valid-name? name)
+    (raise
+     (make-compound-condition
+      (formatted-message (G_ "hostname '~a' contains invalid characters")
+                         name)
+      (condition (&error-location
+                  (location
+                   (source-properties->location procedure-call-location)))))))
+  name)
+
+(define-record-type* <host> %host
+  ;; XXX: Using the record type constructor becomes tiresome when
+  ;; there's multiple records to make.
+  make-host host?
+  (address        host-address)
+  (canonical-name host-canonical-name
+                  (sanitize assert-valid-name))
+  (aliases        host-aliases
+                  (default '())
+                  (sanitize (cut map assert-valid-name <>))))
+
+(define* (host address canonical-name #:optional (aliases '()))
+  "Public constructor for <host> records."
+  (%host
+   (address address)
+   (canonical-name canonical-name)
+   (aliases aliases)))
+
+(define hosts-service-type
+  ;; Extend etc-service-type with a entry for @file{/etc/hosts}.
+  (let* ((serialize-host-record
+          (lambda (record)
+            (match-record record <host> (address canonical-name aliases)
+              (format #f "~a~/~a~{~^~/~a~}~%" address canonical-name aliases))))
+         (host-etc-service
+          (lambda (lst)
+            `(("hosts" ,(plain-file "hosts"
+                                    (format #f "~{~a~}"
+                                            (map serialize-host-record
+                                                 lst))))))))
+    (service-type
+     (name 'etc-hosts)
+     (extensions
+      (list
+       (service-extension etc-service-type
+                          host-etc-service)))
+     (compose concatenate)
+     (extend append)
+     (description "Populate the @file{/etc/hosts} file."))))
 
 
 ;;;

base-commit: 35e626f312aa5f8c9c4c3f06751db5e3394c66b6
-- 
2.38.1