Skip to content

Conversation

@Yeshey
Copy link

@Yeshey Yeshey commented May 20, 2025

Solves #142
This adds the options:

        lazymc = {
          enable = true;
          package = pkgs.lazymc;
          config = { ... };
        };

Under each instance. This makes servers go to sleep when there are no players and wake up when one player tries to join lazymc

servers.<name>.lazymc

Integrates lazymc, putting server to sleep when idle and waking it upon player connection.

  • enable: boolean, default false

  • package: package, default pkgs.lazymc
    The lazymc package to use. You might have to change lazymc version according to your minecraft server version, for example lazymc v0.2.10 supports Minecraft Java Edition 1.7.2+, while for Minecraft Java Edition 1.20.3+ you'll need lazymc v0.2.11

  • config: attribute set, default {}
    Allows defining and overriding settings in the lazymc.toml, for all options see here. The auto generated config options are:

    • server.command: Automatically set to use the server package and jvmOpts;
    • server.directory;
    • server.address: Automatically set to 127.0.0.1:<serverProperties.server-port or 25565>";

    Firewall

    • When lazymc.enable = true, the openFirewall option for this server instance will open the port specified in lazymc.config.public.address (or its default), not the internal Minecraft serverProperties.server-port.

    Example:

    { pkgs, ... }: {
      services.minecraft-servers = {
        eula = true;
        servers.myLazyServer = {
          enable = true;
          package = pkgs.paperServers.paper-1_18_2;
          serverProperties = {
            "server-port" = 25566;
            "max-tick-time" = -1; # Recommended with lazymc
          };
    
          lazymc = {
            enable = true;
            package = let
            # you can use https://lazamar.co.uk/nix-versions/
              pkgs-with-lazymc_0_2_10 = import (builtins.fetchTarball {
                  url = "https://github.com/NixOS/nixpkgs/archive/336eda0d07dc5e2be1f923990ad9fdb6bc8e28e3.tar.gz";
                  sha256 = "sha256:0v8vnmgw7cifsp5irib1wkc0bpxzqcarlv8mdybk6dck5m7p10lr";
              }) { inherit (pkgs) system; };
            in pkgs-with-lazymc_0_2_10.lazymc;
            config = {
              public.address = "0.0.0.0:25565";
            };
          };
        };
      };
    }

@Yeshey
Copy link
Author

Yeshey commented May 20, 2025

Maybe it would have been better to not use the server-port - 1 and just let the user do all the config themeselves, there is quite a bit of logic bc of that, but there would always be some abstraction because openFirewall would have to automatically open the port in lazymc instead of in serverproperties if it was active. And I do think that having a one click solution like this is more user friendly as long as we can make it work well

And if a user sets two mc instances in adjacent ports with lazymc, there is an assertion for that

@Infinidoge
Copy link
Owner

Infinidoge commented May 21, 2025

Please avoid closing and opening pull requests. You can force push to your branch to edit commits.
Additionally, please avoid doing PRs from a master branch, as it leads to CI commits that shouldn't be in the PR (as evidenced).

You can change the branch a PR is targeting in the PR edit menu on GitHub.

@Infinidoge
Copy link
Owner

With regards to the actual content of the PR:

  • TOML writing can be handled through the files option of the server, as opposed to reimplementing it yourself. See here for an example
  • The lazymc config overwrites the management system config, which it should never do. Based on how you wrote it, it seems that lazymc should instead be a management system in and of itself, as right now it incorrectly overwrites load-bearing parts of the existing management systems, breaking them.
  • Overall the implementation feels... very verbose and overcomplicated.

@Yeshey
Copy link
Author

Yeshey commented May 21, 2025

Thanks for reviewing, I didn't realize that deleting and renaming my repo would also close my PRs, I'll try to keep further changes in this one.
Yeah, the code isn't ideal, when I did it i was probably thinking about my own needs more than doing something clean to try to merge, and might have used an LLM to help a bit 😅.
Sorry if it wasted your time, soon as I get a bit of time I'll try to rewrite it. On a rewrite I think I'd drop the "server-port" - 1 logic, openFirewall would open the lazymc port instead of the mc server one if lazymc is enabled; and just let the user manage the ports themeselves, idk if you want to weigh in on that

@Titaniumtown
Copy link

Dont use a llm for anything. opens the project up to legal issues.

@CalmSeoul
Copy link

Will this eventually be merged with the main branch? This would be an awesome addition cause I'm having a tough time trying to get lazymc working with nix-minecraft manually, but having it built in would be incredible

@Yeshey
Copy link
Author

Yeshey commented Jun 21, 2025

