How can I configure default values (lib.mkDefault) for options in a submodule option?

For context, I am using nix-minecraft to configure a Minecraft server on my system. I would like to configure some more secure defaults, as Minecraft has some relatively insecure settings by default. The services.minecraft-servers.servers option is defined as a types.attrsOf (types.submodule {...}) type. I’ve tried the following configuration, which was intended to get a list of defined servers from the services.minecraft-servers.servers option and use it to generate defaults for any server that is defined.

{ pkgs, lib, config, ... }:
{
  config = lib.mkMerge [
    (lib.mkIf config.services.minecraft-servers.enable {
      services.minecraft-servers.servers = lib.genAttrs (builtins.attrNames config.services.minecraft-servers.servers)
        (_: {
          serverProperties = {
          # Use whitelist.
          white-list = lib.mkDefault true;
          enforce-whitelist = lib.mkDefault true;

          # Disables chat reporting requirement; does nothing to aid in
          # security and only serves to make the server less private.
          enforce-secure-profile = lib.mkDefault false;

          # Require online authentication for users.
          online-mode = lib.mkDefault true;
        };
      });
    })
  ];
}

What I was intending to do with the above is to configure my system so that any server defined in services.minecraft-servers.servers would have the default options defined above in serverProperties. However, this just results in an infinite recursion:

warning: Git tree '/home/nullbite/nixfiles' is dirty
building the system configuration...
warning: Git tree '/home/nullbite/nixfiles' is dirty
error:
       … while calling the 'head' builtin

         at /nix/store/cp5s6lac68bzypgc1chl8c0ss3rqxi27-source/lib/attrsets.nix:1541:11:

         1540|         || pred here (elemAt values 1) (head values) then
         1541|           head values
             |           ^
         1542|         else

       … while evaluating the attribute 'value'

         at /nix/store/cp5s6lac68bzypgc1chl8c0ss3rqxi27-source/lib/modules.nix:809:9:

          808|     in warnDeprecation opt //
          809|       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
             |         ^
          810|         inherit (res.defsFinal') highestPrio;

       (stack trace truncated; use '--show-trace' to show the full trace)

       error: infinite recursion encountered

       at /nix/store/cp5s6lac68bzypgc1chl8c0ss3rqxi27-source/lib/modules.nix:809:9:

          808|     in warnDeprecation opt //
          809|       { value = builtins.addErrorContext "while evaluating the option `${showOption loc}':" value;
             |         ^
          810|         inherit (res.defsFinal') highestPrio;

Is there any other way I could set default values for submodule options? I can’t think of any way to access the list of defined options to be able to set them without triggering an infinite recursion.

For reference, the option type is defined here.

I’ve come up with alternative solution for this specific use case that works for what I want. I’ve defined a function mkServer which returns a lib.mkMerge which merges a set of defaults defined in the function with the provided attrset. Here’s a basic example of what I did (my full implementation is here):

mkServer = { ... }@opts: lib.mkMerge [
  # default options
  {
    whitelist = lib.mkDefault {
      # whitelist
    };
    serverProperties = {
      # allows no chat reports to run
      enforce-secure-profile = lib.mkDefault false;

      # whitelist
      white-list = lib.mkDefault true;
      enforce-whitelist = lib.mkDefault true;

      # no telemetry
      snooper-enabled = lib.mkDefault false;
    };
  }
  # argument options
  opts
];

This works for this specific use case, but I’d still like to know if there’s a way to configure submodule option defaults, or more likely, if it’s something I should probably never do. There’s a few other scenarios where I feel like this could be useful (e.g., enabling compression on all btrfs mounts), although there’s probably much better and more granular alternatives, such as custom options or a custom function like above.