How I made the perfect minecraft server

So, I like playing minecraft with my friends. Around half a year ago I've setup a minecraft server for us that then became bigger, now being an open server where you can play after getting accepted (this is done to prevent griefers, not the scope of this post). Now, we've used pufferpanel for hosting it, both when I had Ubuntu server aswell as NixOS on my homeserver. Our server, however, has a so-called "worlds" system. Those worlds are actually servers, where player data is synchronized between them. It's done to use multiple cores aswell as to never wipe the main world. The worlds are:

  • Building world. The main world where players, well, build. Never gets wiped.
  • Farms world. A world where players build big farms and redstone contraptions. Never gets wiped.
  • Resources world. A world dedicated to getting resources. Gets wiped as needed, usually with the next minecraft version.
  • Arts world. Not yet released. A world for players to build maparts in the end dimension. Has no overworld or nether. Never gets wiped.

This worlds idea was taken from PWGood (RU).

All in all, the servers config is almost the same. The only difference between them is their name in plugin configs like player analytics and some others, aswell as world names. This made it very difficult to update the servers using pufferpalen, as you would need to:

  • Update configs on one server and test them
  • Download the configs
  • Upload them to all other servers either by sftp or with a web UI

This is not very convenient, especially if you have a big number of worlds. PWGood's server, pepeland, has, for example, 7 worlds. Granted, I don't know what they use to manage their servers, though it's probably not a panel.

With this in mind, the first idea that came to my mind was using symlinks for configs. This is not ideal as some configs differ in server names and would have to be changed independently. Overall, this method is usable, though I want something better.

Mcman

After some research I've found out about mcman. This is a powerful piece of software, as it allows you to manage servers declaratively. However, this didn't really satisfy our usecase, as you couldn't have multiple servers with the same config... Or so I thought. Turns out, there is an undocumented feature, groups. With it you can have plugins aswell as config files apply to multiple servers in the specific group. This is exactly what we needed, as our survival servers are essentially a group.

Let me tell you more about how I structured the config, aswell as what groups we have.

For groups, we have 4 of them:

  1. paper group for all paper servers. This is here because we have a velocity proxy, so global plugins won't work for some of them, but the biggest problem is ultimately plugin config folders name. On velocity, for example, SkinsRestorer config folder is skinsrestorer, while on paper it's SkinsRestorer. This is why I have the paper group.
  2. lobby group for lobby servers. We have two of them right now, usual lobby for when you first join the server and survivallobby for the lobby of the surival servers to switch between worlds. This group is pretty small and contains only two plugins and their configs, DeluxeHub and DeluxeMenus. What I really like about mcman is that it merges config folders. In this group, we have shared config.yml for DeluxeHub, but the data.yml that contains the spawn coordinates is different for each server, and is stored in ./config/plugins/DeluxeHub/data.yml. This all gets merged and in the end I have the same config.yml, but different data.yml. Same applies to DeluxeMenus and the server selection menu.
  3. survival group. This group contains shared plugins for each of the survival servers, including all worlds as well as survival lobby. This group has, for example, shared chat config that relays messages between survival servers.
  4. survivalworlds group. It has plugins for only the worlds, this includes a plugin for synchronizing user data, as you don't want players to have their inventories and what not in the lobby.

Overall, the group system is really powerful and I'm going to write a documentation page for it soon and contribute to mcman.

Nix

My server now runs NixOS, so flux was a natural choice. I also have a mysql database defined with nix, aswell as a redis database, all of that in a nixosModule. What is NOT the scope of the module is backups, but this will be explained later.

Flux

Now that we have our servers configured, we have to run them somehow. Sure, mcman has a run command, but it's not recommended to run your servers. For running them, we use IogaMaster's flux. It allows to declare servers with nix and run them as systemd services. This is fairly easy, though the only inconvenience is that flux uses FODs - fixed output derivations. They require an output hash to get network access, which is needed to build the server. There is one problem with this, mcman is not yet reproducible, as the spigot source is broken and you have to download the latest version right now. This will be fixed in v2. Back to flux, I've declared my servers, but there was yet another issue: flux doesn't support networks. I've fixed this, along with some other fixes.

Here is a code example on how to define networks and regular servers:

{ ... }: {
  flux.servers = {
    server_in_a_network = {
      package = pkgs.mkMinecraftServer {
        name = "server_in_a_network";
        src = ./network; 
        serverLocation = "servers/server_in_a_network";
        hash = "sha256-yourhash="; #"";
      };
    };
    server_without_a_network = {
      package = pkgs.mkMinecraftServer {
        name = "server_without_a_network";
        src = ./servers/server_without_a_network;
        hash = "sha256-yourhash="; #"";
      };
      proxy.enable = false;
    };
  };
}

Database

Now that we have the servers, we also need a database. We use mysql for our minecraft servers, as this is what most plugins use. We also have redis for things like chat and LuckPerms. I also like nix because it has ensureDatabases, an option that creates databases if they don't exist. For some reason, minecraft plugins can't create databases but can create tables in them, so this is very good, as it allows for us to easily redeploy the server with just a nixosModule and a few options. The only thing that I still don't know how to fix is LuckPerms permissions being in database, so they are not declarative. This is something I'd like to make declarative to be able to redeploy server anywhere without any manual action, such as importing the LuckPerms table, etc.

Moreover, we also use phpmyadmin running as an oci-container like so:

{ ... }: {
  virtualisation.oci-containers.containers = {
    "phpmyadmin" = {
      image = "phpmyadmin:latest";
      volumes = [
        "/run/mysqld/mysqld.sock:/tmp/mysql.sock"
      ];
      environment = {
        PMA_HOST = "localhost";
        UPLOAD_LIMIT = "1G";
        TZ = "Timezone/Redacted";
      };
      ports = ["0.0.0.0:22480:80"];
    };
  };
}

Backups

Backups are needed for something like a minecraft server. We use borg backups for this, as they don't take up much space and are incremental.

This is not a scope of the nixosModule but is instead done on the machine the module is installed on - after all, backups are optional, though highly recommended, I'd even say needed.

I have everything setup in a way that you need to backup only /var/lib/Greendale - the servers are in /var/lib/Greendale/servers, the databases are in /var/lib/Greendale/mysql and /var/lib/Greendale/redis. One is none, two is one, so I've setup three backup locations, two of which are off-site.

Site

The site is made using Next.js. I really like it's developer experience, along with tailwind for css and Drizzle as the ORM.

As for the nix part, I use it for deployment and a dev shell using the nix-community template. I probably will setup an automatic CD pipeline in the future and write a post about it.

Conclusion

I really like nix and mcman for minecraft servers, especially for my usecase. Would I recommend this for everyone? Probably not. But if you already use nix and have some intricate usecase, why not give it a try?

If you want to play on the server, here is the discord link. Note that the server is russian-speaking.