From 1ee1ac47cc528474fecba733adab4b436b12b79f Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 8 Jan 2025 16:22:49 +0100 Subject: [PATCH] nixos/doc: Add modular services section --- nixos/doc/manual/default.nix | 28 ++++++ nixos/doc/manual/development/development.md | 1 + .../manual/development/modular-services.md | 91 +++++++++++++++++++ nixos/doc/manual/redirects.json | 18 ++++ .../src/nixos_render_docs/redirects.py | 2 +- 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 nixos/doc/manual/development/modular-services.md diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix index 5b171420b24d4..56e0ce5826eb0 100644 --- a/nixos/doc/manual/default.nix +++ b/nixos/doc/manual/default.nix @@ -13,6 +13,7 @@ let inherit (pkgs) buildPackages runCommand docbook_xsl_ns; inherit (pkgs.lib) + evalModules hasPrefix removePrefix flip @@ -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; diff --git a/nixos/doc/manual/development/development.md b/nixos/doc/manual/development/development.md index 76f405c3b29cc..37762afb41dab 100644 --- a/nixos/doc/manual/development/development.md +++ b/nixos/doc/manual/development/development.md @@ -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 ``` diff --git a/nixos/doc/manual/development/modular-services.md b/nixos/doc/manual/development/modular-services.md new file mode 100644 index 0000000000000..d0443081a4880 --- /dev/null +++ b/nixos/doc/manual/development/modular-services.md @@ -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 + +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.` +- 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`. + +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.` is not a complete service. It requires that the user provide a value, and this is typically done by importing a module. For example: + + +```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. + + + +## 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 + +[`attrsOf`]: #sec-option-types-composed +[`submodule`]: #sec-option-types-submodule diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index a7ebb49b62b8a..1a3ce92bf7fd4 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -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" ], diff --git a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/redirects.py b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/redirects.py index 1a891a1af238e..554295aff50b9 100644 --- a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/redirects.py +++ b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/redirects.py @@ -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):