* guix.texi (Writing Service Configuration): New section. --- 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 --- 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 +@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 +@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 (sometimes referred to as @end defvr +@anchor{docker-configuration} @deftp {Data Type} docker-configuration This is the data type representing the configuration of Docker and Containerd. @@ -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? @menu -* 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. +* 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 @node Service Composition @@ -35851,6 +35855,245 @@ There can be only one instance of an extensible service type such as Still here? The next section provides a reference of the programming interface for services. +@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=\"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="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 ‘get-string-all’ procedure will read the contents of the +@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 @@ -36076,6 +36319,7 @@ The type of the ``boot service'', which produces the @dfn{boot script}. The boot script is what the initial RAM disk runs when booting. @end defvr +@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 -- 2.34.0