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

with lib;

let

  cfg = config.services.privoxy;

  serialise = name: val:
         if isList val then concatMapStrings (serialise name) val
    else if isBool val then serialise name (if val then "1" else "0")
    else "${name} ${toString val}\n";

  configType = with types;
    let atom = oneOf [ int bool string path ];
    in attrsOf (either atom (listOf atom))
    // { description = ''
          privoxy configuration type. The format consists of an attribute
          set of settings. Each setting can be either a value (integer, string,
          boolean or path) or a list of such values.
        '';
       };

  ageType = types.str // {
    check = x:
      isString x &&
      (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null);
    description = "tmpfiles.d(5) age format";
  };

  configFile = pkgs.writeText "privoxy.conf"
    (concatStrings (
      # Relative paths in some options are relative to confdir. Privoxy seems
      # to parse the options in order of appearance, so this must come first.
      # Nix however doesn't preserve the order in attrsets, so we have to
      # hardcode confdir here.
      [ "confdir ${pkgs.privoxy}/etc\n" ]
      ++ mapAttrsToList serialise cfg.settings
    ));

  inspectAction = pkgs.writeText "inspect-all-https.action"
    ''
      # Enable HTTPS inspection for all requests
      {+https-inspection}
      /
    '';

in

{

  ###### interface

  options.services.privoxy = {

    enable = mkEnableOption "Privoxy, non-caching filtering proxy";

    enableTor = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether to configure Privoxy to use Tor's faster SOCKS port,
        suitable for HTTP.
      '';
    };

    inspectHttps = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether to configure Privoxy to inspect HTTPS requests, meaning all
        encrypted traffic will be filtered as well. This works by decrypting
        and re-encrypting the requests using a per-domain generated certificate.

        To issue per-domain certificates, Privoxy must be provided with a CA
        certificate, using the <literal>ca-cert-file</literal>,
        <literal>ca-key-file</literal> settings.

        <warning><para>
          The CA certificate must also be added to the system trust roots,
          otherwise browsers will reject all Privoxy certificates as invalid.
          You can do so by using the option
          <option>security.pki.certificateFiles</option>.
        </para></warning>
      '';
    };

    certsLifetime = mkOption {
      type = ageType;
      default = "10d";
      example = "12h";
      description = ''
        If <literal>inspectHttps</literal> is enabled, the time generated HTTPS
        certificates will be stored in a temporary directory for reuse. Once
        the lifetime has expired the directory will cleared and the certificate
        will have to be generated again, on-demand.

        Depending on the traffic, you may want to reduce the lifetime to limit
        the disk usage, since Privoxy itself never deletes the certificates.

        <note><para>The format is that of the <literal>tmpfiles.d(5)</literal>
        Age parameter.</para></note>
      '';
    };

    userActions = mkOption {
      type = types.lines;
      default = "";
      description = ''
        Actions to be included in a <literal>user.action</literal> file. This
        will have a higher priority and can be used to override all other
        actions.
      '';
    };

    userFilters = mkOption {
      type = types.lines;
      default = "";
      description = ''
        Filters to be included in a <literal>user.filter</literal> file. This
        will have a higher priority and can be used to override all other
        filters definitions.
      '';
    };

    settings = mkOption {
      type = types.submodule {
        freeformType = configType;

        options.listen-address = mkOption {
          type = types.str;
          default = "127.0.0.1:8118";
          description = "Pair of address:port the proxy server is listening to.";
        };

        options.enable-edit-actions = mkOption {
          type = types.bool;
          default = false;
          description = "Whether the web-based actions file editor may be used.";
        };

        options.actionsfile = mkOption {
          type = types.listOf types.str;
          # This must come after all other entries, in order to override the
          # other actions/filters installed by Privoxy or the user.
          apply = x: x ++ optional (cfg.userActions != "")
            (toString (pkgs.writeText "user.actions" cfg.userActions));
          default = [ "match-all.action" "default.action" ];
          description = ''
            List of paths to Privoxy action files. These paths may either be
            absolute or relative to the privoxy configuration directory.
          '';
        };

        options.filterfile = mkOption {
          type = types.listOf types.str;
          default = [ "default.filter" ];
          apply = x: x ++ optional (cfg.userFilters != "")
            (toString (pkgs.writeText "user.filter" cfg.userFilters));
          description = ''
            List of paths to Privoxy filter files. These paths may either be
            absolute or relative to the privoxy configuration directory.
          '';
        };
      };
      default = {};
      example = literalExample ''
        { listen-address = "[::]:8118";               # listen on IPv6 only
          forward-socks5 = ".onion localhost:9050 ."; # forward .onion requests to Tor
        }
      '';
      description = ''
        This option is mapped to the main Privoxy configuration file.
        Check out the Privoxy user manual at
        <link xlink:href="file://${pkgs.privoxy}/share/doc/privoxy/user-manual/config.html"/>
        for available settings and documentation.
      '';
    };

  };

  ###### implementation

  config = mkIf cfg.enable {

    users.users.privoxy = {
      description = "Privoxy daemon user";
      isSystemUser = true;
      group = "privoxy";
    };

    users.groups.privoxy = {};

    systemd.tmpfiles.rules = with cfg.settings; [
      "d ${certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}"
    ];

    systemd.services.privoxy = {
      description = "Filtering web proxy";
      after = [ "network.target" "nss-lookup.target" ];<commit_msg>nixos/privoxy: document repeated settings<commit_after>{ config, lib, pkgs, ... }:

with lib;

let

  cfg = config.services.privoxy;

  serialise = name: val:
         if isList val then concatMapStrings (serialise name) val
    else if isBool val then serialise name (if val then "1" else "0")
    else "${name} ${toString val}\n";

  configType = with types;
    let atom = oneOf [ int bool string path ];
    in attrsOf (either atom (listOf atom))
    // { description = ''
          privoxy configuration type. The format consists of an attribute
          set of settings. Each setting can be either a value (integer, string,
          boolean or path) or a list of such values.
        '';
       };

  ageType = types.str // {
    check = x:
      isString x &&
      (builtins.match "([0-9]+([smhdw]|min|ms|us)*)+" x != null);
    description = "tmpfiles.d(5) age format";
  };

  configFile = pkgs.writeText "privoxy.conf"
    (concatStrings (
      # Relative paths in some options are relative to confdir. Privoxy seems
      # to parse the options in order of appearance, so this must come first.
      # Nix however doesn't preserve the order in attrsets, so we have to
      # hardcode confdir here.
      [ "confdir ${pkgs.privoxy}/etc\n" ]
      ++ mapAttrsToList serialise cfg.settings
    ));

  inspectAction = pkgs.writeText "inspect-all-https.action"
    ''
      # Enable HTTPS inspection for all requests
      {+https-inspection}
      /
    '';

in

{

  ###### interface

  options.services.privoxy = {

    enable = mkEnableOption "Privoxy, non-caching filtering proxy";

    enableTor = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether to configure Privoxy to use Tor's faster SOCKS port,
        suitable for HTTP.
      '';
    };

    inspectHttps = mkOption {
      type = types.bool;
      default = false;
      description = ''
        Whether to configure Privoxy to inspect HTTPS requests, meaning all
        encrypted traffic will be filtered as well. This works by decrypting
        and re-encrypting the requests using a per-domain generated certificate.

        To issue per-domain certificates, Privoxy must be provided with a CA
        certificate, using the <literal>ca-cert-file</literal>,
        <literal>ca-key-file</literal> settings.

        <warning><para>
          The CA certificate must also be added to the system trust roots,
          otherwise browsers will reject all Privoxy certificates as invalid.
          You can do so by using the option
          <option>security.pki.certificateFiles</option>.
        </para></warning>
      '';
    };

    certsLifetime = mkOption {
      type = ageType;
      default = "10d";
      example = "12h";
      description = ''
        If <literal>inspectHttps</literal> is enabled, the time generated HTTPS
        certificates will be stored in a temporary directory for reuse. Once
        the lifetime has expired the directory will cleared and the certificate
        will have to be generated again, on-demand.

        Depending on the traffic, you may want to reduce the lifetime to limit
        the disk usage, since Privoxy itself never deletes the certificates.

        <note><para>The format is that of the <literal>tmpfiles.d(5)</literal>
        Age parameter.</para></note>
      '';
    };

    userActions = mkOption {
      type = types.lines;
      default = "";
      description = ''
        Actions to be included in a <literal>user.action</literal> file. This
        will have a higher priority and can be used to override all other
        actions.
      '';
    };

    userFilters = mkOption {
      type = types.lines;
      default = "";
      description = ''
        Filters to be included in a <literal>user.filter</literal> file. This
        will have a higher priority and can be used to override all other
        filters definitions.
      '';
    };

    settings = mkOption {
      type = types.submodule {
        freeformType = configType;

        options.listen-address = mkOption {
          type = types.str;
          default = "127.0.0.1:8118";
          description = "Pair of address:port the proxy server is listening to.";
        };

        options.enable-edit-actions = mkOption {
          type = types.bool;
          default = false;
          description = "Whether the web-based actions file editor may be used.";
        };

        options.actionsfile = mkOption {
          type = types.listOf types.str;
          # This must come after all other entries, in order to override the
          # other actions/filters installed by Privoxy or the user.
          apply = x: x ++ optional (cfg.userActions != "")
            (toString (pkgs.writeText "user.actions" cfg.userActions));
          default = [ "match-all.action" "default.action" ];
          description = ''
            List of paths to Privoxy action files. These paths may either be
            absolute or relative to the privoxy configuration directory.
          '';
        };

        options.filterfile = mkOption {
          type = types.listOf types.str;
          default = [ "default.filter" ];
          apply = x: x ++ optional (cfg.userFilters != "")
            (toString (pkgs.writeText "user.filter" cfg.userFilters));
          description = ''
            List of paths to Privoxy filter files. These paths may either be
            absolute or relative to the privoxy configuration directory.
          '';
        };
      };
      default = {};
      example = literalExample ''
        { # Listen on IPv6 only
          listen-address = "[::]:8118";

          # Forward .onion requests to Tor
          forward-socks5 = ".onion localhost:9050 .";

          # Log redirects and filters
          debug = [ 128 64 ];
          # This is equivalent to writing these lines
          # in the Privoxy configuration file:
          # debug 128
          # debug 64
        }
      '';
      description = ''
        This option is mapped to the main Privoxy configuration file.
        Check out the Privoxy user manual at
        <link xlink:href="https://www.privoxy.org/user-manual/config.html"/>
        for available settings and documentation.

        <note><para>
          Repeated settings can be represented by using a list.
        </para></note>
      '';
    };

  };

  ###### implementation

  config = mkIf cfg.enable {

    users.users.privoxy = {
      description = "Privoxy daemon user";
      isSystemUser = true;
      group = "privoxy";
    };

    users.groups.privoxy = {};

    systemd.tmpfiles.rules = with cfg.settings; [
      "d ${certificate-directory} 0770 privoxy privoxy ${cfg.certsLifetime}"
    ];

    systemd.services.privoxy = {
      description = "Filtering web proxy";
      after = [ "network.target" "nss-lookup.target" ];
{'fraction_non_alphanumeric': 0.09646110198596386, 'fraction_numerical': 0.005823503061072122, 'mean_word_length': 2.1406799531066825, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 53, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 2, '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': '2881093', 'n_tokens_mistral': 3645, 'n_tokens_neox': 3541, 'n_words': 1265}