Skip to content

Commit

Permalink
nixos/doc: Add modular services section
Browse files Browse the repository at this point in the history
  • Loading branch information
roberth committed Jan 8, 2025
1 parent 398f612 commit 1ee1ac4
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 1 deletion.
28 changes: 28 additions & 0 deletions nixos/doc/manual/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let
inherit (pkgs) buildPackages runCommand docbook_xsl_ns;

inherit (pkgs.lib)
evalModules
hasPrefix
removePrefix
flip
Expand Down Expand Up @@ -97,8 +98,35 @@ let
${testOptionsDoc.optionsJSON}/${common.outputPath}/options.json
sed -e '/@PYTHON_MACHINE_METHODS@/ {' -e 'r ${testDriverMachineDocstrings}/machine-methods.md' -e 'd' -e '}' \
-i ./development/writing-nixos-tests.section.md
substituteInPlace ./development/modular-services.md \
--replace-fail \
'@PORTABLE_SERVICE_OPTIONS@' \
${portableServiceOptions.optionsJSON}/${common.outputPath}/options.json
substituteInPlace ./development/modular-services.md \
--replace-fail \
'@SYSTEMD_SERVICE_OPTIONS@' \
${systemdServiceOptions.optionsJSON}/${common.outputPath}/options.json
'';

portableServiceOptions = buildPackages.nixosOptionsDoc {
inherit (evalModules { modules = [ ../../modules/system/service/portable/service.nix ]; }) options;
inherit revision warningsAreErrors;
transformOptions = opt: opt // {
# Clean up declaration sites to not refer to the NixOS source tree.
declarations = map stripAnyPrefixes opt.declarations;
};
};

systemdServiceOptions = buildPackages.nixosOptionsDoc {
inherit (evalModules { modules = [ ../../modules/system/service/systemd/service.nix ]; }) options;
# TODO: filter out options that are not systemd-specific, maybe also change option prefix to just `service-opt-`?
inherit revision warningsAreErrors;
transformOptions = opt: opt // {
# Clean up declaration sites to not refer to the NixOS source tree.
declarations = map stripAnyPrefixes opt.declarations;
};
};

in rec {
inherit (optionsDoc) optionsJSON optionsNix optionsDocBook;

Expand Down
1 change: 1 addition & 0 deletions nixos/doc/manual/development/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ writing-documentation.chapter.md
nixos-tests.chapter.md
developing-the-test-driver.chapter.md
testing-installer.chapter.md
modular-services.md
```
91 changes: 91 additions & 0 deletions nixos/doc/manual/development/modular-services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

# Modular Services {#modular-services}

Status: in development. This functionality is new in NixOS 25.05, and significant changes should be expected. We'd love to hear your feedback in <!-- FIXME PR link -->

Traditionally, NixOS services were defined using sets of options *in* modules, not *as* modules. This made them non-modular, resulting in problems with composability, reuse, and portability.

A *modular service* is a [module] that defines values for a core set of options, including which program to run.

NixOS provides two options into which such modules can be plugged:

- `system.services.<name>`
- an option for user services (TBD)

Crucially, these options have the type [`attrsOf`] [`submodule`].
The name of the service is the attribute name corresponding to `attrsOf`.
<!-- ^ This is how composition is *always* provided, instead of a difficult thing (but this is reference docs, not a changelog) -->
The `submodule` is pre-loaded with two modules:
- a generic module that is intended to be portable
- a module with systemd-specific options, whose values or defaults derive from the generic module's option values.

So note that the default value of `system.services.<name>` is not a complete service. It requires that the user provide a value, and this is typically done by importing a module. For example:

<!-- Not using typical example syntax, because reading this is *not* optional, and should it should not be folded closed. -->
```nix
{
system.services.httpd = {
imports = [ nixpkgs.modules.services.foo ];
foo.settings = {
# ...
};
};
}
```

## Portability {#modular-service-portability}

It is possible to write service modules that are portable. This is done by either avoiding the `systemd` option tree, or by defining process-manager-specific definitions in an optional way:

```nix
{ config, options, lib, ... }: {
_class = "service";
config = {
process.executable = "${lib.getExe config.foo.program}";
} // lib.optionalAttrs (options?systemd) {
# ... systemd-specific definitions ...
};
}
```

This way, the module can be loaded into a configuration manager that does not use systemd, and the `systemd` definitions will be ignored.
Similarly, other configuration managers can declare their own options for services to customize.

## Composition and Ownership {#modular-service-composition}

Compared to traditional services, modular services are inherently more composable, by virtue of being modules and receiving a user-provided name when imported.
However, composition can not end there, because services need to be able to interact with each other.
This can be achieved in two ways:
1. Users can link services together by providing the necessary NixOS configuration.
2. Services can be compositions of other services.

These aren't mutually exclusive. In fact, it is a good practice when developing services to first write them as individual services, and then compose them into a higher-level composition. Each of these services is a valid modular service, including their composition.

## Migration {#modular-service-migration}

Many services could be migrated to the modular service system, but even when the modular service system is mature, it is not necessary to migrate all services.
For instance, many system-wide services are a mandatory part of a desktop system, and it doesn't make sense to have multiple instances of them.
Moving their logic into separate Nix files may still be beneficial for the efficient evaluation of configurations that don't use those services, but that is a rather minor benefit, unless modular services potentially become the standard way to define services.

<!-- TODO example of a single-instance service -->

## Portable Service Options {#modular-service-options-portable}

```{=include=} options
id-prefix: service-opt-
list-id: service-options
source: @PORTABLE_SERVICE_OPTIONS@
```

## Systemd-specific Service Options {#modular-service-options-systemd}

```{=include=} options
id-prefix: systemd-service-opt-
list-id: systemd-service-options
source: @SYSTEMD_SERVICE_OPTIONS@
```

[module]: https://nixos.org/manual/nixpkgs/stable/index.html#module-system
<!-- TODO: more anchors -->
[`attrsOf`]: #sec-option-types-composed
[`submodule`]: #sec-option-types-submodule
18 changes: 18 additions & 0 deletions nixos/doc/manual/redirects.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@
"book-nixos-manual": [
"index.html#book-nixos-manual"
],
"modular-service-composition": [
"index.html#modular-service-composition"
],
"modular-service-migration": [
"index.html#modular-service-migration"
],
"modular-service-options-portable": [
"index.html#modular-service-options-portable"
],
"modular-service-options-systemd": [
"index.html#modular-service-options-systemd"
],
"modular-service-portability": [
"index.html#modular-service-portability"
],
"modular-services": [
"index.html#modular-services"
],
"module-services-crab-hole": [
"index.html#module-services-crab-hole"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def validate(self, initial_xref_targets: dict[str, XrefTarget]):
- The first element of an identifier's redirects list must denote its current location.
"""
xref_targets = {}
ignored_identifier_patterns = ("opt-", "auto-generated-", "function-library-")
ignored_identifier_patterns = ("opt-", "auto-generated-", "function-library-", "service-opt-", "systemd-service-opt")
for id, target in initial_xref_targets.items():
# filter out automatically generated identifiers from module options and library documentation
if id.startswith(ignored_identifier_patterns):
Expand Down

0 comments on commit 1ee1ac4

Please sign in to comment.