@CalmSeoul this is a clunky implementation, as it stands it should be rewriten to get merged. I will try to look at it in the summer but i cant promise. I'm not the most proficiant nixer as well.
If u want to use it rn, you can import the flake from my fork and check the readme for how its set up.

@Yeshey Yeshey deleted the branch Infinidoge:master August 8, 2025 13:56
@Yeshey Yeshey closed this Aug 8, 2025
@Yeshey Yeshey deleted the master branch August 8, 2025 13:56
@Yeshey Yeshey reopened this Aug 8, 2025
@Yeshey Yeshey force-pushed the master branch 3 times, most recently from bdeefe5 to 95fb235 Compare August 9, 2025 04:44
@Yeshey
Copy link
Author

Yeshey commented Aug 9, 2025

Rewrote the implementation, sorry for the fuss earlier, no more vibe code now, hopefully it can get merged

@Titaniumtown
Copy link

I tested this and it works! Good job.

Only comment is that, shouldn't lazymc's public port be 25565 and the internal port be 25566? It should be transparent to the end user and not be another port imo.

@Yeshey
Copy link
Author

Yeshey commented Aug 14, 2025

I thought shifting the port up or down would be kinda arbitrary and code heavy, so I decided to leave the assertion to force the user to always pick their lazymc public port instead. Maybe you're right that that should suggest port 25565, and now that I'm thinking about that, there should probably be another assertion for when the internal server port and the lazymc public port are the same

@Yeshey
Copy link
Author

Yeshey commented Aug 14, 2025

Or maybe, to do it this way, the right method would be to force the user to set the serverProperties.server-port instead when lazymc is enabled

@Yeshey
Copy link
Author

Yeshey commented Aug 14, 2025

There you go, I think this is better

now if a user goes

        servers.testerido = {
          enable = true;
          lazymc.enable = true;
        };

they'll get:

       error: Server 'testerido' has the same port set for serverProperties.server-port and lazymc.config.public.address
       Please set for example: `testerido.serverProperties.server-port = 25566;`, lazymc's internal server.address will automatically point to it
       Lazymc's public.address is "0.0.0.0:25565" by default

Copy link

@Titaniumtown Titaniumtown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few comments, mostly good!

@rharish101
Copy link

Hi, are there any blockers left preventing this PR being merged? I'm interested in running lazymc with this flake.

@Yeshey
Copy link
Author

Yeshey commented Oct 10, 2025

Hi, are there any blockers left preventing this PR being merged? I'm interested in running lazymc with this flake.

@Titaniumtown @Infinidoge let me know if there are any more blockers, I can try to work on them to get this done! The automatic github action to update package lock files has also been failing recently, not sure why.

@rharish101
Copy link

rharish101 commented Oct 10, 2025

The automatic github action to update package lock files has also been failing recently, not sure why.

That was recently fixed by #176, which got merged a few hours ago.

Also, if you need someone to test it, I can do so, once you've rebased your flake. I'm running a single Paper server. Fair warning, I've never used lazymc before 😛.

@Yeshey
Copy link
Author

Yeshey commented Oct 12, 2025

The automatic github action to update package lock files has also been failing recently, not sure why.

That was recently fixed by #176, which got merged a few hours ago.

Also, if you need someone to test it, I can do so, once you've rebased your flake. I'm running a single Paper server. Fair warning, I've never used lazymc before 😛.

That'd be delightful :), sorry I didn't see your response earlier!
Let me see if I can rebase my flake to fix this! Lazymc is pretty straightforward, I added a section to the readme file that should help you set it up if you're willing to test it, if that's enough for you to find your way around it it would already be a testament to my assertions and bit of documentation hahaah

@Yeshey Yeshey force-pushed the master branch 2 times, most recently from c80eb05 to 27d2dc5 Compare October 12, 2025 11:01
@rharish101
Copy link

I just tested it, and it works! My setup is just a single Paper server with lazymc enabled for it, with Velocity as a proxy. I saw a slow connection time the first time I connected to the server (after a while), but every other time, it was fast enough. I think this means that lazymc is working, right?

@Yeshey
Copy link
Author

Yeshey commented Oct 14, 2025

I just tested it, and it works! My setup is just a single Paper server with lazymc enabled for it, with Velocity as a proxy. I saw a slow connection time the first time I connected to the server (after a while), but every other time, it was fast enough. I think this means that lazymc is working, right?

if you have time, check if it works withmanagementSystem.systemd-socket.enable = true; as well. Last time I checked it was working properly
I guess we wait for someone with write access to give us a heads up! ahah

@rharish101
Copy link

if you have time, check if it works withmanagementSystem.systemd-socket.enable = true; as well.

What's managementSystem here? I can't find a NixOS option here: https://search.nixos.org/options?channel=unstable&query=managementSystem

@Yeshey
Copy link
Author

