From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id yOiUBdOqwWEYIgEAgWs5BA (envelope-from ) for ; Tue, 21 Dec 2021 11:22:11 +0100 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0 with LMTPS id CEFlAdOqwWHhRAAA1q6Kng (envelope-from ) for ; Tue, 21 Dec 2021 10:22:11 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id A33172F12D for ; Tue, 21 Dec 2021 11:22:10 +0100 (CET) Received: from localhost ([::1]:58660 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mzcHZ-00053F-Lg for larch@yhetil.org; Tue, 21 Dec 2021 05:22:09 -0500 Received: from eggs.gnu.org ([209.51.188.92]:36754) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mzcH8-0004sV-Sr for guix-devel@gnu.org; Tue, 21 Dec 2021 05:21:42 -0500 Received: from [2a00:1450:4864:20::130] (port=42982 helo=mail-lf1-x130.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mzcH5-00085T-In for guix-devel@gnu.org; Tue, 21 Dec 2021 05:21:41 -0500 Received: by mail-lf1-x130.google.com with SMTP id b22so28205742lfb.9 for ; Tue, 21 Dec 2021 02:21:31 -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:date:message-id:mime-version; bh=Q3lvO9QOQj5fjzFzj8pWQBmuoKSgsrEDi1eyD0+rGIk=; b=8ID8hQrGTExMN04NwrbrQoC28TD/Hi9gOVmgIi076aZetzu+dCf6kjISePtAjleKCj GTnBkTH6VWHzPmL/T3sCeZphV1ypccXrs17dTpgnLUt3lVzURX4VVSd/h6csZ+/QPrJY X4L0NXKjY23rHj+axfMLlrvYNTbplfYGBQ7YYYhWC1Bxy73A92/AFa6XBowrVpfkV4s2 mkjyF9NjHniLmdZ+8vsrlVqSj07OC3ZaDAViJIhNZrVJQxv6TotsRQPo2BSbEtq7Jvlr 7WfYZPPsk3fRbVnin6HWqaKnPv4kCWAem9FGQOBOMGG3/mXCv5aGSisNSnp5REa+Y4Ia u6UA== 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:date:message-id:mime-version; bh=Q3lvO9QOQj5fjzFzj8pWQBmuoKSgsrEDi1eyD0+rGIk=; b=UZxviaids+t7SEWrvvR/2ZiHlokP41tiplaeOPgJggEX9ZGuP1KzTAblj+v/z455l2 QJAbt6OEG6bZ9NPf7aQKjnLCbBSmwwdhArKBPNTnNpE09uuQJ7UPMpDXH/oqpiYmqkaU HLp0utuuUhgmJAlsUWpOjTYWodrPNOQQjhf6dxz6I5BP846GUWeWfGTc/eWodIBpT8nt lAMQhF4Pp8UIElEJ9Twwu6pwSafndKH2BxD/jt38lv7w1LV6fAEJnWhw0g4gyYd3QIjX b3TfRy2qvD0Y2n95NWWkLSpWTOUhKxr7PsHk4b30lpx28uyzQ32OLs/t9QGuzyoFGxuT Z1EQ== X-Gm-Message-State: AOAM532KR9/mrsk1kwHFo87p09Znl/nE7axlD6pKj1uMeEdNrhHPSB5l 6aRHNFMp0bO2mbwaIt1Z2hEqfA5aGqqmzw== X-Google-Smtp-Source: ABdhPJxfK6hc9qoC5OWNEKFND7ppLg5lfSzqV5HyrNVzSYQaUHtSZvEhdIgt9ISghdtcpzyXuHD6iw== X-Received: by 2002:a05:6512:31d5:: with SMTP id j21mr2472563lfe.669.1640082089821; Tue, 21 Dec 2021 02:21: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 j16sm2666997lfe.4.2021.12.21.02.21.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Dec 2021 02:21:29 -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: [RFC PATCH] doc: Add Writing Service Configuration section. Date: Tue, 21 Dec 2021 13:21:20 +0300 Message-ID: <87h7b2b6n3.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::130 (failed) Received-SPF: none client-ip=2a00:1450:4864:20::130; envelope-from=andrew@trop.in; helo=mail-lf1-x130.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-BeenThere: guix-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Xinglu Chen Errors-To: guix-devel-bounces+larch=yhetil.org@gnu.org Sender: "Guix-devel" X-Migadu-Flow: FLOW_IN X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1640082130; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=Q3lvO9QOQj5fjzFzj8pWQBmuoKSgsrEDi1eyD0+rGIk=; b=a8lNeZOIKKSbzFbdRnPYWorS2CT9oCACcdtnR4M1vEYDO1AAYp7zXW/gyjwZXEZMcynV4+ YqqGPUuaVzMHWgDNrZQ7C5ZUHShqJggI8/r2bxBnT9Tyb/iYcuoeLnrnC2yFwq8XplrnoH 3bR5OTQgIqtSY/D2Dzvsg1JsjmM/fZznV5+N1nZ+L8B5Za3xE+2fOMAob8nT09cjxJbFwL LOIdqI8bHs94W+7Egj2uG+PbRKgS+RXpt0UNN4+stHzWcUloTHhulivHza5AEFPd3xc+H8 biUYPJDVr+1X57BkogargFhvgvXGB2P1WKpQJybnsPYZEAquVF5MhYtomM9HzQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1640082130; a=rsa-sha256; cv=none; b=fM+EoCZHkfzv1e4v94g1zGQt792TqqJ3vQqqQaemsRY1tWfpWCUW6kUBlVTAwyb/1nDLR0 4lQ1HuIXUuE898F7p1/bmv77jhdmSUy2HQU0gb9+m+N8gS5In9bS3hfDQ9S4Y9+V+Znz9f LpcCOv4O7TV5d2DOrAnF3AAGOMuQIJO+RGeKp2hd8oYsLnyRW22rTUqwcxuzDjBuXWi1XX WiXQH90wBIx4CoyllkadyY7G6jXxBE5T24bVpLEc9TMOR33P7Y69fAT9HljQR0MWVQ8sFO 0JAjkD1SQc0h3y84guANGGY3dtIwqqOI9/IyZ3n6esAn15RJPx67rr7liYZSIA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=trop-in.20210112.gappssmtp.com header.s=20210112 header.b=8ID8hQrG; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-devel-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-devel-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: -10.63 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=trop-in.20210112.gappssmtp.com header.s=20210112 header.b=8ID8hQrG; dmarc=none; spf=pass (aspmx1.migadu.com: domain of "guix-devel-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="guix-devel-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: A33172F12D X-Spam-Score: -10.63 X-Migadu-Scanner: scn0.migadu.com X-TUID: EmKavNmXyDoR --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable * guix.texi (Writing Service Configuration): New section. =2D-- After reading the source code of different system services and implementing= a few of home services I decided to write down most important tips for implementing guix service configurations. I belive having such a guideline can simplify the development of new services and configurations for them, as well as keeping those implementations consistent, which will simplify the l= ife for users too because they won't need to learn a different configuration approaches for different services. This section is not a final document, but a starting point for discussion a= nd further extension of the guideline. Feel free to raise a question, point t= o a mistake, make a suggestion or propose an idea. Best regards, Andrew Tropin doc/guix.texi | 209 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 4 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 333cb4117a..a48fb0e2b7 100644 =2D-- a/doc/guix.texi +++ b/doc/guix.texi @@ -35652,10 +35652,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. +* Service Reference:: API reference. +* Shepherd Services:: A particular type of service. +* Writing Service Configurations:: A guideline for writing guix services. @end menu =20 @node Service Composition @@ -35851,6 +35852,206 @@ 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 + +There are a lot of system and home services already written, but from +time to time it's necessary to write one more. 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 section a guix service can accept a value and +be extended with additional values by other services. There are some +cases, when the service accepts a list of pairs or some other values for +example @code{console-font-service-type} accepts list of pairs (tty and +font name/file) or @code{etc-service-type} accepts list of lists +(resulting file name and file-like object), those services are kinda +special, they are an intermediate helpers doing auxiliary work. + +However, in most cases a guix service is wrapping some software, which +consist of package or a few packages, and configuration file or files. +Therefore, the value for such service is quite complicated and it's hard +to represent it with a just list or basic data type, in such cases we +use a record. Each such record have -configuration suffix, for example +@code{docker-configuration} for @code{docker-service-type} and a few +different fields helping to customize the software. Configuration +records for home services 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 properly named and defined let's discuss +how to name and define fields, and which approach to use for +implementing the serialization code related to them. + +@subsubheading Configuration Record Fields + +@enumerate +@item +It's a good idea to have a field/fields for specifying package/packages +being installed for this service. For example +@code{docker-configuration} has @code{docker}, @code{docker-cli}, +@code{containerd} fields. 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 called the same as target +configuration file name, but in kebab-case: bashrc for bashrc, +bash-profile for bash_profile, 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, turns on/off installation of some packages or +provide other custom behavior. 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. + +@end enumerate + +@subsubheading Fields for Configuration Files + +The field should accept a datastructure (preferably a combination of +simple lists, alists, vectors, 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 +target one, like SXML for 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, file-like +objects 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 apps +using JSON and YAML as a format for configurations covered. Maybe some +fine-tunning will be needed for particular application, but the primary +serilalization part is already finished. + +The pros and cons of such approach is inherited from open-world +assumption. It doesn't matter if underlying applications provides new +configuration options, we don't need to change anything in service +configuration and its serialization code, it will work perfectly fine, +on the other hand it harder to type check and structure check +``compile-time'', and we can end up with configuration, which won't be +accepted by target application cause of 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 +other 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 user express any configuration they +wants. +@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 user already have a configuration file for an app, make sure +that it is possible to reuse it directly without rewriting. In the +example above, the following snippet allows to include already existing +config to the newly generated one utilizing @code{include} directive of +i3/sway config language: + +@example +(include ,(local-file "./sway/config")) +@end example + +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 to specify paths to plugins or other binary files like: +@code{(load-plugin ,(file-append plugin-package "/share/plugin.so"))} +(the example value for imaginary service configuration config file +field). + +In some cases target configuration language may not have such +@code{include} directive and can't provide such a functionallity, to +workaround it we can do the following trick: + +@example +`(#~(call-with-input-file + #$(local-file "./sway/config") + (@@ (ice-9 textual-ports) get-string-all))) +@end example + +G-expressions get serialized to its values, and the example above reads +the content of the file-like object and inserts it in the resulting +configuration file. + +Following these simple rules will help to make a simple, consistent and +maintainable service configurations, will let user express any possible +needs and reuse existing configuration files. + @node Service Reference @subsection Service Reference =20 =2D-=20 2.34.0 --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQJDBAEBCgAtFiEEKEGaxlA4dEDH6S/6IgjSCVjB3rAFAmHBqqAPHGFuZHJld0B0 cm9wLmluAAoJECII0glYwd6wzU0P/iXz2f7KJa79dV9MlD+7lGC2U90W9kwDXVLe nnL0bPaCLIv4rmvNkjOJWEhMLgUh/i/RAL82I6hWLwXgynimBPeBLqq4YoxJLRuG oqySKO/sFOMH29Ht71QW7gJ1KggfA2EjTnUB06z7ysEBDzQHKasDitY5bDxr9zrg mtXqezjMQkVaoWTxi0HIDTtE/jUfTCYbxSr9qqyrJA0uRolT5YQR5iT0Zy3BzoFq 38vwcTAttqhaLXfrJk5Jk/kE/mrnJ6xes1+q2m4yMXcCj3K1qXfL/8+4/ePEM9dF tQZV2n/IplVvnjbmy9JDng9O/zCddEq2MrKWnb8cGXxDs+C9GhvVcTRGPFRso/rq 0obrflYvPGmMZ4kftZMwfqDC2O8pi5MF10xCXjxKYRWLyVcLSc7mO0C817D+xTh8 vHxXxRizbMEqXvmNXz1Bfm6XmT1Ki/NcjYRU9UM45ss/35OWrWXrzd7Dg3qKfkwI mLn5yV03xLI2T+3KFmDDEdQkGj6SCnnfmw8wiQVrv+8NLG69p6Z3Q7LDK0bGrVuu TO8rXx7R85zKUfPaFz9tx0xDlDp2lxinZK995v2geH49ykfPUeg+aii9/Fjlcu/1 HuDnSDcH8h/Ix3kuRtql1EIzmKTbgfPLKKKUE3PylS6CNKKMu8AYgpiKNO7J/EyB plWLIyVb =YYUo -----END PGP SIGNATURE----- --=-=-=--