From a4d33fef3111c2b842d201250eb72f9dcb543686 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 24 Oct 2021 17:11:52 +0200 Subject: doc: Add new "Networking Setup" node for the main setup options. This should make it easier to find how to get started setting up networking. * doc/guix.texi (Networking Setup): New section. (Networking Services): Remove 'static-networking-service', 'dhcp-client-service-type', 'network-manager-service-type', 'connman-service-type', 'wicd-service', 'modem-manager-service-type', 'usb-modeswitch-service-type', and 'wpa-supplicant-service-type'. --- doc/guix.texi | 289 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 160 insertions(+), 129 deletions(-) (limited to 'doc') diff --git a/doc/guix.texi b/doc/guix.texi index 9914829b2b..1535fb9f93 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -344,7 +344,8 @@ Services * Base Services:: Essential system services. * Scheduled Job Execution:: The mcron service. * Log Rotation:: The rottlog service. -* Networking Services:: Network setup, SSH daemon, etc. +* Networking Setup:: Setting up network interfaces. +* Networking Services:: Firewall, SSH daemon, etc. * Unattended Upgrades:: Automated system upgrades. * X Window:: Graphical display. * Printing Services:: Local and remote printer support. @@ -15606,7 +15607,8 @@ declaration. * Base Services:: Essential system services. * Scheduled Job Execution:: The mcron service. * Log Rotation:: The rottlog service. -* Networking Services:: Network setup, SSH daemon, etc. +* Networking Setup:: Setting up network interfaces. +* Networking Services:: Firewall, SSH daemon, etc. * Unattended Upgrades:: Automated system upgrades. * X Window:: Graphical display. * Printing Services:: Local and remote printer support. @@ -16828,63 +16830,26 @@ The list of syslog-controlled files to be rotated. By default it is: "/var/log/maillog")}. @end defvr -@node Networking Services -@subsection Networking Services - -The @code{(gnu services networking)} module provides services to configure -the network interface. - -@cindex DHCP, networking service -@defvr {Scheme Variable} dhcp-client-service-type -This is the type of services that run @var{dhcp}, a Dynamic Host Configuration -Protocol (DHCP) client, on all the non-loopback network interfaces. Its value -is the DHCP client package to use, @code{isc-dhcp} by default. -@end defvr +@node Networking Setup +@subsection Networking Setup -@deffn {Scheme Procedure} dhcpd-service-type -This type defines a service that runs a DHCP daemon. To create a -service of this type, you must supply a @code{}. -For example: +The @code{(gnu services networking)} module provides services to +configure network interfaces and set up networking on your machine. +Those services provide different ways for you to set up your machine: by +declaring a static network configuration, by running a Dynamic Host +Configuration Protocol (DHCP) client, or by running daemons such as +NetworkManager and Connman that automate the whole process, +automatically adapt to connectivity changes, and provide a high-level +user interface. -@lisp -(service dhcpd-service-type - (dhcpd-configuration - (config-file (local-file "my-dhcpd.conf")) - (interfaces '("enp0s25")))) -@end lisp -@end deffn +On a laptop, NetworkManager and Connman are by far the most convenient +options, which is why the default desktop services include +NetworkManager (@pxref{Desktop Services, @code{%desktop-services}}). +For a server, or for a virtual machine or a container, static network +configuration or a simple DHCP client are often more appropriate. -@deftp {Data Type} dhcpd-configuration -@table @asis -@item @code{package} (default: @code{isc-dhcp}) -The package that provides the DHCP daemon. This package is expected to -provide the daemon at @file{sbin/dhcpd} relative to its output -directory. The default package is the -@uref{https://www.isc.org/products/DHCP, ISC's DHCP server}. -@item @code{config-file} (default: @code{#f}) -The configuration file to use. This is required. It will be passed to -@code{dhcpd} via its @code{-cf} option. This may be any ``file-like'' -object (@pxref{G-Expressions, file-like objects}). See @code{man -dhcpd.conf} for details on the configuration file syntax. -@item @code{version} (default: @code{"4"}) -The DHCP version to use. The ISC DHCP server supports the values ``4'', -``6'', and ``4o6''. These correspond to the @code{dhcpd} program -options @code{-4}, @code{-6}, and @code{-4o6}. See @code{man dhcpd} for -details. -@item @code{run-directory} (default: @code{"/run/dhcpd"}) -The run directory to use. At service activation time, this directory -will be created if it does not exist. -@item @code{pid-file} (default: @code{"/run/dhcpd/dhcpd.pid"}) -The PID file to use. This corresponds to the @code{-pf} option of -@code{dhcpd}. See @code{man dhcpd} for details. -@item @code{interfaces} (default: @code{'()}) -The names of the network interfaces on which dhcpd should listen for -broadcasts. If this list is not empty, then its elements (which must be -strings) will be appended to the @code{dhcpd} invocation when starting -the daemon. It may not be necessary to explicitly specify any -interfaces here; see @code{man dhcpd} for details. -@end table -@end deftp +This section describes the various network setup services available, +starting with static network configuration. @defvr {Scheme Variable} static-networking-service-type This is the type for statically-configured network interfaces. @@ -16914,81 +16879,13 @@ For example: @end lisp @end deffn -@cindex wicd -@cindex wireless -@cindex WiFi -@cindex network management -@deffn {Scheme Procedure} wicd-service [#:wicd @var{wicd}] -Return a service that runs @url{https://launchpad.net/wicd,Wicd}, a network -management daemon that aims to simplify wired and wireless networking. - -This service adds the @var{wicd} package to the global profile, providing -several commands to interact with the daemon and configure networking: -@command{wicd-client}, a graphical user interface, and the @command{wicd-cli} -and @command{wicd-curses} user interfaces. -@end deffn - -@cindex ModemManager - -@defvr {Scheme Variable} modem-manager-service-type -This is the service type for the -@uref{https://wiki.gnome.org/Projects/ModemManager, ModemManager} -service. The value for this service type is a -@code{modem-manager-configuration} record. - -This service is part of @code{%desktop-services} (@pxref{Desktop -Services}). -@end defvr - -@deftp {Data Type} modem-manager-configuration -Data type representing the configuration of ModemManager. - -@table @asis -@item @code{modem-manager} (default: @code{modem-manager}) -The ModemManager package to use. - -@end table -@end deftp - -@cindex USB_ModeSwitch -@cindex Modeswitching - -@defvr {Scheme Variable} usb-modeswitch-service-type -This is the service type for the -@uref{https://www.draisberghof.de/usb_modeswitch/, USB_ModeSwitch} -service. The value for this service type is -a @code{usb-modeswitch-configuration} record. - -When plugged in, some USB modems (and other USB devices) initially present -themselves as a read-only storage medium and not as a modem. They need to be -@dfn{modeswitched} before they are usable. The USB_ModeSwitch service type -installs udev rules to automatically modeswitch these devices when they are -plugged in. - -This service is part of @code{%desktop-services} (@pxref{Desktop -Services}). +@cindex DHCP, networking service +@defvr {Scheme Variable} dhcp-client-service-type +This is the type of services that run @var{dhcp}, a Dynamic Host Configuration +Protocol (DHCP) client, on all the non-loopback network interfaces. Its value +is the DHCP client package to use, @code{isc-dhcp} by default. @end defvr -@deftp {Data Type} usb-modeswitch-configuration -Data type representing the configuration of USB_ModeSwitch. - -@table @asis -@item @code{usb-modeswitch} (default: @code{usb-modeswitch}) -The USB_ModeSwitch package providing the binaries for modeswitching. - -@item @code{usb-modeswitch-data} (default: @code{usb-modeswitch-data}) -The package providing the device data and udev rules file used by -USB_ModeSwitch. - -@item @code{config-file} (default: @code{#~(string-append #$usb-modeswitch:dispatcher "/etc/usb_modeswitch.conf")}) -Which config file to use for the USB_ModeSwitch dispatcher. By default the -config file shipped with USB_ModeSwitch is used which disables logging to -@file{/var/log} among other default settings. If set to @code{#f}, no config -file is used. - -@end table -@end deftp - @cindex NetworkManager @defvr {Scheme Variable} network-manager-service-type @@ -17125,6 +17022,139 @@ List of additional command-line arguments to pass to the daemon. @end table @end deftp +@cindex wicd +@cindex wireless +@cindex WiFi +@cindex network management +@deffn {Scheme Procedure} wicd-service [#:wicd @var{wicd}] +Return a service that runs @url{https://launchpad.net/wicd,Wicd}, a network +management daemon that aims to simplify wired and wireless networking. + +This service adds the @var{wicd} package to the global profile, providing +several commands to interact with the daemon and configure networking: +@command{wicd-client}, a graphical user interface, and the @command{wicd-cli} +and @command{wicd-curses} user interfaces. +@end deffn + +@cindex ModemManager +Some networking devices such as modems require special care, and this is +what the services below focus on. + +@defvr {Scheme Variable} modem-manager-service-type +This is the service type for the +@uref{https://wiki.gnome.org/Projects/ModemManager, ModemManager} +service. The value for this service type is a +@code{modem-manager-configuration} record. + +This service is part of @code{%desktop-services} (@pxref{Desktop +Services}). +@end defvr + +@deftp {Data Type} modem-manager-configuration +Data type representing the configuration of ModemManager. + +@table @asis +@item @code{modem-manager} (default: @code{modem-manager}) +The ModemManager package to use. + +@end table +@end deftp + +@cindex USB_ModeSwitch +@cindex Modeswitching + +@defvr {Scheme Variable} usb-modeswitch-service-type +This is the service type for the +@uref{https://www.draisberghof.de/usb_modeswitch/, USB_ModeSwitch} +service. The value for this service type is +a @code{usb-modeswitch-configuration} record. + +When plugged in, some USB modems (and other USB devices) initially present +themselves as a read-only storage medium and not as a modem. They need to be +@dfn{modeswitched} before they are usable. The USB_ModeSwitch service type +installs udev rules to automatically modeswitch these devices when they are +plugged in. + +This service is part of @code{%desktop-services} (@pxref{Desktop +Services}). +@end defvr + +@deftp {Data Type} usb-modeswitch-configuration +Data type representing the configuration of USB_ModeSwitch. + +@table @asis +@item @code{usb-modeswitch} (default: @code{usb-modeswitch}) +The USB_ModeSwitch package providing the binaries for modeswitching. + +@item @code{usb-modeswitch-data} (default: @code{usb-modeswitch-data}) +The package providing the device data and udev rules file used by +USB_ModeSwitch. + +@item @code{config-file} (default: @code{#~(string-append #$usb-modeswitch:dispatcher "/etc/usb_modeswitch.conf")}) +Which config file to use for the USB_ModeSwitch dispatcher. By default the +config file shipped with USB_ModeSwitch is used which disables logging to +@file{/var/log} among other default settings. If set to @code{#f}, no config +file is used. + +@end table +@end deftp + + +@node Networking Services +@subsection Networking Services + +The @code{(gnu services networking)} module discussed in the previous +section provides services for more advanced setups: providing a DHCP +service for others to use, filtering packets with iptables or nftables, +running a WiFi access point with @command{hostapd}, running the +@command{inetd} ``superdaemon'', and more. This section describes +those. + +@deffn {Scheme Procedure} dhcpd-service-type +This type defines a service that runs a DHCP daemon. To create a +service of this type, you must supply a @code{}. +For example: + +@lisp +(service dhcpd-service-type + (dhcpd-configuration + (config-file (local-file "my-dhcpd.conf")) + (interfaces '("enp0s25")))) +@end lisp +@end deffn + +@deftp {Data Type} dhcpd-configuration +@table @asis +@item @code{package} (default: @code{isc-dhcp}) +The package that provides the DHCP daemon. This package is expected to +provide the daemon at @file{sbin/dhcpd} relative to its output +directory. The default package is the +@uref{https://www.isc.org/products/DHCP, ISC's DHCP server}. +@item @code{config-file} (default: @code{#f}) +The configuration file to use. This is required. It will be passed to +@code{dhcpd} via its @code{-cf} option. This may be any ``file-like'' +object (@pxref{G-Expressions, file-like objects}). See @code{man +dhcpd.conf} for details on the configuration file syntax. +@item @code{version} (default: @code{"4"}) +The DHCP version to use. The ISC DHCP server supports the values ``4'', +``6'', and ``4o6''. These correspond to the @code{dhcpd} program +options @code{-4}, @code{-6}, and @code{-4o6}. See @code{man dhcpd} for +details. +@item @code{run-directory} (default: @code{"/run/dhcpd"}) +The run directory to use. At service activation time, this directory +will be created if it does not exist. +@item @code{pid-file} (default: @code{"/run/dhcpd/dhcpd.pid"}) +The PID file to use. This corresponds to the @code{-pf} option of +@code{dhcpd}. See @code{man dhcpd} for details. +@item @code{interfaces} (default: @code{'()}) +The names of the network interfaces on which dhcpd should listen for +broadcasts. If this list is not empty, then its elements (which must be +strings) will be appended to the @code{dhcpd} invocation when starting +the daemon. It may not be necessary to explicitly specify any +interfaces here; see @code{man dhcpd} for details. +@end table +@end deftp + @cindex hostapd service, for Wi-Fi access points @cindex Wi-Fi access points, hostapd service @defvr {Scheme Variable} hostapd-service-type @@ -17187,6 +17217,7 @@ network that can be seen on @code{wlan0}, by default. The service's value is a @code{hostapd-configuration} record. @end defvr + @cindex iptables @defvr {Scheme Variable} iptables-service-type This is the service type to set up an iptables configuration. iptables is a -- cgit v1.2.3 From 223f1b1eb3707f1d3ef91200dd616ee6c8b77db0 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sun, 17 Oct 2021 23:43:52 +0200 Subject: services: static-networking: Change interface to mimic netlink. * gnu/services/base.scm ()[interface, ip, netmask] [gateway]: Remove. [addresses, links, routes]: New fields. [requirement]: Default to '(udev). (, , ): New record types. (ensure-no-separate-netmask, %ensure-no-separate-netmask): Remove. (ipv6-address?, cidr->netmask, ip+netmask->cidr) (network-set-up/hurd, network-tear-down/hurd) (network-set-up/linux, network-tear-down/linux) (static-networking->hurd-pfinet-options): New procedures. (static-networking-shepherd-service): New procedure. (static-networking-shepherd-services): Rewrite in terms of the above. (static-networking-service): Deprecate. Adjust to new 'static-networking' API. (%base-services): Likewise. * gnu/system/install.scm (%installation-services): Likewise. * gnu/system/hurd.scm (%base-services/hurd): Likewise, and separate 'loopback' from 'networking'. * gnu/build/hurd-boot.scm (set-hurd-device-translators): Remove "servers/socket/2". * gnu/tests/networking.scm (run-openvswitch-test)["networking has started on ovs0"]: Check for 'networking instead of 'networking-ovs0, which is no longer provided. * doc/guix.texi (Networking Setup): Document the new interface. Remove documentation of 'static-networking-service'. (Virtualization Services): Change Ganeti example to use the new interface. --- doc/guix.texi | 190 ++++++++++++++++++--- gnu/build/hurd-boot.scm | 10 +- gnu/services/base.scm | 425 +++++++++++++++++++++++++++++++++-------------- gnu/system/hurd.scm | 27 ++- gnu/system/install.scm | 11 +- gnu/tests/networking.scm | 2 +- 6 files changed, 494 insertions(+), 171 deletions(-) (limited to 'doc') diff --git a/doc/guix.texi b/doc/guix.texi index 1535fb9f93..73ad818cb6 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -16852,32 +16852,165 @@ This section describes the various network setup services available, starting with static network configuration. @defvr {Scheme Variable} static-networking-service-type -This is the type for statically-configured network interfaces. -@c TODO Document data structures. +This is the type for statically-configured network interfaces. Its +value must be a list of @code{static-networking} records. Each of them +declares a set of @dfn{addresses}, @dfn{routes}, and @dfn{links}, as +show below. + +@cindex network interface controller (NIC) +@cindex NIC, networking interface controller +Here is the simplest configuration, with only one network interface +controller (NIC) and only IPv4 connectivity: + +@example +;; Static networking for one NIC, IPv4-only. +(service static-networking-service-type + (list (static-networking + (addresses + (list (network-address + (device "eno1") + (value "10.0.2.15/24")))) + (routes + (list (network-route + (destination "default") + (gateway "10.0.2.2")))) + (name-servers '("10.0.2.3"))))) +@end example + +The snippet above can be added to the @code{services} field of your +operating system configuration (@pxref{Using the Configuration System}). +It will configure your machine to have 10.0.2.15 as its IP address, with +a 24-bit netmask for the local network---meaning that any 10.0.2.@var{x} +address is on the local area network (LAN). Traffic to addresses +outside the local network is routed @i{via} 10.0.2.2. Host names are +resolved by sending domain name system (DNS) queries to 10.0.2.3. @end defvr -@deffn {Scheme Procedure} static-networking-service @var{interface} @var{ip} @ - [#:netmask #f] [#:gateway #f] [#:name-servers @code{'()}] @ - [#:requirement @code{'(udev)}] -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. @var{requirement} -can be used to declare a dependency on another service before configuring the -interface. - -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. +@deftp {Data Type} static-networking +This is the data type representing a static network configuration. -For example: +As an example, here is how you would declare the configuration of a +machine with a single network interface controller (NIC) available as +@code{eno1}, and with one IPv4 and one IPv6 address: @lisp -(static-networking-service "eno1" "192.168.1.82" - #:gateway "192.168.1.2" - #:name-servers '("192.168.1.2")) +;; Network configuration for one NIC, IPv4 + IPv6. +(static-networking + (addresses (list (network-address + (device "eno1") + (value "10.0.2.15/24")) + (network-address + (device "eno1") + (value "2001:123:4567:101::1/64")))) + (routes (list (network-route + (destination "default") + (gateway "10.0.2.2")) + (network-route + (destination "default") + (gateway "2020:321:4567:42::1")))) + (name-servers '("10.0.2.3"))) @end lisp -@end deffn + +If you are familiar with the @command{ip} command of the +@uref{https://wiki.linuxfoundation.org/networking/iproute2, +@code{iproute2} package} found on Linux-based systems, the declaration +above is equivalent to typing: + +@example +ip address add 10.0.2.15/24 dev eno1 +ip address add 2001:123:4567:101::1/64 dev eno1 +ip route add default via inet 10.0.2.2 +ip route add default via inet6 2020:321:4567:42::1 +@end example + +Run @command{man 8 ip} for more info. Venerable GNU/Linux users will +certainly know how to do it with @command{ifconfig} and @command{route}, +but we'll spare you that. + +The available fields of this data type are as follows: + +@table @asis +@item @code{addresses} +@itemx @code{links} (default: @code{'()}) +@itemx @code{routes} (default: @code{'()}) +The list of @code{network-address}, @code{network-link}, and +@code{network-route} records for this network (see below). + +@item @code{name-servers} (default: @code{'()}) +The list of IP addresses (strings) of domain name servers. These IP +addresses go to @file{/etc/resolv.conf}. + +@item @code{provision} (default: @code{'(networking)}) +If true, this should be a list of symbols for the Shepherd service +corresponding to this network configuration. + +@item @code{requirement} (default @code{'()}) +The list of Shepherd services depended on. +@end table +@end deftp + +@deftp {Data Type} network-address +This is the data type representing the IP address of a network +interface. + +@table @code +@item device +The name of the network interface for this address---e.g., +@code{"eno1"}. + +@item value +The actual IP address and network mask, in +@uref{https://en.wikipedia.org/wiki/CIDR#CIDR_notation, @acronym{CIDR, +Classless Inter-Domain Routing} notation}, as a string. + +For example, @code{"10.0.2.15/24"} denotes IPv4 address 10.0.2.15 on a +24-bit sub-network---all 10.0.2.@var{x} addresses are on the same local +network. + +@item ipv6? +Whether @code{value} denotes an IPv6 address. By default this is +automatically determined. +@end table +@end deftp + +@deftp {Data Type} network-route +This is the data type representing a network route. + +@table @asis +@item @code{destination} +The route destination (a string), either an IP address or +@code{"default"} to denote the default route. + +@item @code{source} (default: @code{#f}) +The route source. + +@item @code{device} (default: @code{#f}) +The device used for this route---e.g., @code{"eno2"}. + +@item @code{ipv6?} (default: auto) +Whether this is an IPv6 route. By default this is automatically +determined based on @code{destination} or @code{gateway}. + +@item @code{gateway} (default: @code{#f}) +IP address (a string) through which traffic is routed. +@end table +@end deftp + +@deftp {Data Type} network-link +Data type for a network link (@pxref{Link,,, guile-netlink, +Guile-Netlink Manual}). + +@table @code +@item name +The name of the link---e.g., @code{"v0p0"}. + +@item type +A symbol denoting the type of the link---e.g., @code{'veth}. + +@item arguments +List of arguments for this type of link. +@end table +@end deftp @cindex DHCP, networking service @defvr {Scheme Variable} dhcp-client-service-type @@ -30442,11 +30575,18 @@ cluster node that supports multiple storage backends, and installs the "ganeti-instance-guix" "ganeti-instance-debootstrap")) %base-packages)) (services - (append (list (static-networking-service "eth0" "192.168.1.201" - #:netmask "255.255.255.0" - #:gateway "192.168.1.254" - #:name-servers '("192.168.1.252" - "192.168.1.253")) + (append (list (service static-networking-service-type + (list (static-networking + (addresses + (list (network-address + (device "eth0") + (value "192.168.1.201/24")))) + (routes + (list (network-route + (destination "default") + (gateway "192.168.1.254")))) + (name-servers '("192.168.1.252" + "192.168.1.253"))))) ;; Ganeti uses SSH to communicate between nodes. (service openssh-service-type diff --git a/gnu/build/hurd-boot.scm b/gnu/build/hurd-boot.scm index 8b27995438..ac36bd17d4 100644 --- a/gnu/build/hurd-boot.scm +++ b/gnu/build/hurd-boot.scm @@ -185,13 +185,9 @@ set." ("servers/crash-suspend" ("/hurd/crash" "--suspend")) ("servers/password" ("/hurd/password")) ("servers/socket/1" ("/hurd/pflocal")) - ("servers/socket/2" ("/hurd/pfinet" - "--interface" "eth0" - "--address" - "10.0.2.15" ;the default QEMU guest IP - "--netmask" "255.255.255.0" - "--gateway" "10.0.2.2" - "--ipv6" "/servers/socket/26")) + ;; /servers/socket/2 and /26 are created by 'static-networking-service'. + ;; XXX: Spawn pfinet without arguments on these nodes so that a DHCP + ;; client has someone to talk to? ("proc" ("/hurd/procfs" "--stat-mode=444")))) (define devices diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 7008ab137c..d93ea6f063 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -35,6 +35,8 @@ (define-module (gnu services base) #:use-module (guix store) #:use-module (guix deprecation) + #:autoload (guix diagnostics) (warning) + #:autoload (guix i18n) (G_) #:use-module (gnu services) #:use-module (gnu services admin) #:use-module (gnu services shepherd) @@ -54,6 +56,7 @@ #:use-module ((gnu packages base) #:select (coreutils glibc glibc-utf8-locales)) #:autoload (gnu packages guile-xyz) (guile-netlink) + #:autoload (gnu packages hurd) (hurd) #:use-module (gnu packages package-management) #:use-module ((gnu packages gnupg) #:select (guile-gcrypt)) #:use-module (gnu packages linux) @@ -84,14 +87,32 @@ virtual-terminal-service-type static-networking - static-networking? - static-networking-interface - static-networking-ip - static-networking-netmask - static-networking-gateway + static-networking-addresses + static-networking-links + static-networking-routes static-networking-requirement + network-address + network-address? + network-address-device + network-address-value + network-address-ipv6? + + network-link + network-link? + network-link-name + network-link-type + network-link-arguments + + network-route + network-route? + network-route-destination + network-route-source + network-route-device + network-route-ipv6? + network-route-gateway + static-networking-service static-networking-service-type @@ -2355,113 +2376,267 @@ notably to select, copy, and paste text. The default options use the (description "Start the @command{kmscon} virtual terminal emulator for the Linux @dfn{kernel mode setting} (KMS)."))) + +;;; +;;; Static networking. +;;; + +(define (ipv6-address? str) + "Return true if STR denotes an IPv6 address." + (false-if-exception (->bool (inet-pton AF_INET6 str)))) + (define-record-type* 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)) + (addresses static-networking-addresses) ;list of + (links static-networking-links (default '())) ;list of + (routes static-networking-routes (default '())) ;list of (provision static-networking-provision - (default #f)) + (default '(networking))) (requirement static-networking-requirement - (default '())) + (default '(udev))) (name-servers static-networking-name-servers ;FIXME: doesn't belong here (default '()))) -(define static-networking-shepherd-service +(define-record-type* + network-address make-network-address + network-address? + (device network-address-device) ;string--e.g., "en01" + (value network-address-value) ;string--CIDR notation + (ipv6? network-address-ipv6? ;Boolean + (thunked) + (default + (ipv6-address? (cidr->ip (network-address-value this-record)))))) + +(define-record-type* + network-link make-network-link + network-link? + (name network-link-name) ;string--e.g, "v0p0" + (type network-link-type) ;symbol--e.g.,'veth + (arguments network-link-arguments)) ;list + +(define-record-type* + network-route make-network-route + network-route? + (destination network-route-destination) + (source network-route-source (default #f)) + (device network-route-device (default #f)) + (ipv6? network-route-ipv6? (thunked) + (default + (or (ipv6-address? (network-route-destination this-record)) + (and=> (network-route-gateway this-record) + ipv6-address?)))) + (gateway network-route-gateway (default #f))) + +(define* (cidr->netmask str #:optional (family AF_INET)) + "Given @var{str}, a string in CIDR notation (e.g., \"1.2.3.4/24\"), return +the netmask as a string like \"255.255.255.0\"." + (match (string-split str #\/) + ((ip (= string->number bits)) + (let ((mask (ash (- (expt 2 bits) 1) + (- (if (= family AF_INET6) 128 32) + bits)))) + (inet-ntop family mask))) + (_ #f))) + +(define (cidr->ip str) + "Strip the netmask bit of @var{str}, a CIDR-notation IP/netmask address." + (match (string-split str #\/) + ((or (ip _) (ip)) + ip))) + +(define* (ip+netmask->cidr ip netmask #:optional (family AF_INET)) + "Return the CIDR notation (a string) for @var{ip} and @var{netmask}, two +@var{family} address strings, where @var{family} is @code{AF_INET} or +@code{AF_INET6}." + (let* ((netmask (inet-pton family netmask)) + (bits (logcount netmask))) + (string-append ip "/" (number->string bits)))) + +(define (static-networking->hurd-pfinet-options config) + "Return command-line options for the Hurd's pfinet translator corresponding +to CONFIG." + (unless (null? (static-networking-links config)) + ;; XXX: Presumably this is not supported, or perhaps could be approximated + ;; by running separate pfinet instances in some cases? + (warning (G_ "network links are currently ignored on GNU/Hurd~%"))) + + (match (static-networking-addresses config) + ((and addresses (first _ ...)) + `("--ipv6" "/servers/socket/26" + "--interface" ,(network-address-device first) + ,@(append-map (lambda (address) + `(,(if (network-address-ipv6? address) + "--address6" + "--address") + ,(cidr->ip (network-address-value address)) + ,@(match (cidr->netmask (network-address-value address) + (if (network-address-ipv6? address) + AF_INET6 + AF_INET)) + (#f '()) + (mask (list "--netmask" mask))))) + addresses) + ,@(append-map (lambda (route) + (match route + (($ "default" #f device _ gateway) + (if (network-route-ipv6? route) + `("--gateway6" ,gateway) + `("--gateway" ,gateway))) + (($ destination) + (warning (G_ "ignoring network route for '~a'~%") + destination) + '()))) + (static-networking-routes config)))))) + +(define (network-set-up/hurd config) + "Set up networking for the Hurd." + ;; The Hurd implements SIOCGIFADDR and other old-style ioctls, but the only + ;; way to set up IPv6 is by starting pfinet with the right options. + (if (equal? (static-networking-provision config) '(loopback)) + (scheme-file "set-up-pflocal" #~(begin 'nothing-to-do! #t)) + (scheme-file "set-up-pfinet" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils) + (ice-9 format)) + + ;; TODO: Do that without forking. + (let ((options '#$(static-networking->hurd-pfinet-options + config))) + (format #t "starting '~a~{ ~s~}'~%" + #$(file-append hurd "/hurd/pfinet") + options) + (apply invoke #$(file-append hurd "/bin/settrans") "-fac" + "/servers/socket/2" + #$(file-append hurd "/hurd/pfinet") + options))))))) + +(define (network-tear-down/hurd config) + (scheme-file "tear-down-pfinet" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + + ;; Forcefully terminate pfinet. XXX: In theory this + ;; should just undo the addresses and routes of CONFIG; + ;; this could be done using ioctls like SIOCDELRT, but + ;; these are IPv4-only; another option would be to use + ;; fsysopts but that seems to crash pfinet. + (invoke #$(file-append hurd "/bin/settrans") "-fg" + "/servers/socket/2") + #f)))) + +(define network-set-up/linux (match-lambda - (($ interface ip netmask gateway provision - requirement name-servers) + (($ addresses links routes) + (scheme-file "set-up-network" + (with-extensions (list guile-netlink) + #~(begin + (use-modules (ip addr) (ip link) (ip route)) + + #$@(map (lambda (address) + #~(begin + (addr-add #$(network-address-device address) + #$(network-address-value address) + #:ipv6? + #$(network-address-ipv6? address)) + ;; FIXME: loopback? + (link-set #$(network-address-device address) + #:up #t))) + addresses) + #$@(map (match-lambda + (($ name type arguments) + #~(link-add #$name #$type + #:type-args '#$arguments))) + links) + #$@(map (lambda (route) + #~(route-add #$(network-route-destination route) + #:device + #$(network-route-device route) + #:ipv6? + #$(network-route-ipv6? route) + #:via + #$(network-route-gateway route) + #:src + #$(network-route-source route))) + routes) + #t)))))) + +(define network-tear-down/linux + (match-lambda + (($ addresses links routes) + (scheme-file "tear-down-network" + (with-extensions (list guile-netlink) + #~(begin + (use-modules (ip addr) (ip link) (ip route) + (netlink error) + (srfi srfi-34)) + + (define-syntax-rule (false-if-netlink-error exp) + (guard (c ((netlink-error? c) #f)) + exp)) + + ;; Wrap calls in 'false-if-netlink-error' so this + ;; script goes as far as possible undoing the effects + ;; of "set-up-network". + + #$@(map (lambda (route) + #~(false-if-netlink-error + (route-del #$(network-route-destination route) + #:device + #$(network-route-device route) + #:ipv6? + #$(network-route-ipv6? route) + #:via + #$(network-route-gateway route) + #:src + #$(network-route-source route)))) + routes) + #$@(map (match-lambda + (($ name type arguments) + #~(false-if-netlink-error + (link-del #$name)))) + links) + #$@(map (lambda (address) + #~(false-if-netlink-error + (addr-del #$(network-address-device + address) + #$(network-address-value address) + #:ipv6? + #$(network-address-ipv6? address)))) + addresses) + #f)))))) + +(define (static-networking-shepherd-service config) + (match config + (($ addresses links routes + provision requirement name-servers) (let ((loopback? (and provision (memq 'loopback provision)))) - (define set-up-via-ioctl - #~(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))))) - - (define tear-down-via-ioctl - #~(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)) - - (define set-up-via-netlink - (with-extensions (list guile-netlink) - #~(let ((ip #$(if netmask - #~(ip+netmask->cidr #$ip #$netmask) - ip))) - (addr-add #$interface ip) - (when #$gateway - (route-add "default" #:device #$interface - #:via #$gateway)) - (link-set #$interface #:up #t)))) - - (define tear-down-via-netlink - (with-extensions (list guile-netlink) - #~(begin - (link-set #$interface #:down #t) - (when #$gateway - (route-del "default" #:device #$interface)) - (addr-del #$interface #$ip) - #f))) - - (define helpers - #~(define (ip+netmask->cidr ip netmask) - ;; Return the CIDR notation (a string) for IP and NETMASK, two - ;; IPv4 address strings. - (let* ((netmask (inet-pton AF_INET netmask)) - (bits (logcount netmask))) - (string-append ip "/" (number->string bits))))) - (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))))) + (provision provision) (start #~(lambda _ ;; Return #t if successfully started. - #$helpers - (if (string-contains %host-type "-linux") - #$set-up-via-netlink - #$set-up-via-ioctl))) + (load #$(let-system (system target) + (if (string-contains (or target system) "-linux") + (network-set-up/linux config) + (network-set-up/hurd config)))))) (stop #~(lambda _ ;; Return #f is successfully stopped. - (if (string-contains %host-type "-linux") - #$tear-down-via-netlink - #$tear-down-via-ioctl))) - (modules `((ip addr) - (ip link) - (ip route) - ,@%default-modules)) + (load #$(let-system (system target) + (if (string-contains (or target system) "-linux") + (network-tear-down/linux config) + (network-tear-down/hurd config)))))) (respawn? #f)))))) +(define (static-networking-shepherd-services networks) + (map static-networking-shepherd-service networks)) + (define (static-networking-etc-files interfaces) "Return a /etc/resolv.conf entry for INTERFACES or the empty list." (match (delete-duplicates @@ -2480,30 +2655,6 @@ Linux @dfn{kernel mode setting} (KMS)."))) # 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 - 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) @@ -2521,12 +2672,13 @@ 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 '())) +(define-deprecated (static-networking-service interface ip + #:key + netmask gateway provision + ;; Most interfaces require udev to be usable. + (requirement '(udev)) + (name-servers '())) + static-networking-service-type "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. @@ -2537,11 +2689,24 @@ interface of interest. Behind the scenes what it does is extend 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))))) + (list (static-networking + (addresses + (list (network-address + (device interface) + (value (if netmask + (ip+netmask->cidr ip netmask) + ip)) + (ipv6? #f)))) + (routes + (if gateway + (list (network-route + (destination "default") + (gateway gateway) + (ipv6? #f))) + '())) + (requirement requirement) + (provision (or provision '(networking))) + (name-servers name-servers))))) (define %base-services @@ -2573,10 +2738,12 @@ to handle." (tty "tty6"))) (service static-networking-service-type - (list (static-networking (interface "lo") - (ip "127.0.0.1") - (requirement '()) - (provision '(loopback))))) + (list (static-networking + (addresses (list (network-address + (device "lo") + (value "127.0.0.1")))) + (requirement '()) + (provision '(loopback))))) (syslog-service) (service urandom-seed-service-type) (service guix-service-type) diff --git a/gnu/system/hurd.scm b/gnu/system/hurd.scm index 0794671ce4..0e73ca0d99 100644 --- a/gnu/system/hurd.scm +++ b/gnu/system/hurd.scm @@ -79,11 +79,28 @@ (service hurd-getty-service-type (hurd-getty-configuration (tty "tty2"))) (service static-networking-service-type - (list (static-networking (interface "lo") - (ip "127.0.0.1") - (requirement '()) - (provision '(loopback networking)) - (name-servers '("10.0.2.3"))))) + (list (static-networking + (addresses + (list (network-address + (device "lo") + (value "127.0.0.1")))) + (requirement '()) + (provision '(loopback))) + (static-networking + (addresses + ;; The default QEMU guest address. To get "eth0", + ;; you need QEMU to emulate a device for which Mach + ;; has an in-kernel driver, for instance with: + ;; --device rtl8139,netdev=net0 --netdev user,id=net0 + (list (network-address + (device "eth0") + (value "10.0.2.15/24")))) + (routes + (list (network-route + (destination "default") + (gateway "10.0.2.2")))) + (provision '(networking)) + (name-servers '("10.0.2.3"))))) (syslog-service) (service guix-service-type (guix-configuration diff --git a/gnu/system/install.scm b/gnu/system/install.scm index 7b394184ad..bdfe580145 100644 --- a/gnu/system/install.scm +++ b/gnu/system/install.scm @@ -408,10 +408,13 @@ Access documentation at any time by pressing Alt-F2.\x1b[0m ;; Loopback device, needed by OpenSSH notably. (service static-networking-service-type - (list (static-networking (interface "lo") - (ip "127.0.0.1") - (requirement '()) - (provision '(loopback))))) + (list (static-networking + (addresses + (list (network-address + (device "lo") + (value "127.0.0.1")))) + (requirement '()) + (provision '(loopback))))) (service wpa-supplicant-service-type) (dbus-service) diff --git a/gnu/tests/networking.scm b/gnu/tests/networking.scm index 131428c128..c66af279f2 100644 --- a/gnu/tests/networking.scm +++ b/gnu/tests/networking.scm @@ -337,7 +337,7 @@ port 7, and a dict service on port 2628." (srfi srfi-1)) (live-service-running (find (lambda (live) - (memq 'networking-ovs0 + (memq 'networking (live-service-provision live))) (current-services)))) marionette)) -- cgit v1.2.3 From 1644f4f1f8957f3f3a88037aed80d501fdceaae7 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 27 Oct 2021 12:20:49 +0200 Subject: services: Define '%qemu-static-networking'. * gnu/services/base.scm (%qemu-static-networking): New variable. * gnu/system/hurd.scm (%base-services/hurd): Use it. * doc/guix.texi (Networking Setup): Document it. --- doc/guix.texi | 8 ++++++++ gnu/services/base.scm | 16 ++++++++++++++++ gnu/system/hurd.scm | 21 ++++++--------------- 3 files changed, 30 insertions(+), 15 deletions(-) (limited to 'doc') diff --git a/doc/guix.texi b/doc/guix.texi index 73ad818cb6..22f710d0c1 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -17012,6 +17012,14 @@ List of arguments for this type of link. @end table @end deftp +@cindex networking, with QEMU +@cindex QEMU, networking +@defvr {Scheme Variable} %qemu-static-networking +This is the @code{static-networking} record representing network setup +when using QEMU's user-mode network stack on @code{eth0} (@pxref{Using +the user mode network stack,,, QEMU, QEMU Documentation}). +@end defvr + @cindex DHCP, networking service @defvr {Scheme Variable} dhcp-client-service-type This is the type of services that run @var{dhcp}, a Dynamic Host Configuration diff --git a/gnu/services/base.scm b/gnu/services/base.scm index d93ea6f063..31b418dabf 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -116,6 +116,8 @@ static-networking-service static-networking-service-type + %qemu-static-networking + udev-configuration udev-configuration? udev-configuration-rules @@ -2708,6 +2710,20 @@ to handle." (provision (or provision '(networking))) (name-servers name-servers))))) +(define %qemu-static-networking + ;; Networking configuration for QEMU's user-mode network stack (info "(QEMU) + ;; Using the user mode network stack"). + (static-networking + (addresses (list (network-address + (device "eth0") + (value "10.0.2.15/24")))) + (routes (list (network-route + (destination "default") + (gateway "10.0.2.2")))) + (requirement '()) + (provision '(networking)) + (name-servers '("10.0.2.3")))) + (define %base-services ;; Convenience variable holding the basic services. diff --git a/gnu/system/hurd.scm b/gnu/system/hurd.scm index 0e73ca0d99..ec8484d746 100644 --- a/gnu/system/hurd.scm +++ b/gnu/system/hurd.scm @@ -86,21 +86,12 @@ (value "127.0.0.1")))) (requirement '()) (provision '(loopback))) - (static-networking - (addresses - ;; The default QEMU guest address. To get "eth0", - ;; you need QEMU to emulate a device for which Mach - ;; has an in-kernel driver, for instance with: - ;; --device rtl8139,netdev=net0 --netdev user,id=net0 - (list (network-address - (device "eth0") - (value "10.0.2.15/24")))) - (routes - (list (network-route - (destination "default") - (gateway "10.0.2.2")))) - (provision '(networking)) - (name-servers '("10.0.2.3"))))) + + ;; QEMU user-mode networking. To get "eth0", you need + ;; QEMU to emulate a device for which Mach has an + ;; in-kernel driver, for instance with: + ;; --device rtl8139,netdev=net0 --netdev user,id=net0 + %qemu-static-networking)) (syslog-service) (service guix-service-type (guix-configuration -- cgit v1.2.3 From 5967aee398c26a4814da1d8dba3f2dba84b06a4b Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 27 Oct 2021 12:26:31 +0200 Subject: services: Define '%loopback-static-networking'. * gnu/services/base.scm (%loopback-static-networking): New variable. (%base-services): Use it. * gnu/system/hurd.scm (%base-services/hurd): Use it. * gnu/system/install.scm (%installation-services): Use it. * doc/guix.texi (Networking Setup): Document it. --- doc/guix.texi | 7 +++++++ gnu/services/base.scm | 17 +++++++++++------ gnu/system/hurd.scm | 8 +------- gnu/system/install.scm | 8 +------- 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'doc') diff --git a/doc/guix.texi b/doc/guix.texi index 22f710d0c1..c00ec81e44 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -17012,6 +17012,13 @@ List of arguments for this type of link. @end table @end deftp +@cindex loopback device +@defvr {Scheme Variable} %loopback-static-networking +This is the @code{static-networking} record representing the ``loopback +device'', @code{lo}, for IP addresses 127.0.0.1 and ::1, and providing +the @code{loopback} Shepherd service. +@end defvr + @cindex networking, with QEMU @cindex QEMU, networking @defvr {Scheme Variable} %qemu-static-networking diff --git a/gnu/services/base.scm b/gnu/services/base.scm index 31b418dabf..5f93483dda 100644 --- a/gnu/services/base.scm +++ b/gnu/services/base.scm @@ -116,6 +116,7 @@ static-networking-service static-networking-service-type + %loopback-static-networking %qemu-static-networking udev-configuration @@ -2710,6 +2711,15 @@ to handle." (provision (or provision '(networking))) (name-servers name-servers))))) +(define %loopback-static-networking + ;; The loopback device. + (static-networking + (addresses (list (network-address + (device "lo") + (value "127.0.0.1")))) + (requirement '()) + (provision '(loopback)))) + (define %qemu-static-networking ;; Networking configuration for QEMU's user-mode network stack (info "(QEMU) ;; Using the user mode network stack"). @@ -2754,12 +2764,7 @@ to handle." (tty "tty6"))) (service static-networking-service-type - (list (static-networking - (addresses (list (network-address - (device "lo") - (value "127.0.0.1")))) - (requirement '()) - (provision '(loopback))))) + (list %loopback-static-networking)) (syslog-service) (service urandom-seed-service-type) (service guix-service-type) diff --git a/gnu/system/hurd.scm b/gnu/system/hurd.scm index ec8484d746..2acc7b7e11 100644 --- a/gnu/system/hurd.scm +++ b/gnu/system/hurd.scm @@ -79,13 +79,7 @@ (service hurd-getty-service-type (hurd-getty-configuration (tty "tty2"))) (service static-networking-service-type - (list (static-networking - (addresses - (list (network-address - (device "lo") - (value "127.0.0.1")))) - (requirement '()) - (provision '(loopback))) + (list %loopback-static-networking ;; QEMU user-mode networking. To get "eth0", you need ;; QEMU to emulate a device for which Mach has an diff --git a/gnu/system/install.scm b/gnu/system/install.scm index bdfe580145..073d7df1db 100644 --- a/gnu/system/install.scm +++ b/gnu/system/install.scm @@ -408,13 +408,7 @@ Access documentation at any time by pressing Alt-F2.\x1b[0m ;; Loopback device, needed by OpenSSH notably. (service static-networking-service-type - (list (static-networking - (addresses - (list (network-address - (device "lo") - (value "127.0.0.1")))) - (requirement '()) - (provision '(loopback))))) + (list %loopback-static-networking)) (service wpa-supplicant-service-type) (dbus-service) -- cgit v1.2.3 From c946e1464d0850190f899d592bb765fa52b804c2 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 13 Dec 2021 11:28:31 +0100 Subject: doc: Handle ' ' when syntax-highlighting HTML. * doc/build.scm (syntax-highlighted-html)[build](entity->string): Handle " ". --- doc/build.scm | 1 + 1 file changed, 1 insertion(+) (limited to 'doc') diff --git a/doc/build.scm b/doc/build.scm index a9e279159b..e7b42be17c 100644 --- a/doc/build.scm +++ b/doc/build.scm @@ -449,6 +449,7 @@ its
 blocks (as produced by 'makeinfo --html')."
                 ("rarr"   "→")
                 ("hellip" "…")
                 ("rsquo"  "’")
+                ("nbsp"   " ")
                 (e (pk 'unknown-entity e) (primitive-exit 2))))
 
             (define (concatenate-snippets pieces)
-- 
cgit v1.2.3


From 788f56b4dc0729e07ad546c5bc9694759c271f09 Mon Sep 17 00:00:00 2001
From: Ludovic Courtès 
Date: Mon, 13 Dec 2021 11:30:25 +0100
Subject: doc: Add missing closing paren.

* doc/guix.texi (Desktop Services): Add missing closing paren in example.
---
 doc/guix.texi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'doc')

diff --git a/doc/guix.texi b/doc/guix.texi
index c00ec81e44..757e44398c 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -20478,7 +20478,7 @@ a system which relies on @code{%desktop-services}, you may use
 
 (operating-system
   @dots{}
-  (services %my-desktop-services)
+  (services %my-desktop-services))
 @end lisp
 @end defvr
 
-- 
cgit v1.2.3