{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.services.karakeep;

  karakeepEnv = lib.mkMerge [
    { DATA_DIR = "/var/lib/karakeep"; }
    (lib.mkIf cfg.meilisearch.enable {
      MEILI_ADDR = "http://127.0.0.1:${toString config.services.meilisearch.listenPort}";
    })
    (lib.mkIf cfg.browser.enable {
      BROWSER_WEB_URL = "http://127.0.0.1:${toString cfg.browser.port}";
    })
    cfg.extraEnvironment
  ];

  environmentFiles = [
    "/var/lib/karakeep/settings.env"
  ]
  ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile);
in
{
  options = {
    services.karakeep = {
      enable = lib.mkEnableOption "Enable the Karakeep service";
      package = lib.mkPackageOption pkgs "karakeep" { };

      extraEnvironment = lib.mkOption {
        description = ''
          Environment variables to pass to Karakaeep. This is how most settings
          can be configured. Changing DATA_DIR is possible but not supported.

          See <https://docs.karakeep.app/configuration/>
        '';
        type = lib.types.attrsOf lib.types.str;
        default = { };
        example = lib.literalExpression ''
          {
            PORT = "1234";
            DISABLE_SIGNUPS = "true";
            DISABLE_NEW_RELEASE_CHECK = "true";
          }
        '';
      };

      environmentFile = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        default = null;
        description = ''
          An optional path to an environment file that will be used in the web and workers
          services. This is useful for loading private keys.
        '';
        example = "/var/lib/karakeep/secrets.env";
      };

      browser = {
        enable = lib.mkOption {
          description = ''
            Enable the karakeep-browser service that runs a chromium instance in
            the background with debugging ports exposed. This is necessary for
            certain features like screenshots.
          '';
          type = lib.types.bool;
          default = true;
        };
        port = lib.mkOption {
          description = "The port the browser should run on.";
          type = lib.types.port;
          default = 9222;
        };
        exe = lib.mkOption {
          description = "The browser executable (must be Chrome-like).";
          type = lib.types.str;
          default = "${pkgs.chromium}/bin/chromium";
          defaultText = lib.literalExpression "\${pkgs.chromium}/bin/chromium";
          example = lib.literalExpression "\${pkgs.google-chrome}/bin/google-chrome-stable";
        };
      };

      meilisearch = {
        enable = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = ''
            Enable Meilisearch and configure Karakeep to use it. Meilisearch is
            required for text search.
          '';
        };
      };
    };
  };

  config = lib.mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];

    users.groups.karakeep = { };
    users.users.karakeep = {
      isSystemUser = true;
      group = "karakeep";
    };

    services.meilisearch = lib.mkIf cfg.meilisearch.enable {
      enable = true;
    };

    systemd.services.karakeep-init = {
      description = "Initialize Karakeep Data";
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      partOf = [ "karakeep.service" ];
      path = [ pkgs.openssl ];
      script = ''
        umask 0077

        if [ ! -f "$STATE_DIRECTORY/settings.env" ]; then
          cat <<EOF >"$STATE_DIRECTORY/settings.env"
        # Generated by NixOS Karakeep module
        MEILI_MASTER_KEY=$(openssl rand -base64 36)
        NEXTAUTH_SECRET=$(openssl rand -base64 36)
        EOF
        fi

        export DATA_DIR="$STATE_DIRECTORY"
        exec "${cfg.package}/lib/karakeep/migrate"
      '';
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
        User = "karakeep";
        Group = "karakeep";
        StateDirectory = "karakeep";
        PrivateTmp = "yes";
      };
    };

    systemd.services.karakeep-workers = {
      description = "Karakeep Workers";
      wantedBy = [ "multi-user.target" ];
      after = [
        "network.target"
        "karakeep-init.service"
      ];
      partOf = [ "karakeep.service" ];
      path = [
        pkgs.monolith
        pkgs.yt-dlp
      ];
      environment = karakeepEnv;
      serviceConfig = {
        User = "karakeep";
        Group = "karakeep";
        ExecStart = "${cfg.package}/lib/karakeep/start-workers";
        StateDirectory = "karakeep";
        EnvironmentFile = environmentFiles;
        PrivateTmp = "yes";
      };
    };

    systemd.services.karakeep-web = {
      description = "Karakeep Web";
      wantedBy = [ "multi-user.target" ];
      after = [
        "network.target"
        "karakeep-init.service"
        "karakeep-workers.service"
      ];
      partOf = [ "karakeep.service" ];
      environment = karakeepEnv;
      serviceConfig = {
        ExecStart = "${cfg.package}/lib/karakeep/start-web";
        User = "karakeep";
        Group = "karakeep";
        StateDirectory = "karakeep";
        EnvironmentFile = environmentFiles;
        PrivateTmp = "yes";
      };
    };

    systemd.services.karakeep-browser = lib.mkIf cfg.browser.enable {
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      partOf = [ "karakeep.service" ];
      script = ''
        export HOME="$CACHE_DIRECTORY"
        exec ${cfg.browser.exe} \
          --headless --no-sandbox --disable-gpu --disable-dev-shm-usage \
          --remote-debugging-address=127.0.0.1 \
          --remote-debugging-port=${toString cfg.browser.port} \
          --hide-scrollbars \
          --user-data-dir="$STATE_DIRECTORY"
      '';
      serviceConfig = {
        Type = "simple";
        Restart = "on-failure";

        CacheDirectory = "karakeep-browser";
        StateDirectory = "karakeep-browser";

        DevicePolicy = "closed";
        DynamicUser = true;
        LockPersonality = true;
        NoNewPrivileges = true;
        PrivateDevices = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectSystem = "strict";
        RestrictNamespaces = true;
        RestrictRealtime = true;
      };
    };
  };

  meta = {
    maintainers = [ lib.maintainers.three ];
  };
}
