Modular Services
Status: in development. This functionality is new in release 25.11, and significant changes should be expected. We’d love to hear your feedback in our matrix channel or at the tracking issue.
This repository carries the subsystem outside of nixpkgs so that it can move at its own pace and be consumed by NixOS, Home Manager and nix-darwin alike.
The in-tree nixpkgs copy is disabled at evaluation time; see integrations/nixos/disable-upstream.nix.
In NixOS, services were traditionally defined using sets of options in modules, not as modules. This made them non-modular, resulting in problems with composability, reuse, and portability.
A configuration management framework is an application of evalModules with the class and specialArgs input attribute set to particular values.
NixOS is such a configuration management framework, and so are Home Manager,
nix-darwin, nimi
and finix.
The service management component of a configuration management framework is the set of module options that connects Nix expressions with the underlying service (or process) manager.
For NixOS this is the module wrapping systemd, on nix-darwin the module wrapping launchd, and on finix the module wrapping finit.
A modular service is a module that defines values for a core set of options declared in the service management component of a configuration management framework, including which program to run.
Since it’s a module, it can be composed with other modules via imports to extend its functionality.
The NixOS integration provided by this repository declares 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.
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:
{ config, ... }:
{
system.services.my-service-instance = {
imports = [ config.modularServices.some-application.default ];
foo.settings = {
# ...
};
};
}
config.modularServices.<pkg>.<svc> is how a NixOS configuration consumes a service: the service itself, plus the systemd-specific definitions that only this integration can supply.
modularServices.<pkg> pkgs is that service on its own, for an integration whose service manager is not systemd.
pkgs.<pkg>.services.* still resolves to the nixpkgs copy unless overlays.passthruServices is applied; see the README.
Motivation
Traditionally a NixOS service is a set of options in a module: one module declares services.foo.*, there is exactly one instance of it, and its name is fixed by whoever wrote the module.
Such a service cannot be instantiated twice, cannot be extended without patching the module that declares it, and cannot be used outside NixOS.
A modular service is a module as a service, which makes it:
instantiable more than once, under a name you choose, by importing it at
system.services.<name>;extensible, by importing further modules alongside it rather than editing it;
composable, since a service can contain sub-services of its own – see the section called “Composition and Ownership”;
portable across configuration management frameworks that declare the same core options – see the section called “Portability”.
This is also the answer to “why not just ship a systemd unit file”.
A unit file describes one service, for one service manager, in a format with no notion of options, types, defaults, or merging; it cannot be parameterized, imported twice under different names, or extended without editing it.
A modular service is a module that produces such a unit, and going through the module system is what buys the four properties above.
systemd-specific escapes remain available through the systemd option tree, guarded as shown in the section called “Portability”.
The portable option set is still small, as we try to find reusable abstractions.
It describes how to start the service (process.argv), optionally how to reload it (process.reloadSignal, process.reloadCommand, and configData for files whose contents may change without a restart), and how the service signals readiness (notificationProtocol).
Everything else – ordering, dependencies, restart policy, isolation – is left to the service manager, and on NixOS is reached through the systemd options.
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.
A module that only defines portable options is portable, but requires some translation layer to be picked up by a service manager:
# Non-module dependencies (`importApply`)
{ pkgs }:
# Service module
{ config, lib, ... }:
let
cfg = config.foo;
format = pkgs.formats.toml { };
in
{
_class = "service";
options.foo = {
package = lib.mkPackageOption pkgs "foo" { };
settings = lib.mkOption {
type = format.type;
default = { };
description = "Configuration for `foo`, rendered to `foo.toml`.";
};
};
config.process.argv = [
(lib.getExe cfg.package)
"--config"
(format.generate "foo.toml" cfg.settings)
];
}
You can define process-manager-specific definitions in an optional way like:
{
config,
options,
lib,
...
}:
{
_class = "service";
config = {
process.argv = [ (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.
The services in this repository take the second route further, and keep the manager-specific definitions out of the service module entirely: they live in a variant alongside the integration that understands them, integrations/nixos/modular/<pkg>/<svc>/system.nix for NixOS.
That leaves the service module in modular-services/ portable by construction rather than by convention, and it lets an integration refine a service it does not own.
Composition and Ownership
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:
Users can link services together by providing the necessary NixOS configuration.
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
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.
Writing and Reviewing a Modular Service
For more details, refer to the contributor documentation in Writing and Reviewing Modular Services.
Portable Service Options
-
_module.args -
Additional arguments passed to each module in addition to ones like
lib,config, andpkgs,modulesPath.This option is also available to all submodules. Submodules do not inherit args from their parent module, nor do they provide args to their parent module or sibling submodules. The sole exception to this is the argument
namewhich is provided by parent modules to a submodule and contains the attribute name the submodule is bound to, or a unique generated name if it is not bound to an attribute.Some arguments are already passed by default, of which the following cannot be changed with this option:
lib: The nixpkgs library.config: The results of all options after merging the values from all modules together.options: The options declared in all modules.specialArgs: ThespecialArgsargument passed toevalModules.All attributes of
specialArgsWhereas option values can generally depend on other option values thanks to laziness, this does not apply to
imports, which must be computed statically before anything else.For this reason, callers of the module system can provide
specialArgswhich are available during import resolution.For NixOS,
specialArgsincludesmodulesPath, which allows you to import extra modules from the nixpkgs package tree without having to somehow make the module aware of the location of thenixpkgsor NixOS directories.{ modulesPath, ... }: { imports = [ (modulesPath + "/profiles/minimal.nix") ]; }
For NixOS, the default value for this option includes at least this argument:
pkgs: The nixpkgs package set according to thenixpkgs.pkgsoption.
Type: lazy attribute set of raw value
Default:
{ }Declared by:
<nixpkgs/lib/modules.nix> -
configData -
Configuration data files for the service
These files are made available to the service and can be updated without restarting the service process, enabling configuration reloading. The service manager implementation determines how these files are exposed to the service (e.g., via a specific directory path). This path is available in the
pathsub-option for eachconfigData.<name>entry.This is particularly useful for services that support configuration reloading via signals (e.g., SIGHUP) or which pick up changes automatically, so that no downtime is required in order to reload the service.
Type: lazy attribute set of (submodule)
Default:
{ }Example:
{ "server.conf" = { text = '' port = 8080 workers = 4 ''; }; "ssl/cert.pem" = { source = ./cert.pem; }; }Declared by:
lib/services/config-data.nix -
configData.<name>.enable -
Whether this configuration file should be generated. This option allows specific configuration files to be disabled.
Type: boolean
Default:
trueDeclared by:
lib/services/config-data-item.nix -
configData.<name>.name -
Name of the configuration file (relative to the service’s configuration directory). Defaults to the attribute name.
Type: string
Declared by:
lib/services/config-data-item.nix -
configData.<name>.path -
The actual path where this configuration file will be available. This is determined by the service manager implementation.
On NixOS it is an absolute path. Other service managers may provide a relative path, in order to be unprivileged and/or relocatable.
Type: string (read only)
Declared by:
lib/services/config-data-item.nix -
configData.<name>.source -
Path of the source file.
Type: absolute path
Declared by:
lib/services/config-data-item.nix -
configData.<name>.text -
Text content of the configuration file.
Type: null or strings concatenated with “\n”
Default:
nullDeclared by:
lib/services/config-data-item.nix -
meta.maintainers -
List of maintainers of each module. This option should be defined at most once per module.
The option value is not a list of maintainers, but an attribute set that maps module file names to lists of maintainers.
Type: list of lib.maintainers
Default:
[ ]Example:
[ lib.maintainers.alice lib.maintainers.bob ]Declared by:
lib/services/vendor/meta-maintainers.nix -
meta.teams -
List of team maintainers of each module. This option should be defined at most once per module.
Type: list of lib.teams
Default:
[ ]Example:
[ lib.teams.acme lib.teams.haskell ]Declared by:
lib/services/vendor/meta-maintainers.nix -
notificationProtocol -
Notification protocol that this service supports with the underlying service manager.
Type: submodule
Declared by:
lib/services/service.nix -
notificationProtocol.s6 -
Whether to enable Whether the service supports s6-notify…
Type: boolean
Default:
falseExample:
trueDeclared by:
lib/services/service.nix -
notificationProtocol.systemd -
Whether to enable Whether the service supports systemd-notify…
Type: boolean
Default:
falseExample:
trueDeclared by:
lib/services/service.nix -
process.argv -
Command filename and arguments for starting this service. This is a raw command-line that should not contain any shell escaping. If expansion of environmental variables is required then use a shell script or
importasfrompkgs.execline.When
flagsare set, the arguments rendered from them are merged intoargv. Seeflagsfor how the two are ordered against each other.Type: list of (string or absolute path convertible to it)
Default:
[ ]Example:
[ (lib.getExe config.package) "--nobackground" ]Declared by:
lib/services/service.nix -
process.flagFormat -
Function mapping flag names to option format specs for
lib.cli.toCommandLine.Receives the flag name and returns
{ option, sep, explicitBool, formatArg? }.Type: function that evaluates to a(n) attribute set of anything
Default:
<function>Example:
name: { option = name; sep = "="; explicitBool = false; }Declared by:
lib/services/service.nix -
process.flags -
Flags to pass to the service process. The key is the flag name (e.g.
"--port"), the value is the flag value.Each
name = valuepair is rendered vialib.cli.toCommandLineusingflagFormat.null: the flag is omitted (regardless offlagFormat)bool: rendered per
flagFormat.explicitBoolexplicitBool = false(default):trueemits the bare flag,falseis omittedexplicitBool = true: bothtrueandfalseare rendered as explicit arguments viaflagFormat.formatArg
string / path / int: rendered as the option’s argument, joined to the option name per
flagFormat.sepand stringified byflagFormat.formatArg
To pass the same flag multiple times, use the list form with repeated keys, e.g.
[ { "--host" = "a"; } { "--host" = "b"; } ].The rendered arguments are merged into
argv, soargvandflagsshare a singlelib.mkOrderspace:A flag with no ordering property of its own is placed at priority 1250, between
lib.modules.defaultOrderPriority(1000, which is what an unadornedargvdefinition gets) andlib.mkAfter(1500). Plain flags therefore follow the command name and any other plainargvarguments.lib.mkAfteronargvstill lands after the flags, which is how trailing positional arguments are expressed.lib.mkOrderon a flag is honoured verbatim againstargv, so a sub-command can be placed between two groups of flags.
Because 1250 is substituted for flags that carry no ordering property,
lib.mkOrder 1000on a flag is indistinguishable from leaving that flag unadorned. To order a flag around plainargventries, pick a priority next to 1000, such as 999 or 1001.Type: attribute list of (null or boolean or signed integer or (string or absolute path convertible to it))
Default:
{ }Example:
{ "--port" = "8080"; "--verbose" = true; # ordered ahead of the unadorned flags above "--config" = lib.mkOrder 1100 "/etc/foo.conf"; } # or, for repeated flags: [ { "--host" = "localhost"; } { "--host" = "0.0.0.0"; } ]Declared by:
lib/services/service.nix -
process.reloadCommand -
Command used for reloading in the underlying service manager to reload.
Type: null or string
Default:
nullExample:
"${pkgs.coreutils}/bin/kill -HUP $MAINPID"Declared by:
lib/services/service.nix -
process.reloadSignal -
Configures the reload signal to send to the service manager.
Type: null or string
Default:
nullExample:
"HUP"Declared by:
lib/services/service.nix -
services -
A collection of modular services that are configured in one go.
You could consider the sub-service relationship to be an ownership relation. It does not automatically create any other relationship between services (e.g. systemd slices), unless perhaps such a behavior is explicitly defined and enabled in another option.
Type: attribute set of (submodule)
Default:
{ }Declared by:
lib/services/service.nix
Systemd-specific Service Options
-
_module.args -
Additional arguments passed to each module in addition to ones like
lib,config, andpkgs,modulesPath.This option is also available to all submodules. Submodules do not inherit args from their parent module, nor do they provide args to their parent module or sibling submodules. The sole exception to this is the argument
namewhich is provided by parent modules to a submodule and contains the attribute name the submodule is bound to, or a unique generated name if it is not bound to an attribute.Some arguments are already passed by default, of which the following cannot be changed with this option:
lib: The nixpkgs library.config: The results of all options after merging the values from all modules together.options: The options declared in all modules.specialArgs: ThespecialArgsargument passed toevalModules.All attributes of
specialArgsWhereas option values can generally depend on other option values thanks to laziness, this does not apply to
imports, which must be computed statically before anything else.For this reason, callers of the module system can provide
specialArgswhich are available during import resolution.For NixOS,
specialArgsincludesmodulesPath, which allows you to import extra modules from the nixpkgs package tree without having to somehow make the module aware of the location of thenixpkgsor NixOS directories.{ modulesPath, ... }: { imports = [ (modulesPath + "/profiles/minimal.nix") ]; }
For NixOS, the default value for this option includes at least this argument:
pkgs: The nixpkgs package set according to thenixpkgs.pkgsoption.
Type: lazy attribute set of raw value
Default:
{ }Declared by:
<nixpkgs/lib/modules.nix> -
systemd.lib -
Library functions for working with systemd services.
Available functions:
escapeSystemdExecArgs: Escapes a list of arguments for use in ExecStart. Prevents systemd’s specifier (%) and variable ($) substitution by escaping them to %% and $$ respectively.Example:
escapeSystemdExecArgs [ "/bin/echo" "Unit %n" ]produces"/bin/echo" "Unit %%n"
Type: lazy attribute set of raw value (read only)
Default:
{ }Declared by:
integrations/nixos/systemd/service.nix -
systemd.mainExecReload -
Main command line for systemd’s ExecReload with systemd’s specifier and environment variable substitution enabled.
This option sets the primary ExecReload entry, and is the way to extend the command line derived from
process.reloadCommand.This option allows you to use systemd specifiers like
%n(unit name),%i(instance),%t(runtime directory), and environment variables using${VAR}syntax in your command line.By default, it is set to
process.reloadCommand. Becauseprocess.reloadCommandis already a command line (not an argument list), it is used verbatim so that references like$MAINPIDare preserved.When
process.reloadCommandis unset, this option isnulland noExecReloadis emitted; a service may then setsystemd.service.serviceConfig.ExecReloaditself.To extend
process.reloadCommandwith systemd specifiers, you can append to the command line:systemd.mainExecReload = config.process.reloadCommand + " --systemd-unit %n";This pattern allows you to pass the unit name (or other systemd specifiers) as additional arguments.
See systemd.service(5) (section “COMMAND LINES”) for details on variable substitution and systemd.unit(5) (section “SPECIFIERS”) for available specifiers like
%n,%i,%t.Type: null or string
Default:
config.process.reloadCommandDeclared by:
integrations/nixos/systemd/service.nix -
systemd.mainExecStart -
Main command line for systemd’s ExecStart with systemd’s specifier and environment variable substitution enabled.
This option sets the primary ExecStart entry. Additional ExecStart entries can be added via
systemd.service.serviceConfig.ExecStartwithlib.mkBeforeorlib.mkAfter.This option allows you to use systemd specifiers like
%n(unit name),%i(instance),%t(runtime directory), and environment variables using${VAR}syntax in your command line.By default, this is set to the escaped version of
process.argvto prevent systemd substitution. Set this option explicitly to enable systemd’s substitution features.To extend
process.argvwith systemd specifiers, you can append to the escaped arguments:systemd.mainExecStart = config.systemd.lib.escapeSystemdExecArgs config.process.argv + " --systemd-unit %n";This pattern allows you to pass the unit name (or other systemd specifiers) as additional arguments while keeping the base command from
process.argvproperly escaped.See systemd.service(5) (section “COMMAND LINES”) for details on variable substitution and systemd.unit(5) (section “SPECIFIERS”) for available specifiers like
%n,%i,%t.Type: string
Default:
config.systemd.lib.escapeSystemdExecArgs config.process.argvDeclared by:
integrations/nixos/systemd/service.nix -
systemd.service -
Alias of
systemd.services."".Type: submodule
Declared by:
integrations/nixos/systemd/service.nix -
systemd.services -
This module configures systemd services, with the notable difference that their unit names will be prefixed with the abstract service name.
This option’s value is not suitable for reading, but you can define a module here that interacts with just the unit configuration in the host system configuration.
Note that this option contains deferred modules. This means that the module has not been combined with the system configuration yet, no values can be read from this option. What you can do instead is define a module that reads from the module arguments (such as
config) that are available when the module is merged into the system configuration.Type: lazy attribute set of module
Default:
{ }Declared by:
integrations/nixos/systemd/service.nix -
systemd.socket -
Alias of
systemd.sockets."".Type: submodule
Declared by:
integrations/nixos/systemd/service.nix -
systemd.sockets -
Declares systemd socket units. Names will be prefixed by the service name / path.
See
systemd.services.Type: lazy attribute set of module
Default:
{ }Declared by:
integrations/nixos/systemd/service.nix
Writing and Reviewing Modular Services
Status
Modular Services are, as of writing, a new feature with support in NixOS. It is in development, and be considerate of the fact that the intermediate outcome of RFC 163 is that we should try a module-based approach to portable services; it is not yet a widely agreed upon solution.
Relation to NixOS Modules
A modular service is not a replacement for a NixOS module, but may be in the future.
Using a modular service to implement a NixOS module is an expected use case, but exposes the NixOS module to a degree of uncertainty that is not acceptable for widely used modules yet.
Maintainership
If you contribute a modular service, you must mark yourself as maintainer of the modular service.
The maintainership of a modular service does not need to be the same as the maintainership of a NixOS module.
If you are not a maintainer of the NixOS module, you should offer to join the NixOS module’s meta.maintainers team, so that you are included in reviews and discussions, most of which also affect the modular service.
The NixOS module maintainers have no obligation towards the modular service, except perhaps to notify you if they notice that the modular service breaks.
Minimum Standard
Modular services MUST be accompanied by a VM test that exercises the modular service, in at least one integration under integrations/.
Modular services MUST have a meta.maintainers module attribute that lists the maintainers of the modular service.
Reviewing Modular Services
When reviewing a modular service, you should check the following. Details and rationale are provided below.
- [ ] Has a VM test, in every integration it claims to support (at minimum `integrations/nixos/tests/packages/`)
- [ ] Registered in that integration's `tests/default.nix`
- [ ] Has a `meta.maintainers` attribute
- [ ] Systemd-specific definitions live in a NixOS variant, not in the service itself, to promote portability.
- [ ] `_class = "service"`
- [ ] Imports nothing from `lib/services`, so that it stays integration-agnostic
- [ ] Has an entry in `modular-services/default.nix` whose `<ns>.package` default comes from the providing package
- [ ] Is the modular services infrastructure sufficient for this service? If one or more features are not covered, comment in https://github.com/NixOS/nixpkgs/issues/428084
- [ ] Has been added to `doc/registry.nix` (enforced by `checks.docs-registry-complete`)
Details
VM test
For NixOS, add the test to integrations/nixos/tests/packages/ and register it in integrations/nixos/tests/default.nix; the surrounding tests there are worked examples.
A test file imports config.modularServices.<pkg>.<svc> into the service, so that it exercises the same variant a NixOS configuration gets.
Best practices: keep tests minimal and focused (boot a VM, enable the service, and assert a basic request succeeds). For general guidance, see the NixOS Tests chapter.
Integration-specific definitions
A service module under modular-services/ declares what the service is: its options, and process.argv.
Anything that only one service manager understands – unit dependencies, serviceConfig, credentials – belongs to a variant of that service, in the integration that understands it.
For NixOS that is integrations/nixos/modular/<pkg>/<svc>/, a pair of files: default.nix imports the pure module out of modularServices.<pkg>, and system.nix adds the systemd definitions.
Register the pair in integrations/nixos/modular/default.nix, as a path rather than an import, so that the variant keeps its own file attribution; checks.nixos-modular-variants asserts that.
Keeping the two apart is what lets a service be loaded into a configuration manager that has no systemd option tree at all.
A service that must vary its own definitions per manager can still do so inline, with lib.optionalAttrs (options ? systemd); see Portability.
_class = "service"
A _class declaration ensures a clear error when the module is accidentally imported into a configuration that isn’t a modular service, such as a NixOS configuration.
Provide it as the first attribute in the module:
# Non-module dependencies (`importApply`)
{ writeScript, runtimeShell }:
# Service module
{ lib, config, ... }:
{
_class = "service";
options = {
# ...
};
config = {
# ...
};
}
Overriding the package default
The package option of a service must default to the package that provides the service. Otherwise, since some packages are defined by an override, the modular service would launch a wrong package, if it builds at all.
In this repository a service module is registered in modular-services/default.nix, which supplies both the importApply of its non-module dependencies and that default:
{
example =
pkgs:
{
imports = [ (importApply ./example/service.nix { inherit (pkgs) formats; }) ];
example.package = lib.mkDefault pkgs.example;
};
}
lib.mkDefault is used throughout, rather than the unpriorised definition nixpkgs uses in passthru, so that a configuration can set the package without lib.mkForce.
In nixpkgs the equivalent lives in the package’s passthru.services, and must use finalAttrs.finalPackage so that overrides propagate.
That form is still what a service upstreamed into nixpkgs needs.
If it is not possible, or if the module is not represented by a single package, consider exposing the modular service directly by file path only.
Flake attributes
Everything this repository offers, flake or no flake. flake.nix adds no
output of its own: it pins nixpkgs through flake.lock and keys the
per-system outputs by system, so calling default.nix directly gives the same
surface.
This chapter is generated from those outputs, and
checks.docs-outputs-complete asserts that every one of them is described
here.
Whatever the system
A flake publishes these unkeyed. None of them evaluates a package set, so a configuration that imports one pulls in no second nixpkgs.
| attribute | what |
|---|---|
lib.mkComplianceSuite | The integration-agnostic compliance suite, for a package set. |
lib.services | lib.servicesFor against the lib this repository was called with. |
lib.servicesFor | The portable layer against a caller-supplied lib, and the entry point for a new integration. |
modularServices.<pkg> | modularServices.<pkg> pkgs yields the service itself, naming no service manager. A NixOS configuration imports config.modularServices.<pkg>.<svc> instead, which is this plus the systemd definitions the integration adds. pkgs.<pkg>.services.* still resolves to the nixpkgs copy unless overlays.passthruServices is applied. Listed under Service modules. |
nixosModules.default | This repository’s implementation, plus the disable of the nixpkgs copy. The one to import. |
nixosModules.disableUpstream | Just the disable, without the implementation. |
nixosModules.documentation | Replacement for the option-documentation registry that the disable removes. |
nixosModules.systemServices | Just the systemd implementation, without the disable. |
overlays.default | Adds pkgs.modularServices.*. Overrides nothing, so no rebuilds. |
overlays.passthruServices | Opt-in: repoints pkgs.<pkg>.services.* at this repository. Excludes php, which regenerates its own passthru. |
For one system
These are produced for the system default.nix was called with, which is what
a flake keys as checks.<system>, packages.<system> and so on.
| attribute | what |
|---|---|
checks.<name> | Every test, integration and repo-level alike. kind splits the CI matrix: an eval check builds on any runner, a vm check needs /dev/kvm. integration is the directory under integrations/ the check came from, or repo for the checks that belong to no single one. Listed under Checks. |
ci.matrix | The GitHub Actions job matrix. Consumed only by the workflow. |
devShells.default | nixfmt-tree and jq. |
formatter | nixfmt-tree, so nix fmt formats the tree. |
packages.docs | This manual. |
Service modules
modularServices.<pkg> pkgs yields the service itself, naming no service manager. A NixOS configuration imports config.modularServices.<pkg>.<svc> instead, which is this plus the systemd definitions the integration adds. pkgs.<pkg>.services.* still resolves to the nixpkgs copy unless overlays.passthruServices is applied.
modularServices.autopush-rs-autoconnectmodularServices.autopush-rs-autoendpointmodularServices.easytiermodularServices.ghostunnelmodularServices.git-pagesmodularServices.holo-daemonmodularServices.ktls-utilsmodularServices.phpmodularServices.snid
Checks
Every test, integration and repo-level alike. kind splits the CI matrix: an eval check builds on any runner, a vm check needs /dev/kvm. integration is the directory under integrations/ the check came from, or repo for the checks that belong to no single one.
| check | kind | integration |
|---|---|---|
disable-keys-exist | eval | repo |
docs | eval | repo |
docs-outputs-complete | eval | repo |
docs-registry-complete | eval | repo |
lib-eval | eval | repo |
nixos-compliance-basic-argv | vm | nixos |
nixos-compliance-eval | eval | nixos |
nixos-compliance-reload | vm | nixos |
nixos-compliance-sub-services | vm | nixos |
nixos-compliance-systemd-eval | eval | nixos |
nixos-disable-proof | eval | nixos |
nixos-etc | vm | nixos |
nixos-modular-variants | eval | nixos |
nixos-pkg-autopush-rs | vm | nixos |
nixos-pkg-easytier | vm | nixos |
nixos-pkg-ghostunnel | vm | nixos |
nixos-pkg-git-pages | vm | nixos |
nixos-pkg-holo-daemon | vm | nixos |
nixos-pkg-php-fpm | vm | nixos |
nixos-pkg-snid | vm | nixos |
nixos-pkg-tlshd | vm | nixos |
nixos-units | eval | nixos |
non-flake-consumer | eval | repo |
The Portable Layer
lib.services.importService
The portable service base, service.nix, with its non-module dependency
applied: importService { inherit pkgs; } is a module. configure loads it
by default; pass it, or a copy pinned from another revision, as
baseModules to choose the base explicitly.
lib.services.importService :: { pkgs :: AttrSet } -> Module
lib.services.configure
Entrypoint for integrating modular services into a containing module system.
Each containing system (NixOS, …) calls configure to
obtain a serviceSubmodule type for its services option. The returned submodule
includes the portable service base and any service-manager-specific modules
passed via extraRootModules.
Implementing for a new integration (e.g. home-manager, nix-darwin):
An integration lives in integrations/<name>/ and provides exactly four
files. See integrations/README.md for the full contract; this docstring
covers the one that calls configure.
integrations/<name>/default.nix is the module that a user of that
configuration system imports. It declares the services option in terms of
configure, and translates the resulting service tree into whatever the
integration’s service manager consumes:
# integrations/darwin/default.nix
{ lib, config, pkgs, ... }:
let
portable-lib = import ../../lib/services { inherit lib; };
modularServiceConfiguration = portable-lib.configure {
serviceManagerPkgs = pkgs;
# To load a different portable service base, set `baseModules` instead:
# baseModules = [ (portable-lib.importService { inherit pkgs; }) ];
extraRootModules = [
./launchd/service.nix # launchd-specific options (plist generation, etc.)
];
};
in
{
_class = "darwin";
imports = [ ./disable-upstream.nix ];
options.services = lib.mkOption {
type = lib.types.attrsOf modularServiceConfiguration.serviceSubmodule;
default = { };
};
config = {
# Convert service tree -> launchd plists, assertions, etc.
# (analogous to how NixOS converts to systemd units)
launchd.agents = ...;
assertions = ...;
warnings = ...;
};
}
The remaining three files are disable-upstream.nix (eval-time removal of
that configuration system’s in-tree copy, if it has one), lib.nix
({ evalSystem, runTest, ... }: how to evaluate and test there), and
tests/default.nix ({ <name> = { kind = "eval" | "vm"; drv; }; }).
ci/tests.nix discovers integrations/ from the filesystem, so an
integration that honours the contract is picked up by checks,
nix flake check and CI with no further wiring.
lib.services.configure :: AttrSet -> { serviceSubmodule :: SubmoduleType }
Inputs
serviceManagerPkgs1. A Nixpkgs instance used for built-in logic such as converting
configData.<path>.textto a store path. Required unlessbaseModulesis set.baseModules2. Modules that replace the portable service base. They are loaded into the “root” service submodule and must handle propagation to sub-
servicesthemselves. Defaults to this repository’s portable service base,importService { pkgs = serviceManagerPkgs; }. Set it to supply a different one, for example aservice.nixpinned from another revision.extraRootModules3. Modules to be loaded into the “root” service submodule, but not into its sub-
services. That’s the modules’ own responsibility. Typically contains service-manager-specific option modules (e.g. systemd unit options, launchd plist options).extraRootSpecialArgs4. Fixed module arguments provided alongside
extraRootModules.
Output
An attribute set.
serviceSubmodule: a Module System option type which is a submodule with the portable modules and this function’s inputs loaded into it.