1 2# Modular Services 3 4This directory defines a modular service infrastructure for NixOS. 5See the [Modular Services chapter] in the manual [[source]](../../doc/manual/development/modular-services.md). 6 7[Modular Services chapter]: https://nixos.org/manual/nixos/unstable/#modular-services 8 9# Design decision log 10 11## Initial design 12 13- `system.services.<name>`. Alternatives considered 14 - `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. 15 - `services.abstract`: used in https://github.com/NixOS/nixpkgs/pull/267111, but too weird. Service modules should fit naturally into the configuration system. 16 Also "abstract" is wrong, because it has submodules - in other words, evalModules results, concrete services - not abstract at all. 17 - `services.modular`: only slightly better than `services.abstract`, but still weird 18 19- No `daemon.*` options. https://github.com/NixOS/nixpkgs/pull/267111/files#r1723206521 20 21- 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)? 22 23- 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. 24 25- `modules/system/service/systemd/system.nix` has `system` twice. Not great, but 26 - they have different meanings 27 1. These are system-provided modules, provided by the configuration manager 28 2. `systemd/system` configures SystemD _system units_. 29 - This reserves `modules/service` for actual service modules, at least until those are lifted out of NixOS, potentially 30 31## Configuration Data (`configData`) Design 32 33Without a mechanism for adding files, all configuration had to go through `process.*`, requiring process restarts even when those would have been avoidable. 34Many services implement automatic reloading or reloading on e.g. `SIGUSR1`, but those mechanisms need files to read. `configData` provides such files. 35 36### Naming and Terminology 37 38- **`configData` instead of `environment.etc`**: The name `configData` is service manager agnostic. While systemd system services can use `/etc`, other service managers may expose configuration data differently (e.g., different directory, relative paths). 39 40- **`path` attribute**: Each `configData` entry automatically gets a `path` attribute set by the service manager implementation, allowing services to reference the location of their configuration files. These paths themselves are not subject to change from generation to generation; only their contents are. 41 42- **`name` attribute**: In `environment.etc` this would be `target` but that's confusing, especially for symlinks, as it's not the symlink's target. 43 44### Service Manager Integration 45 46- **Portable base**: The `configData` interface is declared in `portable/config-data.nix`, making it available to all service manager implementations. 47 48- **Systemd integration**: The systemd implementation (`systemd/system.nix`) maps `configData` entries to `environment.etc` entries under `/etc/system-services/`. 49 50- **Path computation**: `systemd/config-data-path.nix` recursively computes unique paths for services and sub-services (e.g., `/etc/system-services/webserver/` vs `/etc/system-services/webserver-api/`). 51 Fun fact: for the module system it is a completely normal module, despite its recursive definition. 52 If we parameterize `/etc/system-services`, it will have to become an `importApply` style module nonetheless (function returning module). 53 54- **Simple attribute structure**: Unlike `environment.etc`, `configData` uses a simpler structure with just `enable`, `name`, `text`, `source`, and `path` attributes. Complex ownership options were omitted for simplicity and portability. 55 Per-service user creation is still TBD. 56 57## No `pkgs` module argument 58 59The modular service infrastructure avoids exposing `pkgs` as a module argument to service modules. Instead, derivations and builder functions are provided through lexical closure, making dependency relationships explicit and avoiding uncertainty about where dependencies come from. 60 61### Benefits 62 63- **Explicit dependencies**: Services declare what they need rather than implicitly depending on `pkgs` 64- **No interference**: Service modules can be reused in different contexts without assuming a specific `pkgs` instance. An unexpected `pkgs` version is not a failure mode anymore. 65- **Clarity**: With fewer ways to do things, there's no ambiguity about where dependencies come from (from the module, not the OS or service manager) 66 67### Implementation 68 69- **Portable layer**: Service modules in `portable/` do not receive `pkgs` as a module argument. Any required derivations must be provided by the caller. 70 71- **Systemd integration**: The `systemd/system.nix` module imports `config-data.nix` as a function, providing `pkgs` in lexical closure: 72 ```nix 73 (import ../portable/config-data.nix { inherit pkgs; }) 74 ``` 75 76- **Service modules**: 77 1. Should explicitly declare their package dependencies as options rather than using `pkgs` defaults: 78 ```nix 79 { 80 # Bad: uses pkgs module argument 81 foo.package = mkOption { 82 default = pkgs.python3; 83 # ... 84 }; 85 } 86 ``` 87 88 ```nix 89 { 90 # Good: caller provides the package 91 foo.package = mkOption { 92 type = types.package; 93 description = "Python package to use"; 94 defaultText = lib.literalMD "The package that provided this module."; 95 }; 96 } 97 ``` 98 99 2. `passthru.services` can still provide a complete module using the package's lexical scope, making the module truly self-contained: 100 101 **Package (`package.nix`):** 102 ```nix 103 { 104 lib, 105 writeScript, 106 runtimeShell, 107 # ... other dependencies 108 }: 109 stdenv.mkDerivation (finalAttrs: { 110 # ... package definition 111 112 passthru.services.default = { 113 imports = [ 114 (lib.modules.importApply ./service.nix { 115 inherit writeScript runtimeShell; 116 }) 117 ]; 118 someService.package = finalAttrs.finalPackage; 119 }; 120 }) 121 ``` 122 123 **Service module (`service.nix`):** 124 ```nix 125 # Non-module dependencies (importApply) 126 { writeScript, runtimeShell }: 127 128 # Service module 129 { 130 lib, 131 config, 132 options, 133 ... 134 }: 135 { 136 # Service definition using writeScript, runtimeShell from lexical scope 137 process.argv = [ 138 (writeScript "wrapper" '' 139 #!${runtimeShell} 140 # ... wrapper logic 141 '') 142 # ... other args 143 ]; 144 } 145 ```