Home Explore Blog Models CI



nixpkgs

3rd chunk of `nixos/doc/manual/development/writing-modules.chapter.md`
d525fef07b5292bb69b0411b8c6b3a16c414530c53ba2da70000000100000cc2
Care must be taken when writing systemd services using `Exec*` directives. By
default systemd performs substitution on `%<char>` specifiers in these
directives, expands environment variables from `$FOO` and `${FOO}`, splits
arguments on whitespace, and splits commands on `;`. All of these must be escaped
to avoid unexpected substitution or splitting when interpolating into an `Exec*`
directive, e.g. when using an `extraArgs` option to pass additional arguments to
the service. The functions `utils.escapeSystemdExecArg` and
`utils.escapeSystemdExecArgs` are provided for this, see [Example: Escaping in
Exec directives](#exec-escaping-example) for an example. When using these
functions system environment substitution should *not* be disabled explicitly.

::: {#locate-example .example}
### NixOS Module for the "locate" Service
```nix
{
  config,
  lib,
  pkgs,
  ...
}:

let
  inherit (lib)
    concatStringsSep
    mkIf
    mkOption
    optionalString
    types
    ;
  cfg = config.services.locate;
in
{
  options.services.locate = {
    enable = mkOption {
      type = types.bool;
      default = false;
      description = ''
        If enabled, NixOS will periodically update the database of
        files used by the locate command.
      '';
    };

    interval = mkOption {
      type = types.str;
      default = "02:15";
      example = "hourly";
      description = ''
        Update the locate database at this interval. Updates by
        default at 2:15 AM every day.

        The format is described in
        systemd.time(7).
      '';
    };

    # Other options omitted for documentation
  };

  config = {
    systemd.services.update-locatedb = {
      description = "Update Locate Database";
      path = [ pkgs.su ];
      script = ''
        mkdir -p $(dirname ${toString cfg.output})
        chmod 0755 $(dirname ${toString cfg.output})
        exec updatedb \
          --localuser=${cfg.localuser} \
          ${optionalString (!cfg.includeStore) "--prunepaths='/nix/store'"} \
          --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
      '';
    };

    systemd.timers.update-locatedb = mkIf cfg.enable {
      description = "Update timer for locate database";
      partOf = [ "update-locatedb.service" ];
      wantedBy = [ "timers.target" ];
      timerConfig.OnCalendar = cfg.interval;
    };
  };
}
```
:::

::: {#exec-escaping-example .example}
### Escaping in Exec directives
```nix
{
  config,
  pkgs,
  utils,
  ...
}:

let
  cfg = config.services.echo;
  echoAll = pkgs.writeScript "echo-all" ''
    #! ${pkgs.runtimeShell}
    for s in "$@"; do
      printf '%s\n' "$s"
    done
  '';
  args = [
    "a%Nything"
    "lang=\${LANG}"
    ";"
    "/bin/sh -c date"
  ];
in
{
  systemd.services.echo = {
    description = "Echo to the journal";
    wantedBy = [ "multi-user.target" ];
    serviceConfig.Type = "oneshot";
    serviceConfig.ExecStart = ''
      ${echoAll} ${utils.escapeSystemdExecArgs args}
    '';
  };
}
```
:::

```{=include=} sections
option-declarations.section.md
option-types.section.md
option-def.section.md
assertions.section.md
meta-attributes.section.md
importing-modules.section.md
replace-modules.section.md
freeform-modules.section.md
settings-options.section.md
```

Title: NixOS Module Examples: Systemd Service Configuration and Exec Directive Escaping
Summary
This section details the critical need for escaping `Exec*` directives in systemd services within NixOS modules to prevent unintended substitutions of special characters, environment variables, and argument splitting. The utility functions `utils.escapeSystemdExecArg` and `utils.escapeSystemdExecArgs` are introduced as solutions. An example NixOS module for the 'locate' service is provided, illustrating how to declare specific options (`enable`, `interval`) and use them to define a `systemd.service` and `systemd.timer`. A further example explicitly demonstrates the application of `utils.escapeSystemdExecArgs` for safely passing arguments that contain special characters to an `ExecStart` directive.