Yeshey commented Oct 14, 2025

What's managementSystem here? I can't find a NixOS option here: https://search.nixos.org/options?channel=unstable&query=managementSystem

Sorry, It's a nix-minecraft option, seems like it isn't immediately found in the documentation ahaha I saw it in the code,
if you put inside a nix-minecraft server config:

          managementSystem.systemd-socket.enable = true;
          managementSystem.tmux.enable = false;

it'll basically not use tmux, but there will also be no easy way to give input to the server.

Either way I did it on my own server and it seems to work as well, so at least on my side everything's working :)

@rharish101
Copy link

rharish101 commented Oct 14, 2025

I tried it out, and it works on my system too.

EDIT: I just mean that the server runs with lazymc. I tested neither the systemd nor the tmux sockets.

Copy link
Owner

@Infinidoge Infinidoge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am honest, I don't really want this functionality to be baked into the module. This is something the user can theoretically manage for themself. However considering the complexity of that, I am not (yet) rejecting this outright.

With that said, in addition to the comments on files, please rebase out all automated lockfile commits, and change your branch to something other than master. Automated commits pollute the PR. (Note to self, add that to CONTRIBUTING.md)

Comment on lines +762 to +783
"lazymc.toml".value = lib.recursiveUpdate {
server = {
command = "${getExe conf.package} ${conf.jvmOpts}";
directory = ".";
address = "127.0.0.1:${toString conf.serverProperties.server-port or 25565}";
};
} conf.lazymc.config;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will create the lazymc.toml file even if not configured. Please add a mkIf guard.

The other values don't have a mkIf guard as their values will be an empty attribute set (or otherwise empty value) when not configured.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't chuck a mkIf in there, ended up solving it like this:

            // (optionalAttrs conf.lazymc.enable {
              "lazymc.toml".value = lib.recursiveUpdate {
                server = {
                  command = "${getExe conf.package} ${conf.jvmOpts}";
                  directory = ".";
                  address = "127.0.0.1:${toString conf.serverProperties.server-port or 25565}";
                };
              } conf.lazymc.config;
            })

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange, I don't see why a mkIf wouldn't work.

${
if server.lazymc.enable then
''
# Won't gracefully stop if server is running unfortunately
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server must stop gracefully if running.
Stopping while the server is running is an expected situation,

Copy link
Author

@Yeshey Yeshey Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the main problem. I've tried a couple ways but there doesn't seem to be any good way talk with lazymc. Or a cross minecraft version way to check if the server is running (that should be lazymcs job).

If the server is running we can send the stop command through stdin and it will stop. But the service won't quit because when it stops lazymc will keep going... The best way to solve this would probably be for this to get merged to lazymc: timvisee/lazymc#81.

Right now I can try to check if maybe we can see if something still running on the port but I've looked at this a bunch and didn't find any good way to solve this.
Paper and Velocity servers do close properly but that's besides the point

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, apparently I can use something like this ps -o state= -p $(pgrep -f java) to check if the server is sleeping or not, I'll try to make use of that to implement graceful exits

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm I wouldn't rely on ps and pgrep, especially based on name. ps based on PID would be fine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, after looking a lot more into this I think I can safely say that lazymc does take care of the shutdown process gracefully and we don't need to be messing with it

Apparantly, vanilla servers don't show anything in the log when shut down with a SIGTERM or SIGINT, but it saves everything and exits safely. It's only in windows where the stop command being issued either manually or through rcon is requiered. I thought flavours like papper were working because they don't disable the logger and display that they're exiting normally.

Furthermore I was spooked by exceptions like these that would be thrown if the server was on:

[12:04:04] [Server console handler/ERROR]: Exception handling console input java.io.IOException: Input/output error at java.base/java.io.FileInputStream.readBytes(Native Method) ~[?:?] at java.base/java.io.FileInputStream.read(FileInputStream.java:287) ~[?:?] at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:345) ~[?:?] at java.base/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420) ~[?:?] at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:399) ~[?:?] at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:350) ~[?:?] at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:393) ~[?:?] at java.base/sun.nio.cs.StreamDecoder.lockedRead(StreamDecoder.java:217) ~[?:?] at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:171) ~[?:?] at java.base/java.io.InputStreamReader.read(InputStreamReader.java:188) ~[?:?] at java.base/java.io.BufferedReader.fill(BufferedReader.java:160) ~[?:?] at java.base/java.io.BufferedReader.implReadLine(BufferedReader.java:370) ~[?:?] at java.base/java.io.BufferedReader.readLine(BufferedReader.java:347) ~[?:?] at java.base/java.io.BufferedReader.readLine(BufferedReader.java:436) ~[?:?] at aro$1.run(SourceFile:189) [server-1.21.10.jar:?]

