From c2d0037bab3107cefa919831594c9a4c61ca6317 Mon Sep 17 00:00:00 2001 From: Noah Masur <7386960+nmasur@users.noreply.github.com> Date: Fri, 14 Oct 2022 01:34:35 +0000 Subject: [PATCH] autostart and stop minecraft server --- modules/gaming/minecraft-server.nix | 120 +++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/modules/gaming/minecraft-server.nix b/modules/gaming/minecraft-server.nix index 3da55a3..f27b6f0 100644 --- a/modules/gaming/minecraft-server.nix +++ b/modules/gaming/minecraft-server.nix @@ -1,4 +1,13 @@ -{ ... }: { +{ pkgs, ... }: + +let + + localPort = 25564; + publicPort = 25565; + rconPort = 25575; + rconPassword = "thiscanbeanything"; + +in { unfreePackages = [ "minecraft-server" ]; @@ -7,9 +16,9 @@ eula = true; declarative = true; whitelist = { }; - openFirewall = true; + openFirewall = false; serverProperties = { - server-port = 25565; + server-port = localPort; difficulty = "normal"; gamemode = "survival"; white-list = false; @@ -26,7 +35,112 @@ spawn-monsters = true; allow-nether = true; allow-flight = false; + enable-rcon = true; + "rcon.port" = rconPort; + "rcon.password" = rconPassword; }; }; + networking.firewall.allowedTCPPorts = [ publicPort ]; + + ## Automatically start and stop Minecraft server based on player connections + + # Adapted shamelessly from: + # https://dataswamp.org/~solene/2022-08-20-on-demand-minecraft-with-systemd.html + + # Prevent Minecraft from starting by default + systemd.services.minecraft-server = { wantedBy = pkgs.lib.mkForce [ ]; }; + + # Listen for connections on the public port, to trigger the actual + # listen-minecraft service. + systemd.sockets.listen-minecraft = { + wantedBy = [ "sockets.target" ]; + requires = [ "network.target" ]; + listenStreams = [ "${toString publicPort}" ]; + }; + + # Proxy traffic to local port, and trigger hook-minecraft + systemd.services.listen-minecraft = { + path = [ pkgs.systemd ]; + requires = [ "hook-minecraft.service" "listen-minecraft.socket" ]; + after = [ "hook-minecraft.service" "listen-minecraft.socket" ]; + serviceConfig.ExecStart = + "${pkgs.systemd.out}/lib/systemd/systemd-socket-proxyd 127.0.0.1:${ + toString localPort + }"; + }; + + # Start Minecraft if required and wait for it to be available + # Then unlock the listen-minecraft.service + systemd.services.hook-minecraft = { + path = with pkgs; [ systemd libressl busybox ]; + + # Start Minecraft and the auto-shutdown timer + script = '' + systemctl start minecraft-server.service + systemctl start stop-minecraft.timer + ''; + + # Keep checking until the service is available + postStart = '' + for i in $(seq 60); do + if ${pkgs.libressl.nc}/bin/nc -z 127.0.0.1 ${ + toString localPort + } > /dev/null ; then + exit 0 + fi + ${pkgs.busybox.out}/bin/sleep 1 + done + exit 1 + ''; + }; + + # Run a player check on a schedule for auto-shutdown + systemd.timers.stop-minecraft = { + timerConfig = { + OnCalendar = "*-*-* *:*:0/20"; # Every 20 seconds + Unit = "stop-minecraft.service"; + }; + wantedBy = [ "timers.target" ]; + }; + + # If no players are connected, then stop services and prepare to resume again + systemd.services.stop-minecraft = { + serviceConfig.Type = "oneshot"; + script = '' + # Check when service was launched + servicestartsec=$( + date -d \ + "$(systemctl show \ + --property=ActiveEnterTimestamp \ + minecraft-server.service \ + | cut -d= -f2)" \ + +%s) + + # Calculate elapsed time + serviceelapsedsec=$(( $(date +%s) - servicestartsec)) + + # Ignore if service just started + if [ $serviceelapsedsec -lt 180 ] + then + echo "Server was just started" + exit 0 + fi + + PLAYERS=$( + printf "list\n" \ + | ${pkgs.rcon.out}/bin/rcon -m \ + -H 127.0.0.1 -p ${builtins.toString rconPort} -P ${rconPassword} \ + ) + + if echo "$PLAYERS" | grep "are 0 of a" + then + echo "Stopping server" + systemctl stop minecraft-server.service + systemctl stop hook-minecraft.service + systemctl stop stop-minecraft.timer + fi + ''; + }; + }