<commit_before>{ config, pkgs, lib, ... }:

let
  toplevelConfig = config;
  inherit (lib) types;
  inherit (import ../system/boot/systemd-lib.nix {
    inherit config pkgs lib;
  }) mkPathSafeName;
in {
  options.systemd.services = lib.mkOption {
    type = types.attrsOf (types.submodule ({ name, config, ... }: {
      options.confinement.enable = lib.mkOption {
        type = types.bool;
        default = false;
        description = ''
          If set, all the required runtime store paths for this service are
          bind-mounted into a <literal>tmpfs</literal>-based <citerefentry>
            <refentrytitle>chroot</refentrytitle>
            <manvolnum>2</manvolnum>
          </citerefentry>.
        '';
      };

      options.confinement.fullUnit = lib.mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to include the full closure of the systemd unit file into the
          chroot, instead of just the dependencies for the executables.

          <warning><para>While it may be tempting to just enable this option to
          make things work quickly, please be aware that this might add paths
          to the closure of the chroot that you didn't anticipate. It's better
          to use <option>confinement.packages</option> to <emphasis
          role="strong">explicitly</emphasis> add additional store paths to the
          chroot.</para></warning>
        '';
      };

      options.confinement.packages = lib.mkOption {
        type = types.listOf (types.either types.str types.package);
        default = [];
        description = let
          mkScOption = optName: "<option>serviceConfig.${optName}</option>";
        in ''
          Additional packages or strings with context to add to the closure of
          the chroot. By default, this includes all the packages from the
          ${lib.concatMapStringsSep ", " mkScOption [
            "ExecReload" "ExecStartPost" "ExecStartPre" "ExecStop"
            "ExecStopPost"
          ]} and ${mkScOption "ExecStart"} options. If you want to have all the
          dependencies of this systemd unit, you can use
          <option>confinement.fullUnit</option>.

          <note><para><emphasis role="strong">Only</emphasis> the latter
          (${mkScOption "ExecStart"}) will be used if
          ${mkScOption "RootDirectoryStartOnly"} is enabled.</para></note>

          <note><para>Also, the store paths listed in <option>path</option> are
          <emphasis role="strong">not</emphasis> included in the closure as
          well as paths from other options except those listed
          above.</para></note>
        '';
      };

      options.confinement.binSh = lib.mkOption {
        type = types.nullOr types.path;
        default = toplevelConfig.environment.binsh;
        defaultText = "config.environment.binsh";
        example = lib.literalExample "\${pkgs.dash}/bin/dash";
        description = ''
          The program to make available as <filename>/bin/sh</filename> inside
          the chroot. If this is set to <literal>null</literal>, no
          <filename>/bin/sh</filename> is provided at all.

          This is useful for some applications, which for example use the
          <citerefentry>
            <refentrytitle>system</refentrytitle>
            <manvolnum>3</manvolnum>
          </citerefentry> library function to execute commands.
        '';
      };

      options.confinement.mode = lib.mkOption {
        type = types.enum [ "full-apivfs" "chroot-only" ];
        default = "full-apivfs";
        description = ''
          The value <literal>full-apivfs</literal> (the default) sets up
          private <filename class="directory">/dev</filename>, <filename
          class="directory">/proc</filename>, <filename
          class="directory">/sys</filename> and <filename
          class="directory">/tmp</filename> file systems in a separate user
          name space.

          If this is set to <literal>chroot-only</literal>, only the file
          system name space is set up along with the call to <citerefentry>
            <refentrytitle>chroot</refentrytitle>
            <manvolnum>2</manvolnum>
          </citerefentry>.

          <note><para>This doesn't cover network namespaces and is solely for
          file system level isolation.</para></note>
        '';
      };

      config = let
        rootName = "${mkPathSafeName name}-chroot";
        inherit (config.confinement) binSh fullUnit;
      in lib.mkIf config.confinement.enable {
        serviceConfig = {
          RootDirectory = pkgs.runCommand rootName {} "mkdir \"$out\"";
          TemporaryFileSystem = "/";
          MountFlags = lib.mkDefault "private";
        } // lib.optionalAttrs (config.confinement.mode == "full-apivfs") {
          MountAPIVFS = true;
          PrivateDevices = true;
          PrivateTmp = true;
          PrivateUsers = true;
          ProtectControlGroups = true;
          ProtectKernelModules = true;
          ProtectKernelTunables = true;
        };
        confinement.packages = let
          startOnly = config.serviceConfig.RootDirectoryStartOnly or false;
          execOpts = if startOnly then [ "ExecStart" ] else [
            "ExecReload" "ExecStart" "ExecStartPost" "ExecStartPre" "ExecStop"
            "ExecStopPost"
          ];
          execPkgs = lib.concatMap (opt: let
            isSet = config.serviceConfig ? ${opt};
          in lib.optional isSet config.serviceConfig.${opt}) execOpts;
          unitAttrs = toplevelConfig.systemd.units."${name}.service";
          allPkgs = lib.singleton (builtins.toJSON unitAttrs);<commit_msg>nixos/confinement: Explicitly set serviceConfig<commit_after>{ config, pkgs, lib, ... }:

let
  toplevelConfig = config;
  inherit (lib) types;
  inherit (import ../system/boot/systemd-lib.nix {
    inherit config pkgs lib;
  }) mkPathSafeName;
in {
  options.systemd.services = lib.mkOption {
    type = types.attrsOf (types.submodule ({ name, config, ... }: {
      options.confinement.enable = lib.mkOption {
        type = types.bool;
        default = false;
        description = ''
          If set, all the required runtime store paths for this service are
          bind-mounted into a <literal>tmpfs</literal>-based <citerefentry>
            <refentrytitle>chroot</refentrytitle>
            <manvolnum>2</manvolnum>
          </citerefentry>.
        '';
      };

      options.confinement.fullUnit = lib.mkOption {
        type = types.bool;
        default = false;
        description = ''
          Whether to include the full closure of the systemd unit file into the
          chroot, instead of just the dependencies for the executables.

          <warning><para>While it may be tempting to just enable this option to
          make things work quickly, please be aware that this might add paths
          to the closure of the chroot that you didn't anticipate. It's better
          to use <option>confinement.packages</option> to <emphasis
          role="strong">explicitly</emphasis> add additional store paths to the
          chroot.</para></warning>
        '';
      };

      options.confinement.packages = lib.mkOption {
        type = types.listOf (types.either types.str types.package);
        default = [];
        description = let
          mkScOption = optName: "<option>serviceConfig.${optName}</option>";
        in ''
          Additional packages or strings with context to add to the closure of
          the chroot. By default, this includes all the packages from the
          ${lib.concatMapStringsSep ", " mkScOption [
            "ExecReload" "ExecStartPost" "ExecStartPre" "ExecStop"
            "ExecStopPost"
          ]} and ${mkScOption "ExecStart"} options. If you want to have all the
          dependencies of this systemd unit, you can use
          <option>confinement.fullUnit</option>.

          <note><para><emphasis role="strong">Only</emphasis> the latter
          (${mkScOption "ExecStart"}) will be used if
          ${mkScOption "RootDirectoryStartOnly"} is enabled.</para></note>

          <note><para>Also, the store paths listed in <option>path</option> are
          <emphasis role="strong">not</emphasis> included in the closure as
          well as paths from other options except those listed
          above.</para></note>
        '';
      };

      options.confinement.binSh = lib.mkOption {
        type = types.nullOr types.path;
        default = toplevelConfig.environment.binsh;
        defaultText = "config.environment.binsh";
        example = lib.literalExample "\${pkgs.dash}/bin/dash";
        description = ''
          The program to make available as <filename>/bin/sh</filename> inside
          the chroot. If this is set to <literal>null</literal>, no
          <filename>/bin/sh</filename> is provided at all.

          This is useful for some applications, which for example use the
          <citerefentry>
            <refentrytitle>system</refentrytitle>
            <manvolnum>3</manvolnum>
          </citerefentry> library function to execute commands.
        '';
      };

      options.confinement.mode = lib.mkOption {
        type = types.enum [ "full-apivfs" "chroot-only" ];
        default = "full-apivfs";
        description = ''
          The value <literal>full-apivfs</literal> (the default) sets up
          private <filename class="directory">/dev</filename>, <filename
          class="directory">/proc</filename>, <filename
          class="directory">/sys</filename> and <filename
          class="directory">/tmp</filename> file systems in a separate user
          name space.

          If this is set to <literal>chroot-only</literal>, only the file
          system name space is set up along with the call to <citerefentry>
            <refentrytitle>chroot</refentrytitle>
            <manvolnum>2</manvolnum>
          </citerefentry>.

          <note><para>This doesn't cover network namespaces and is solely for
          file system level isolation.</para></note>
        '';
      };

      config = let
        rootName = "${mkPathSafeName name}-chroot";
        inherit (config.confinement) binSh fullUnit;
        wantsAPIVFS = lib.mkDefault (config.confinement.mode == "full-apivfs");
      in lib.mkIf config.confinement.enable {
        serviceConfig = {
          RootDirectory = pkgs.runCommand rootName {} "mkdir \"$out\"";
          TemporaryFileSystem = "/";
          MountFlags = lib.mkDefault "private";

          # https://github.com/NixOS/nixpkgs/issues/14645 is a future attempt
          # to change some of these to default to true.
          #
          # If we run in chroot-only mode, having something like PrivateDevices
          # set to true by default will mount /dev within the chroot, whereas
          # with "chroot-only" it's expected that there are no /dev, /proc and
          # /sys file systems available.
          #
          # However, if this suddenly becomes true, the attack surface will
          # increase, so let's explicitly set these options to true/false
          # depending on the mode.
          MountAPIVFS = wantsAPIVFS;
          PrivateDevices = wantsAPIVFS;
          PrivateTmp = wantsAPIVFS;
          PrivateUsers = wantsAPIVFS;
          ProtectControlGroups = wantsAPIVFS;
          ProtectKernelModules = wantsAPIVFS;
          ProtectKernelTunables = wantsAPIVFS;
        };
        confinement.packages = let
          startOnly = config.serviceConfig.RootDirectoryStartOnly or false;
          execOpts = if startOnly then [ "ExecStart" ] else [
            "ExecReload" "ExecStart" "ExecStartPost" "ExecStartPre" "ExecStop"
            "ExecStopPost"
          ];
          execPkgs = lib.concatMap (opt: let
            isSet = config.serviceConfig ? ${opt};
          in lib.optional isSet config.serviceConfig.${opt}) execOpts;
          unitAttrs = toplevelConfig.systemd.units."${name}.service";
          allPkgs = lib.singleton (builtins.toJSON unitAttrs);
{'fraction_non_alphanumeric': 0.10182029756462473, 'fraction_numerical': 0.0009143047128252015, 'mean_word_length': 2.2651289009497964, 'pattern_counts': {'":': 0, '<': 139, '<?xml version=': 0, '>': 139, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '520420', 'n_tokens_mistral': 3255, 'n_tokens_neox': 3162, 'n_words': 945}