And even more like that appear on servers like paper because they seem to have more safeguards, but that happens because we loose the connection to tmux and is harmless as far as I can see

I also noticed that only in unix lazymc suspends the process, and tmux is also suspended, but that's expected behavior, it keeps working as soon as someone enters.

So the logic for exiting when using tmux should be even simpler as lazymc takes care of it. I'll push an update when I can, but the functionality is the same as it's already implemented

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have guessed that lazymc would have had this into consideration, but if this is the case, the sending stop commands etc. functionality of this module can also probably be simplified

Copy link
Author

@Yeshey Yeshey Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplified a bit, mostly left your stop code untouched, sending the two CTRL+C to tmux was redundant so I removed that. But I believe everything is working well now, the exception on lost input device might still happen on the server bc tmux dies first but that's harmless as far as I can see and doesn't affect the shutdown, and for us to prevent that it would be a lot of complicated bash logic to detect server states and PIDs and stuff to know when to close tmux, I was going down that path and trust me it's not pretty. I think how it is now is as good an implementation as we'll get, or at least as I can make

@Yeshey
Copy link
Author

Yeshey commented Oct 15, 2025

Cool thanks! I rebased, but instead of renaming the branch (that would close the PR and we would loose the discussion) I disabled github actions on my fork, hopefully that prevents the clutter, I'll go through the file comments soon as I can

@Yeshey Yeshey force-pushed the master branch 4 times, most recently from adaf45d to b8b92d3 Compare October 17, 2025 13:46
@rharish101
Copy link

Is there anything else left to do? I'd like to use this without manually using @Yeshey's fork.

@Yeshey
Copy link
Author

Yeshey commented Oct 26, 2025

Is there anything else left to do? I'd like to use this without manually using @Yeshey's fork.

I'll try to explain the situation as it is right now
The way this nixOS module stops the Minecraft servers is by directly sending the letters "stop" (or any other keyword to stop the server according to the type of server) into the stdin of tmux so the server stops itself, and when the server stops, only then tmux will also exit and the systemd service will exit as well.
But this is not how lazymc works, lazymc sends a SIGINT (or SIGTERM?) signal to the server and hopes that the server stops correctly independently of the type of server it is. And it doesn't expose any API so we can't easily know if the server has already shut down or not, so what enabling lazymc now does is it kills tmux (witch might bring up non fatal exception on the minecraft server because the terminal suddenly dissapears) that in turn kills lazymc witch takes care of killing the server as well with signals.
This to say that lazymc handles server shutdowns fundamentally differently than this module, and accepting that would mean that all the logic for stopping servers by sending stop words is kinda irrelevant at least when lazymc is enabled.
We could theoretically check if the server process is suspended or running to see if it is sleeping or not initialized and work around this, but this is complex and I think this is a pretty bad idea as lazymc is already expecting to handle server shutdown with signals.

I see why there's friction, but if @Infinidoge sees a path for this to get merged I'd also love to be able to finally get this done

Comment on lines 179 to 196
function server_running {
${tmux} -S ${sock} has-session
}
${optionalString (!server.lazymc.enable) ''
function server_running {
${tmux} -S ${sock} has-session
}
if ! server_running ; then
exit 0
fi
if ! server_running ; then
exit 0
fi
${tmux} -S ${sock} send-keys C-u ${escapeShellArg server.stopCommand} Enter
${tmux} -S ${sock} send-keys C-u ${escapeShellArg server.stopCommand} Enter
while server_running; do sleep 1s; done
while server_running; do sleep 1s; done
''}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub is being dumb and not letting me wring a proper suggestion

Instead of wrapping everything in an embedded optional string, just do

stop = optionalString (!server.lazymc.enable) ''
  # original shell script here
'';

Copy link
Author

@Yeshey Yeshey Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sure, fixed, but couldn't that be done in here as well tho, or is there a reason there's an embedded optional string here:

          stop = ''
            ${optionalString (server.stopCommand != null) ''
              echo ${escapeShellArg server.stopCommand} > ${escapeShellArg (ms.systemd-socket.stdinSocket.path name)}

              while kill -0 "$1" 2> /dev/null; do sleep 1s; done
            ''}
          '';

in the ms.systemd-socket.enable part

@Infinidoge
Copy link
Owner

Sorry for the lack of responses, currently midterm season at university so I haven't been able to be active on GitHub.
As such it will likely be a while longer before this will be merged.

freeformType and strMatching
@Yeshey
Copy link
Author

Yeshey commented Oct 27, 2025

Sorry for the lack of responses, currently midterm season at university so I haven't been able to be active on GitHub. As such it will likely be a while longer before this will be merged.

Don't worry I know the feeling, I'm looking into this now instead of sleeping as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants