{
  pkgs,
  lib,
  config,
  ...
}:
let
  cfg = config.services.crowdsec;
  settingsFormat = pkgs.formats.yaml { };

  # TODO(tlater): Upstream properly installing the service file
  crowdsec = pkgs.symlinkJoin {
    name = "crowdsec-with-unit";
    paths = [
      pkgs.crowdsec
      (pkgs.runCommandLocal "crowdsec.service" { } ''
        mkdir -p $out/lib/systemd/system/

        substitute ${pkgs.crowdsec}/share/crowdsec/config/crowdsec.service $out/lib/systemd/system/crowdsec.service \
            --replace-fail /usr/local ${pkgs.crowdsec}
      '')
    ];
  };
in
{
  options.services.crowdsec =
    let
      inherit (lib.types) nullOr package path;
    in
    {
      enable = lib.mkEnableOption "crowdsec";

      package = lib.mkOption {
        type = package;
        default = crowdsec;
      };

      stateDirectory = lib.mkOption {
        type = path;
        readOnly = true;

        description = ''
          The state directory of the crowdsec instance. Cannot be
          changed, but is exposed for downstream use.
        '';
      };

      settings = lib.mkOption {
        inherit (settingsFormat) type;
        default = { };

        description = ''
          The crowdsec configuration. Refer to
          <https://docs.crowdsec.net/docs/next/configuration/crowdsec_configuration/>
          for details on supported values.
        '';
      };

      # clientCredentials = lib.mkOption {
      #   type = path;

      #   description = ''
      #     The API client credentials to configure; Required to access
      #     the service at all.
      #   '';
      # };

      centralApiCredentials = lib.mkOption {
        type = nullOr path;
        default = null;

        description = ''
          The API key to access crowdsec's central API - this is
          required to access any of the shared blocklists.

          Use of this feature is optional, entering no API key (the
          default) turns all sharing or receiving of blocked IPs off.

          Note that adding the API key by itself does not enable
          sharing of blocked IPs with the central API. This limits the
          types of blocklists this instance can access.

          To also turn sharing blocked IPs on, set
          `api.server.online_client.sharing = true;`.
        '';
      };

      ctiApiKey = lib.mkOption {
        type = nullOr path;
        default = null;

        description = ''
          The API key for crowdsec's CTI offering.
        '';
      };
    };

  config = lib.mkIf cfg.enable {
    # Set up default settings; anything that *shouldn't* be changed is
    # set to the default priority so that users need to use
    # `lib.mkForce`.
    services.crowdsec = {
      stateDirectory = "/var/lib/crowdsec";

      settings = {
        common = {
          # Default config daemonizes, but the service is set to
          # `notify`; presumably the daemonization isn't really intended
          daemonize = false;
          # The default logs to files, which isn't the preferred way
          # on NixOS
          log_media = "stdout";
        };

        config_paths = {
          config_dir = "/etc/crowdsec/";
          data_dir = "${cfg.stateDirectory}/data/";
          # This "config" file is intended to be written to using the
          # cscli tool, so you can temporarily make it so rules don't
          # do anything but log what they *would* do for
          # experimentation.
          simulation_path = "${cfg.stateDirectory}/config/simulation.yaml";

          pattern_dir = "${cfg.package}/share/crowdsec/config/patterns";

          # We don't want to actually download anything; Any rules
          # will be properly packaged.
          hub_dir = lib.mkDefault "/var/empty/";
          index_path = lib.mkDefault "/var/empty/.index.json";

          # Integrations aren't supported for now
          notification_dir = lib.mkDefault "/var/empty/";
          plugin_dir = lib.mkDefault "/var/empty/";
        };

        crowdsec_service.acquisition_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/acquis.yaml";

        cscli = {
          prometheus_uri = lib.mkDefault "127.0.0.1:6060";
        };

        api = {
          cti = {
            enabled = cfg.ctiApiKey != null;
            key = cfg.ctiApiKey;
          };
          # client.credentials_path = cfg.clientCredentials;
          client.credentials_path = "${cfg.stateDirectory}/local_credentials.yaml";
          server = {
            listen_uri = lib.mkDefault "127.0.0.1:8080";
            profiles_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/profiles.yaml";
            console_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/console.yaml";

            online_client = {
              # By default, we don't let crowdsec phone home, since
              # this is usually within NixOS users' concerns.
              #
              # TODO: Enable when this option becomes available
              # (1.6.4, current nixpkgs-unstable)
              # sharing = lib.mkDefault false;
              credentials_path = cfg.centralApiCredentials;
            };
          };
        };

        # We enable prometheus by default, since cscli relies on it
        # for metrics
        prometheus = {
          enabled = lib.mkDefault true;
          level = lib.mkDefault "full";
          listen_addr = lib.mkDefault "127.0.0.1";
          listen_port = lib.mkDefault 6060;
        };
      };
    };

    systemd.packages = [
      cfg.package
    ];

    environment.etc."crowdsec/config.yaml".source =
      settingsFormat.generate "crowdsec-settings.yaml" cfg.settings;

    # Note that the service basics are already defined upstream
    systemd.services.crowdsec.serviceConfig = {

      # TODO: ExecStartPre to make `/var/lib/crowdsec/config`

      User = "crowdsec";
      Group = "crowdsec";
      SupplementaryGroups = [ "systemd-journal" ];

      StateDirectory = "crowdsec";

      PrivateTmp = true;
      PrivateUsers = true;
      ProtectHome = true;
      CapabilityBoundingSet = [ ];
      LockPersonality = true;
      PrivateDevices = true;
      ProtectHostname = true;
      ProtectKernelTunables = true;
      ProtectKernelModules = true;
      ProtectControlGroups = true;

      NoNewPrivileges = true;
      RestrictSUIDSGID = true;

      ProtectProc = "invisible";
      ProcSubset = "pid"; # Needed for journal access

      RestrictNamespaces = true;
      RestrictRealtime = true;

      SystemCallFilter = [
        "@system-service"
        "@network-io"
      ];
      SystemCallArchitectures = [ "native" ];
      SystemCallErrorNumber = "EPERM";

      ExecPaths = [ "/nix/store" ];
      NoExecPaths = [ "/" ];
    };

    users = {
      users.crowdsec = {
        isSystemUser = true;
        home = cfg.stateDirectory;
        group = "crowdsec";
      };
      groups = {
        crowdsec = { };
      };
    };
  };
}