Skip to content

Commit 2cdaf72

Browse files
committed
nixos/doc: Add modular services section
1 parent d0eeba2 commit 2cdaf72

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed

nixos/doc/manual/default.nix

+28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ let
1414
inherit (pkgs) buildPackages runCommand docbook_xsl_ns;
1515

1616
inherit (pkgs.lib)
17+
evalModules
1718
hasPrefix
1819
removePrefix
1920
flip
@@ -116,8 +117,35 @@ let
116117
${testOptionsDoc.optionsJSON}/${common.outputPath}/options.json
117118
sed -e '/@PYTHON_MACHINE_METHODS@/ {' -e 'r ${testDriverMachineDocstrings}/machine-methods.md' -e 'd' -e '}' \
118119
-i ./development/writing-nixos-tests.section.md
120+
substituteInPlace ./development/modular-services.md \
121+
--replace-fail \
122+
'@PORTABLE_SERVICE_OPTIONS@' \
123+
${portableServiceOptions.optionsJSON}/${common.outputPath}/options.json
124+
substituteInPlace ./development/modular-services.md \
125+
--replace-fail \
126+
'@SYSTEMD_SERVICE_OPTIONS@' \
127+
${systemdServiceOptions.optionsJSON}/${common.outputPath}/options.json
119128
'';
120129

130+
portableServiceOptions = buildPackages.nixosOptionsDoc {
131+
inherit (evalModules { modules = [ ../../modules/system/service/portable/service.nix ]; }) options;
132+
inherit revision warningsAreErrors;
133+
transformOptions = opt: opt // {
134+
# Clean up declaration sites to not refer to the NixOS source tree.
135+
declarations = map stripAnyPrefixes opt.declarations;
136+
};
137+
};
138+
139+
systemdServiceOptions = buildPackages.nixosOptionsDoc {
140+
inherit (evalModules { modules = [ ../../modules/system/service/systemd/service.nix ]; }) options;
141+
# TODO: filter out options that are not systemd-specific, maybe also change option prefix to just `service-opt-`?
142+
inherit revision warningsAreErrors;
143+
transformOptions = opt: opt // {
144+
# Clean up declaration sites to not refer to the NixOS source tree.
145+
declarations = map stripAnyPrefixes opt.declarations;
146+
};
147+
};
148+
121149
in
122150
rec {
123151
inherit (optionsDoc) optionsJSON optionsNix optionsDocBook;

nixos/doc/manual/development/development.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ writing-documentation.chapter.md
1212
nixos-tests.chapter.md
1313
developing-the-test-driver.chapter.md
1414
testing-installer.chapter.md
15+
modular-services.md
1516
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
# Modular Services {#modular-services}
3+
4+
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 <https://github.com/NixOS/nixpkgs/pull/372170>
5+
6+
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.
7+
8+
A *modular service* is a [module] that defines values for a core set of options, including which program to run.
9+
10+
NixOS provides two options into which such modules can be plugged:
11+
12+
- `system.services.<name>`
13+
- an option for user services (TBD)
14+
15+
Crucially, these options have the type [`attrsOf`] [`submodule`].
16+
The name of the service is the attribute name corresponding to `attrsOf`.
17+
<!-- ^ This is how composition is *always* provided, instead of a difficult thing (but this is reference docs, not a changelog) -->
18+
The `submodule` is pre-loaded with two modules:
19+
- a generic module that is intended to be portable
20+
- a module with systemd-specific options, whose values or defaults derive from the generic module's option values.
21+
22+
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:
23+
24+
<!-- Not using typical example syntax, because reading this is *not* optional, and should it should not be folded closed. -->
25+
```nix
26+
{
27+
system.services.httpd = {
28+
imports = [ nixpkgs.modules.services.foo ];
29+
foo.settings = {
30+
# ...
31+
};
32+
};
33+
}
34+
```
35+
36+
## Portability {#modular-service-portability}
37+
38+
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:
39+
40+
```nix
41+
{ config, options, lib, ... }: {
42+
_class = "service";
43+
config = {
44+
process.executable = "${lib.getExe config.foo.program}";
45+
} // lib.optionalAttrs (options?systemd) {
46+
# ... systemd-specific definitions ...
47+
};
48+
}
49+
```
50+
51+
This way, the module can be loaded into a configuration manager that does not use systemd, and the `systemd` definitions will be ignored.
52+
Similarly, other configuration managers can declare their own options for services to customize.
53+
54+
## Composition and Ownership {#modular-service-composition}
55+
56+
Compared to traditional services, modular services are inherently more composable, by virtue of being modules and receiving a user-provided name when imported.
57+
However, composition can not end there, because services need to be able to interact with each other.
58+
This can be achieved in two ways:
59+
1. Users can link services together by providing the necessary NixOS configuration.
60+
2. Services can be compositions of other services.
61+
62+
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.
63+
64+
## Migration {#modular-service-migration}
65+
66+
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.
67+
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.
68+
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.
69+
70+
<!-- TODO example of a single-instance service -->
71+
72+
## Portable Service Options {#modular-service-options-portable}
73+
74+
```{=include=} options
75+
id-prefix: service-opt-
76+
list-id: service-options
77+
source: @PORTABLE_SERVICE_OPTIONS@
78+
```
79+
80+
## Systemd-specific Service Options {#modular-service-options-systemd}
81+
82+
```{=include=} options
83+
id-prefix: systemd-service-opt-
84+
list-id: systemd-service-options
85+
source: @SYSTEMD_SERVICE_OPTIONS@
86+
```
87+
88+
[module]: https://nixos.org/manual/nixpkgs/stable/index.html#module-system
89+
<!-- TODO: more anchors -->
90+
[`attrsOf`]: #sec-option-types-composed
91+
[`submodule`]: #sec-option-types-submodule

nixos/doc/manual/redirects.json

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22
"book-nixos-manual": [
33
"index.html#book-nixos-manual"
44
],
5+
"modular-service-composition": [
6+
"index.html#modular-service-composition"
7+
],
8+
"modular-service-migration": [
9+
"index.html#modular-service-migration"
10+
],
11+
"modular-service-options-portable": [
12+
"index.html#modular-service-options-portable"
13+
],
14+
"modular-service-options-systemd": [
15+
"index.html#modular-service-options-systemd"
16+
],
17+
"modular-service-portability": [
18+
"index.html#modular-service-portability"
19+
],
20+
"modular-services": [
21+
"index.html#modular-services"
22+
],
523
"module-services-anubis": [
624
"index.html#module-services-anubis"
725
],

pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/redirects.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def validate(self, initial_xref_targets: dict[str, XrefTarget]):
114114
- The first element of an identifier's redirects list must denote its current location.
115115
"""
116116
xref_targets = {}
117-
ignored_identifier_patterns = ("opt-", "auto-generated-", "function-library-")
117+
ignored_identifier_patterns = ("opt-", "auto-generated-", "function-library-", "service-opt-", "systemd-service-opt")
118118
for id, target in initial_xref_targets.items():
119119
# filter out automatically generated identifiers from module options and library documentation
120120
if id.startswith(ignored_identifier_patterns):

0 commit comments

Comments
 (0)