Home Explore Blog Models CI



nixpkgs

3rd chunk of `nixos/modules/system/service/README.md`
8d294ccb3da62f3c230916e88e019d894b0d28afa558a5540000000100000bf0
- **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.
  Per-service user creation is still TBD.

## No `pkgs` module argument

The 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.

### Benefits

- **Explicit dependencies**: Services declare what they need rather than implicitly depending on `pkgs`
- **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.
- **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)

### Implementation

- **Portable layer**: Service modules in `portable/` do not receive `pkgs` as a module argument. Any required derivations must be provided by the caller.

- **Systemd integration**: The `systemd/system.nix` module imports `config-data.nix` as a function, providing `pkgs` in lexical closure:
  ```nix
  (import ../portable/config-data.nix { inherit pkgs; })
  ```

- **Service modules**:
  1. Should explicitly declare their package dependencies as options rather than using `pkgs` defaults:
    ```nix
    {
      # Bad: uses pkgs module argument
      foo.package = mkOption {
        default = pkgs.python3;
        # ...
      };
    }
    ```

    ```nix
    {
      # Good: caller provides the package
      foo.package = mkOption {
        type = types.package;
        description = "Python package to use";
        defaultText = lib.literalMD "The package that provided this module.";
      };
    }
    ```

  2. `passthru.services` can still provide a complete module using the package's lexical scope, making the module truly self-contained:

    **Package (`package.nix`):**
    ```nix
    {
      lib,
      writeScript,
      runtimeShell,
    # ... other dependencies
    }:
    stdenv.mkDerivation (finalAttrs: {
      # ... package definition

      passthru.services.default = {
        imports = [
          (lib.modules.importApply ./service.nix {
            inherit writeScript runtimeShell;
          })
        ];
        someService.package = finalAttrs.finalPackage;
      };
    })
    ```

    **Service module (`service.nix`):**
    ```nix
    # Non-module dependencies (importApply)
    { writeScript, runtimeShell }:

    # Service module
    {
      lib,
      config,
      options,
      ...
    }:
    {
      # Service definition using writeScript, runtimeShell from lexical scope
      process.argv = [
        (writeScript "wrapper" ''
          #!${runtimeShell}
          # ... wrapper logic
        '')
        # ... other args
      ];
    }
    ```

Title: NixOS Modular Services: Omitting `pkgs` as a Module Argument
Summary
This chunk details the design principle of avoiding the `pkgs` argument in NixOS service modules. It begins with a brief mention of the `configData`'s simple attribute structure. The main focus is on how the modular service infrastructure provides derivations and builder functions via lexical closure instead of `pkgs`, leading to explicit dependencies, preventing interference, and enhancing clarity. The implementation section explains that portable modules don't receive `pkgs`, while Systemd integration provides `pkgs` in lexical closure. It also demonstrates how service modules should explicitly declare package dependencies as options and how `passthru.services` can provide self-contained modules by passing non-module dependencies through `importApply` and lexical scope.