From debbugs-submit-bounces@debbugs.gnu.org Thu Dec 23 08:22:42 2021 Received: (at submit) by debbugs.gnu.org; 23 Dec 2021 13:22:42 +0000 Received: from localhost ([127.0.0.1]:60636 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1n0O3N-0006yu-AK for submit@debbugs.gnu.org; Thu, 23 Dec 2021 08:22:42 -0500 Received: from lists.gnu.org ([209.51.188.17]:33510) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1n0O3K-0006ym-Dd for submit@debbugs.gnu.org; Thu, 23 Dec 2021 08:22:39 -0500 Received: from eggs.gnu.org ([209.51.188.92]:33706) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n0O3J-0005Kl-JB for guix-patches@gnu.org; Thu, 23 Dec 2021 08:22:38 -0500 Received: from [2a00:1450:4864:20::134] (port=33679 helo=mail-lf1-x134.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n0O3F-0007QW-JF for guix-patches@gnu.org; Thu, 23 Dec 2021 08:22:36 -0500 Received: by mail-lf1-x134.google.com with SMTP id k21so12450163lfu.0 for ; Thu, 23 Dec 2021 05:22:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=trop-in.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:in-reply-to:date:message-id:mime-version; bh=IzcJBaaTNMY0NS1nIJkKq6/J+O/SnAsYvjGnOHy+20g=; b=NsvYm++PpKOLaO0XZIuQEkJn+URlM/782QZCcv36OzXuALtp7MlB6E06jNqolUGaF+ G+OXqGMUPwEyMB4A7HYCWNmcXWGouTrENyUgd02ownu+4YTS/Rmcmg2XG0x3T1z7tJ20 q85/To7sXC3J0Op4G99zKba/3ri7H0rKJ4boy7PULpxBNbKm3pYdTeysf5VoRQB7w8E4 V6IhowT/BqkP64AabtPjhGWCMZZyD+qDHqFbsz+kWevIuffJ+3LNUoHfc+Qwomtdcjo0 wcFRtP48caM9EhJ93cSwES+aUu8VfHr3zMGkpHgazftjMYV427lDGxu0ckKjjGpU766x D7jg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:in-reply-to:date:message-id :mime-version; bh=IzcJBaaTNMY0NS1nIJkKq6/J+O/SnAsYvjGnOHy+20g=; b=SBNFXBdoufs73uy0g1/1rNCn09qJ4qaUkPs528Jnwd2vWqBvlEAddfxV3h+sFwAo+A SFMxtm8v+HsSudL4jLYCicrAubpdjNOdyQYxIqRH+YnlPGJaZeOpDatR+4CN4lFtZYNB JYY/B03iW0wLpVl2nn5j/WLyH0EzrkTyAOXzK9R3Ozwke3YMV3q12SW2FqSgoKuf8/I8 oRQ1xgYJKpEKlYn0tW1HHMp7aHMiyizdhptB0HvUUXj0oNoVW1vE8chH+829BXvXWQIo 9fMONLNjq4ZhhjNmx56gjR9rFHTDDOhGnmakqqKP2KulZTChSJR3NxhOGslbzvPdBU2d 5G2Q== X-Gm-Message-State: AOAM533sgqtoaa1CqzZg53v1Hz1/EAyhciQ4VBHYwSJVEL2+hovtQEOS iRQtFJwBG4QKZCf+9zVeqtnXWQ== X-Google-Smtp-Source: ABdhPJwzi7HMJtmeFn67azg1yvco5LgQ1BnQsIpJ8IJ5xT1mpOosVRpB4EqupaGOZXPtQpt3m4F9ow== X-Received: by 2002:ac2:4bc1:: with SMTP id o1mr1813194lfq.666.1640265749131; Thu, 23 Dec 2021 05:22:29 -0800 (PST) Received: from localhost (109-252-167-227.dynamic.spd-mgts.ru. [109.252.167.227]) by smtp.gmail.com with ESMTPSA id 6sm505052ljs.39.2021.12.23.05.22.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Dec 2021 05:22:28 -0800 (PST) From: Andrew Tropin To: guix-devel@gnu.org, guix-patches@gnu.org, Ludovic =?utf-8?Q?Court?= =?utf-8?Q?=C3=A8s?= Subject: [PATCH v2] doc: Add Writing Service Configuration section. In-Reply-To: <87h7b2b6n3.fsf@trop.in> Date: Thu, 23 Dec 2021 16:22:25 +0300 Message-ID: <87czln5ucu.fsf@trop.in> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha512; protocol="application/pgp-signature" X-Host-Lookup-Failed: Reverse DNS lookup failed for 2a00:1450:4864:20::134 (failed) Received-SPF: none client-ip=2a00:1450:4864:20::134; envelope-from=andrew@trop.in; helo=mail-lf1-x134.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_NONE=0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-Spam-Score: -2.3 (--) X-Debbugs-Envelope-To: submit Cc: Xinglu Chen X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable * guix.texi (Writing Service Configuration): New section. =2D-- doc/guix.texi | 252 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 248 insertions(+), 4 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 333cb4117a..29d85d3dc5 100644 =2D-- a/doc/guix.texi +++ b/doc/guix.texi @@ -10363,6 +10363,7 @@ compiling modules. It can be @code{#f}, @code{#t},= or @code{'detailed}. The other arguments are as for @code{derivation} (@pxref{Derivations}). @end deffn =20 +@anchor{file-like objects} @cindex file-like objects The @code{local-file}, @code{plain-file}, @code{computed-file}, @code{program-file}, and @code{scheme-file} procedures below return @@ -15942,6 +15943,7 @@ symlink: Return a service that sets the host name to @var{name}. @end deffn =20 +@anchor{console-font-service-type} @defvr {Scheme Variable} console-font-service-type Install the given fonts on the specified ttys (fonts are per virtual console on the kernel Linux). The value of this service is a list= of @@ -33717,6 +33719,7 @@ a daemon that can execute application bundles (some= times referred to as =20 @end defvr =20 +@anchor{docker-configuration} @deftp {Data Type} docker-configuration This is the data type representing the configuration of Docker and Contain= erd. =20 @@ -35652,10 +35655,11 @@ them in an @code{operating-system} declaration. = But how do we define them in the first place? And what is a service anyway? =20 @menu =2D* Service Composition:: The model for composing services. =2D* Service Types and Services:: Types and services. =2D* Service Reference:: API reference. =2D* Shepherd Services:: A particular type of service. +* Service Composition:: The model for composing services. +* Service Types and Services:: Types and services. +* Writing Service Configurations:: A guideline for writing guix services. +* Service Reference:: API reference. +* Shepherd Services:: A particular type of service. @end menu =20 @node Service Composition @@ -35851,6 +35855,245 @@ There can be only one instance of an extensible s= ervice type such as Still here? The next section provides a reference of the programming interface for services. =20 +@node Writing Service Configurations +@subsection Writing Service Configurations + +Guix already contains a wide variety of system and home services, but +sometimes users might want to add new services. This section contains +tips for simplifying this process, and should help to make service +configurations and their implementations more consistent. + +@quotation Note +If you find any exceptions or patterns missing in this section, please +send a patch with additions/changes to @email{guix-devel@@gnu.org} +mailing list or just start a discussion/ask a question. +@end quotation + +@subsubheading Configuration Itself + +As we know from previous sections, a Guix service can accept a service +value, usually some kind of configuration record and optionally, be +extended with additional values by other services (@pxref{Service +Composition}). + +When being extended, most services take some kind of configuration +record or a list thereof, but in some cases a simpler value is all +that is necessary. + +There are some cases, when the service accepts a list of pairs or some +other non-record values. For example, @code{console-font-service-type} +(@pxref{console-font-service-type}) accepts an +association list, and @code{etc-service-type} (@pxref{etc-service-type}) +accepts a list of lists. Those services are kinda special, they do +auxiliary work of setting up some part of the operating system or home +environment, or just an intermediate helpers used by other Guix +services. For example @code{etc-service-type} is not that useful on its +own, but it helps other services to create files in /etc directory, when +it necessary. + +However, in most cases a Guix service is wrapping some software, which +consists of one or more packages, and configuration file or files. +Therefore, the value for such service is quite complicated and it's hard +to represent it with just a list or basic data type, in such cases we +use a record. Each such record (@pxref{SRFI-9 Records, Scheme Records,, +guile, GNU Guile Reference Manual}) have @samp{-configuration} suffix, +for example, the @code{docker-service-type} should accept a record type +named @code{docker-configuration}, which contains fields used to +configure Docker. Configuration records for home services should also +have a @code{home-} prefix in their name. + +There is a module @code{gnu service configuration}, which contains +helpers simplifying configuration definition process. Take a look at +@code{gnu services docker} module or grep for +@code{define-configuration} to find usage examples. + +@c Provide some examples, tips, and rationale behind @code{gnu service +@c configuration} module. + +After a configuration record has been properly named and defined let's +discuss how to name and define the fields, and which approach to use for +implementing the serialization code related to them. + +In this context, the @dfn{serialization} is a process of converting +values of the fields defined in service configuration into a string or +strings of a target config format, which will be put to the +configuration file or files used by the program. + +@subsubheading Configuration Record Fields + +@enumerate +@item +It's a good idea to have one or more fields for specifying the package +or packages that will be installed by a service. For example, +@code{docker-configuration} has @code{docker}, @code{docker-cli}, +@code{containerd} fields (@pxref{docker-configuration}). Sometimes it +make sense to make a field, which accepts a list of packages for cases, +where an arbitrary list of plugins can be passed to the configuration. +There are some services, which provide a field called @code{package} in +their configuration, which is ok, but the way it done in +@code{docker-configuration} is more flexible and thus preferable. + +@item +Fields for configuration files, should be name the same as target +configuration file name, but in kebab-case@footnote{The case used for +identifiers in languages of Lisp family, example: +@code{this-is-kebab-case}.}: @code{bashrc} for @file{.bashrc}, +@code{bash-profile} for @file{.bash_profile}, +@code{tmux-conf} for @file{tmux.conf}, etc. The implementation +for such fields will be discussed in the next subsubsection. + +@item +Other fields in most cases add some boilerplates/reasonable defaults to +configuration files, enable/disable installation of some packages or +provide other custom behavior, for example @code{guix-defaults?} or +@code{aliases} fields in @code{home-bash-configuration} +(@pxref{home-bash-configuration}). There is no any special requirements +or recommendations here, but it's necessary to make it possible to +disable all the effects of such fields to provide a user with an empty +configuration and let them generate it from scratch with only field for +configuration file. For example, setting @code{guix-defaults?} to +@code{#f} and @code{aliases} to @code{'()} will give user an ability to +control the content of @file{.bashrc} solely by setting the value of +@code{bashrc} field. + + +@end enumerate + +@subsubheading Fields for Configuration Files + +The field should accept a data structure (preferably a combination of +simple lists, alists, @ref{Vectors, vectors,, guile,}, +@ref{G-Expressions, gexps} and basic data types), which will be +serialized to target configuration format, in other words, it should +provide an alternative Lisp syntax, which can be later translated to a +target one, like SXML to XML. Such approach is quite flexible and +simple, it requires to write serializer once for one configuration +format and can be reused multiple times in different Guix services. + +Let's take a look at JSON: we implement serialization function, which +converts vectors to arrays, alists to objects (AKA dictionaries or +associative arrays), numbers to numbers, gexps to the strings, +@ref{file-like objects} (@pxref{G-Expressions}) to the strings, which +contains the path to the file in the store, @code{#t} to @code{true} and +so on, and now we have all programs using JSON as a format for +configurations covered. Maybe some fine-tunning will be needed for +particular application, but the primary serialization part is already +finished. + +The pros and cons of such approach is inherited from open-world +assumption. It doesn't matter if the underlying applications provides +new configuration options, we don't need to change anything in the +service configuration and its serialization code, it will work perfectly +fine. On the other hand, it is harder to type check and structure check +at ``compile-time'', and we can end up with a configuration, which won't +be accepted by the target program due to unexisting, misspelled or +wrongly-typed options. It's possible to add those checks, but we will +get the drawbacks of closed-world assumption: we need to keep the +service implementation in-sync with app config options, and it will make +impossible to use the same service with older/newer package version, +which has a slightly different list of available options and will add an +excessive maintanence load. + +However, for some applications with really stable configuration those +checks can be helpful and should be implemented if possible, for some +others we can implement them only partially. + +The alternative approach applied in some exitsting services is to use +records for defining the structure of configuration field, it has the +same downsides of closed-world assumption and a few more problems: + +@enumerate +@item +It has to replicate all the available options for the app (sometimes +hundreds or thousands) to allow the user express any configuration they +want. +@item +Having a few records adds one more layer of abstraction between service +configuration and resulting app config, including different field +casing, new semantic units. +@c provide examples? +@item +It harder to implement optional settings, serialization becomes very +ad-hoc and hard to reuse among other services with the same target +config format. +@end enumerate + +Exceptions can exist, but the overall idea is to provide a lispy syntax +for target configuration. Take a look at Sway example configuration +(which also can be used for i3). The following value of @code{config} +field of @code{home-sway-configuration}: + +@example +`((include ,(local-file "./sway/config")) + (bindsym $mod+Ctrl+Shift+a exec emacsclient -c --eval "'(eshell)'") + (bindsym $mod+Ctrl+Shift+o "[class=3D\"IceCat\"]" kill) + (input * ((xkb_layout us,ru) + (xkb_variant dvorak,)))) +@end example + +would yield something like: + +@example +include /gnu/store/408jwvh6wxxn1j85lj95fniih05gx5xj-config +bindsym $mod+Ctrl+Shift+a exec emacsclient -c --eval '(eshell)' +bindsym $mod+Ctrl+Shift+o [class=3D"IceCat"] kill +input * @{ + xkb_layout us,ru + xkb_variant dvorak, +@} +@end example + +The mapping between Scheme code and resulting configuration is quite +obvious. The serialization code with some type and structure checks +takes less than 70 lines and every possible Sway/i3 configuration can be +expressed using this field. + +@subsubheading Let User Escape +Sometimes a user already has a configuration file for an program, make +sure that it is possible to reuse it directly without rewriting. In the +example above, the following snippet allows one to include already an +existing config to the newly generated one utilizing @code{include} +directive of i3/Sway config language: + +@lisp +(include ,(local-file "./sway/config")) +@end lisp + +When building a resulting config the file-like objects are substituted +with a path of the file in the store and Sway's @code{include} loads +this file during startup. The way file-like objects are treated here +also allows one to specify paths to plugins or other binary files like: + +@lisp +(load-plugin ,(file-append plugin-package "/share/plugin.so")) +@end lisp + +(the example value for imaginary service configuration config file +field). + +In some cases the target configuration language may not have such an +@code{include} directive and can't provide such a functionallity, to +workaround it we can do the following trick: + +@lisp +#~(call-with-input-file + #$(local-file "./sway/config") + (@@ (ice-9 textual-ports) get-string-all)) +@end lisp + +The =E2=80=98get-string-all=E2=80=99 procedure will read the contents of t= he +@file{./sway/config} file (to be more preciese the copy of this file +placed in the store), and return a string containing the contents. Once +serialized, the G-expression will thus be turn into the contents of the +Sway configuration file in @file{./sway/config}. This code can be +easily combined with the rest of Sway's configuration, additionally, we +can control the place where the content of @file{./sway/config} will +appear in resulting file by moving this snippet around. + +Following these simple rules will help to make simple, consistent and +maintainable service configurations, and will let users express any +possible needs and reuse existing configuration files. + @node Service Reference @subsection Service Reference =20 @@ -36076,6 +36319,7 @@ The type of the ``boot service'', which produces th= e @dfn{boot script}. The boot script is what the initial RAM disk runs when booting. @end defvr =20 +@anchor{etc-service-type} @defvr {Scheme Variable} etc-service-type The type of the @file{/etc} service. This service is used to create files under @file{/etc} and can be extended by =2D-=20 2.34.0 --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQJDBAEBCgAtFiEEKEGaxlA4dEDH6S/6IgjSCVjB3rAFAmHEeBEPHGFuZHJld0B0 cm9wLmluAAoJECII0glYwd6wZYMQAIvoi0tVHwWUFhfmI75JUb483zA1fVDa6pI9 4/XHuE1XdGEdC4Wbjvfnh14ue1rJMF/lMuY6PnRRXHT4kniON+poGYPD0S4W02V+ cFISWTuSTwFnRaZjs9y4qet1S2a0JVBv5CBH1BtK1nK+MB2l0Djsj388Nlxc5uUI YGueNBxFB5eBweOn8sTOGROyy/8ZV3kGzIuHyE7X8aY9pV31/30tzYY6KqELwx5K I4mnEmSftq/OF55xu0R3SPazUN9W3F7/GIbmy6BPpd0495VTu4IGXdS3Gpyf40L7 tXFl/8pC3BYPOgcSUmSw7/eyruKtVxwOFm0t/4GmlwtVIKUBFPHavP5S3nj9K7l9 I8OnZjWMlAcPqr/VS7YCcuimwUEuhr5CLThGJPPcL4bFuRwHZez+n2nQyD8Vt1NM qUQavA1y/vv3DACtGmqUc/ZAm4H/5gXa8ka/gx25nqaM/2h+uRnq074FI4Xja8iD iX0dezQp7SAbIWT3ANkw339laQTFebhkAstrpgOWn7kXtMmY0Sip1p1DXhk0ipzb 9MK+2FiKfSOW6S9Oevfz/k8JsvByUTnHa8BIj4yx4yNS82U3LmW2XVsVAniYxnSK mUYdpnCfJJZnv3XBK+o0MTGa2qoRzv3h2XjhLAAoVefENdaBnBbppvsSBFYVdFp4 fjDkZbGj =2leU -----END PGP SIGNATURE----- --=-=-=--