···11+22+# Modular Services
33+44+This directory defines a modular service infrastructure for NixOS.
55+See the [Modular Services chapter] in the manual [[source]](../../doc/manual/development/modular-services.md).
66+77+[Modular Services chapter]: https://nixos.org/manual/nixos/unstable/#modular-services
88+99+# Design decision log
1010+1111+- `system.services.<name>`. Alternatives considered
1212+ - `systemServices`: similar to does not allow importing a composition of services into `system`. Not sure if that's a good idea in the first place, but I've kept the possibility open.
1313+ - `services.abstract`: used in https://github.com/NixOS/nixpkgs/pull/267111, but too weird. Service modules should fit naturally into the configuration system.
1414+ Also "abstract" is wrong, because it has submodules - in other words, evalModules results, concrete services - not abstract at all.
1515+ - `services.modular`: only slightly better than `services.abstract`, but still weird
1616+1717+- No `daemon.*` options. https://github.com/NixOS/nixpkgs/pull/267111/files#r1723206521
1818+1919+- For now, do not add an `enable` option, because it's ambiguous. Does it disable at the Nix level (not generate anything) or at the systemd level (generate a service that is disabled)?
2020+2121+- Move all process options into a `process` option tree. Putting this at the root is messy, because we also have sub-services at that level. Those are rather distinct. Grouping them "by kind" should raise fewer questions.
2222+2323+- `modules/system/service/systemd/system.nix` has `system` twice. Not great, but
2424+ - they have different meanings
2525+ 1. These are system-provided modules, provided by the configuration manager
2626+ 2. `systemd/system` configures SystemD _system units_.
2727+ - This reserves `modules/service` for actual service modules, at least until those are lifted out of NixOS, potentially
2828+
+58
nixos/modules/system/service/portable/service.nix
···11+{
22+ lib,
33+ config,
44+ options,
55+ ...
66+}:
77+let
88+ inherit (lib) mkOption types;
99+ pathOrStr = types.coercedTo types.path (x: "${x}") types.str;
1010+ program =
1111+ types.coercedTo (
1212+ types.package
1313+ // {
1414+ # require mainProgram for this conversion
1515+ check = v: v.type or null == "derivation" && v ? meta.mainProgram;
1616+ }
1717+ ) lib.getExe pathOrStr
1818+ // {
1919+ description = "main program, path or command";
2020+ descriptionClass = "conjunction";
2121+ };
2222+in
2323+{
2424+ options = {
2525+ services = mkOption {
2626+ type = types.attrsOf (
2727+ types.submoduleWith {
2828+ modules = [
2929+ ./service.nix
3030+ ];
3131+ }
3232+ );
3333+ description = ''
3434+ A collection of [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) that are configured in one go.
3535+3636+ You could consider the sub-service relationship to be an ownership relation.
3737+ 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.
3838+ '';
3939+ default = { };
4040+ visible = "shallow";
4141+ };
4242+ process = {
4343+ executable = mkOption {
4444+ type = program;
4545+ description = ''
4646+ The path to the executable that will be run when the service is started.
4747+ '';
4848+ };
4949+ args = lib.mkOption {
5050+ type = types.listOf pathOrStr;
5151+ description = ''
5252+ Arguments to pass to the `executable`.
5353+ '';
5454+ default = [ ];
5555+ };
5656+ };
5757+ };
5858+}
···11+{
22+ lib,
33+ config,
44+ systemdPackage,
55+ ...
66+}:
77+let
88+ inherit (lib) mkOption types;
99+in
1010+{
1111+ imports = [
1212+ ../portable/service.nix
1313+ (lib.mkAliasOptionModule [ "systemd" "service" ] [ "systemd" "services" "" ])
1414+ (lib.mkAliasOptionModule [ "systemd" "socket" ] [ "systemd" "sockets" "" ])
1515+ ];
1616+ options = {
1717+ systemd.services = mkOption {
1818+ description = ''
1919+ This module configures systemd services, with the notable difference that their unit names will be prefixed with the abstract service name.
2020+2121+ 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.
2222+2323+ Note that this option contains _deferred_ modules.
2424+ This means that the module has not been combined with the system configuration yet, no values can be read from this option.
2525+ 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.
2626+ '';
2727+ type = types.lazyAttrsOf (
2828+ types.deferredModuleWith {
2929+ staticModules = [
3030+ # TODO: Add modules for the purpose of generating documentation?
3131+ ];
3232+ }
3333+ );
3434+ default = { };
3535+ };
3636+ systemd.sockets = mkOption {
3737+ description = ''
3838+ Declares systemd socket units. Names will be prefixed by the service name / path.
3939+4040+ See {option}`systemd.services`.
4141+ '';
4242+ type = types.lazyAttrsOf types.deferredModule;
4343+ default = { };
4444+ };
4545+4646+ # Also import systemd logic into sub-services
4747+ # extends the portable `services` option
4848+ services = mkOption {
4949+ type = types.attrsOf (
5050+ types.submoduleWith {
5151+ class = "service";
5252+ modules = [
5353+ ./service.nix
5454+ ];
5555+ specialArgs = {
5656+ inherit systemdPackage;
5757+ };
5858+ }
5959+ );
6060+ };
6161+ };
6262+ config = {
6363+ # Note that this is the systemd.services option above, not the system one.
6464+ systemd.services."" = {
6565+ # TODO description;
6666+ wantedBy = lib.mkDefault [ "multi-user.target" ];
6767+ serviceConfig = {
6868+ Type = lib.mkDefault "simple";
6969+ Restart = lib.mkDefault "always";
7070+ RestartSec = lib.mkDefault "5";
7171+ ExecStart = [
7272+ (systemdPackage.functions.escapeSystemdExecArgs (
7373+ [ config.process.executable ] ++ config.process.args
7474+ ))
7575+ ];
7676+ };
7777+ };
7878+ };
7979+}
+68
nixos/modules/system/service/systemd/system.nix
···11+{
22+ lib,
33+ config,
44+ pkgs,
55+ ...
66+}:
77+88+let
99+ inherit (lib) concatMapAttrs mkOption types;
1010+1111+ dash =
1212+ before: after:
1313+ if after == "" then
1414+ before
1515+ else if before == "" then
1616+ after
1717+ else
1818+ "${before}-${after}";
1919+2020+ makeUnits =
2121+ unitType: prefix: service:
2222+ concatMapAttrs (unitName: unitModule: {
2323+ "${dash prefix unitName}" =
2424+ { ... }:
2525+ {
2626+ imports = [ unitModule ];
2727+ };
2828+ }) service.systemd.${unitType}
2929+ // concatMapAttrs (
3030+ subServiceName: subService: makeUnits unitType (dash prefix subServiceName) subService
3131+ ) service.services;
3232+in
3333+{
3434+ # First half of the magic: mix systemd logic into the otherwise abstract services
3535+ options = {
3636+ system.services = mkOption {
3737+ description = ''
3838+ A collection of NixOS [modular services](https://nixos.org/manual/nixos/unstable/#modular-services) that are configured as systemd services.
3939+ '';
4040+ type = types.attrsOf (
4141+ types.submoduleWith {
4242+ class = "service";
4343+ modules = [
4444+ ./service.nix
4545+ ];
4646+ specialArgs = {
4747+ # perhaps: features."systemd" = { };
4848+ inherit pkgs;
4949+ systemdPackage = config.systemd.package;
5050+ };
5151+ }
5252+ );
5353+ default = { };
5454+ visible = "shallow";
5555+ };
5656+ };
5757+5858+ # Second half of the magic: siphon units that were defined in isolation to the system
5959+ config = {
6060+ systemd.services = concatMapAttrs (
6161+ serviceName: topLevelService: makeUnits "services" serviceName topLevelService
6262+ ) config.system.services;
6363+6464+ systemd.sockets = concatMapAttrs (
6565+ serviceName: topLevelService: makeUnits "sockets" serviceName topLevelService
6666+ ) config.system.services;
6767+ };
6868+}