release: yet another Nix Minecraft Module
This commit is contained in:
parent
9fdb947c2a
commit
839bb7de43
13 changed files with 1621 additions and 1 deletions
474
minecraft/minecraft.nix
Normal file
474
minecraft/minecraft.nix
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
# Enhanced Minecraft Server Module
|
||||
#
|
||||
# This module provides:
|
||||
# - A wrapper around the Infinidoge/nix-minecraft module
|
||||
# - Declarative configuration for multiple servers, including memory, operators, whitelist, and symlinks
|
||||
# - Automatic systemd service and timer generation for scheduled jobs
|
||||
# - Prebuilt helper scripts (rcon, query, backup, say, backup-routine) for server administration
|
||||
# - Support for backups, logging, and scheduled maintenance tasks
|
||||
#
|
||||
# Configuration Options:
|
||||
# enable – Enable the enhanced Minecraft servers (boolean)
|
||||
# user – System user that owns and runs the servers (string) - DON'T CHANGE THIS
|
||||
# group – System group that owns and runs the servers (string) - DON'T CHANGE THIS
|
||||
# dataDir – Directory to store Minecraft server data (path)
|
||||
# servers – Attribute set of servers, keyed by name. Each server can define:
|
||||
# memory.min / memory.max – JVM memory allocation (strings, e.g. "2G")
|
||||
# package – Minecraft server package to use (package)
|
||||
# autoStart – Start server at boot (boolean)
|
||||
# whitelist – Declarative whitelist (UUIDs per user)
|
||||
# operators – Declarative operator list with permission levels
|
||||
# symlinks – Files or packages symlinked into the server data directory
|
||||
# properties – Declarative `server.properties` values (ports, motd, difficulty, etc.)
|
||||
# schedules – Declarative scheduled jobs with systemd timers and services
|
||||
#
|
||||
#
|
||||
# Info:
|
||||
# I am happy to help if you have Issues and am happy to see a PR for any change
|
||||
# I ll use it personally too so you can expect frequent updates
|
||||
#
|
||||
#
|
||||
# ToDo:
|
||||
# Schedule to restart a server using systemD. I have not figured that out yet.
|
||||
# what i do know is that `sudo` does not work neither does it work if you just tell the server to restart.
|
||||
#
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.nyx-minecraft.service;
|
||||
minecraftUUID = types.strMatching "[0-9a-fA-F-]{36}";
|
||||
|
||||
# Setup the Scripts for the server
|
||||
scriptDir = ./Scripts;
|
||||
mkScript = serverName: serverCfg: scriptType: let
|
||||
templateFile = scriptDir + "/minecraft-template-${scriptType}.sh";
|
||||
rawText = builtins.readFile templateFile;
|
||||
scriptText =
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"@DATA_DIR@"
|
||||
"@RSYNC_BIN@"
|
||||
"@MCSTATUS_BIN@"
|
||||
"@MCRCON_BIN@"
|
||||
"@AWK_BIN@"
|
||||
"@QUERY_PORT@"
|
||||
"@RCON_PORT@"
|
||||
"@RCON_PASSWORD@"
|
||||
"@SERVER_NAME@"
|
||||
"@TAR_BIN@"
|
||||
"@ZIP_BIN@"
|
||||
"@UNZIP_BIN@"
|
||||
"@GZIP_BIN@"
|
||||
"@ZSTD_BIN@"
|
||||
"@PV_BIN@"
|
||||
"@DU_BIN@"
|
||||
"@BZIP2_BIN@"
|
||||
"@XZ_BIN@"
|
||||
]
|
||||
# If you add anything here make sure to add it at the systemd service too
|
||||
[
|
||||
cfg.dataDir
|
||||
"${pkgs.rsync}/bin/rsync"
|
||||
"${pkgs.mcstatus}/bin/mcstatus"
|
||||
"${pkgs.mcrcon}/bin/mcrcon"
|
||||
"${pkgs.gawk}/bin/awk"
|
||||
(toString (serverCfg.properties.serverPort + 200))
|
||||
(toString (serverCfg.properties.serverPort + 100))
|
||||
serverCfg.properties.rconPassword
|
||||
serverName
|
||||
"${pkgs.gnutar}/bin/tar"
|
||||
"${pkgs.zip}/bin/zip"
|
||||
"${pkgs.unzip}/bin/unzip"
|
||||
"${pkgs.gzip}/bin/gzip"
|
||||
"${pkgs.zstd}/bin/zstd"
|
||||
"${pkgs.pv}/bin/pv"
|
||||
"${pkgs.coreutils}/bin/du"
|
||||
"${pkgs.bzip2}/bin/bzip2"
|
||||
"${pkgs.xz}/bin/xz"
|
||||
]
|
||||
rawText;
|
||||
in
|
||||
pkgs.writeShellScriptBin "minecraft-${serverName}-${scriptType}" scriptText;
|
||||
in {
|
||||
# Note most of the options get directly exposed
|
||||
# to nix-minecraft which makes it almost an
|
||||
# Drop in replacement
|
||||
options.nyx-minecraft.service = {
|
||||
enable = mkEnableOption "Enable enhanced Minecraft servers with backup, logging, and admin helpers.";
|
||||
|
||||
eula = mkEnableOption ''
|
||||
Whether you agree to
|
||||
<link xlink:href="https://account.mojang.com/documents/minecraft_eula">
|
||||
Mojang's EULA</link>. This option must be set to
|
||||
<literal>true</literal> to run Minecraft server.
|
||||
'';
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "minecraft";
|
||||
description = ''
|
||||
Name of the user to create and run servers under.
|
||||
It is recommended to leave this as the default, as it is
|
||||
the same user as <option>services.minecraft-server</option>.
|
||||
'';
|
||||
internal = true;
|
||||
visible = false;
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "minecraft";
|
||||
description = ''
|
||||
Name of the group to create and run servers under.
|
||||
In order to modify the server files your user must be a part of this
|
||||
group. If you are using the tmux management system (the default), you also need to be a part of this group to attach to the tmux socket.
|
||||
It is recommended to leave this as the default, as it is
|
||||
the same group as <option>services.minecraft-server</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/srv/minecraft";
|
||||
description = ''
|
||||
Directory to store the Minecraft servers.
|
||||
Each server will be under a subdirectory named after
|
||||
the server name in this directory, such as <literal>/srv/minecraft/servername</literal>. '';
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
options = {
|
||||
enable = mkEnableOption "Enable this Server";
|
||||
|
||||
memory = mkOption {
|
||||
type = types.submodule {
|
||||
options = {
|
||||
min = mkOption {
|
||||
type = types.str;
|
||||
default = "2G";
|
||||
description = "Min JVM memory.";
|
||||
};
|
||||
max = mkOption {
|
||||
type = types.str;
|
||||
default = "2G";
|
||||
description = "Max JVM memory.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {
|
||||
min = "2G";
|
||||
max = "2G";
|
||||
};
|
||||
description = "JVM memory settings for this server.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
description = "The Minecraft server package to use.";
|
||||
type = types.package;
|
||||
default = pkgs.minecraft-server;
|
||||
defaultText = literalExpression "pkgs.minecraft-server";
|
||||
example = "pkgs.minecraftServers.vanilla-1_18_2";
|
||||
};
|
||||
|
||||
autoStart = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to start this server on boot.
|
||||
If set to <literal>false</literal>, it can still be started with
|
||||
<literal>systemctl start minecraft-server-servername</literal>.
|
||||
Requires the server to be enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
whitelist = mkOption {
|
||||
type = types.attrsOf minecraftUUID;
|
||||
default = {};
|
||||
description = ''
|
||||
Whitelisted players, only has an effect when
|
||||
enabled via <option>services.minecraft-servers.<name>.serverProperties</option>
|
||||
by setting <literal>white-list</literal> to <literal>true</literal>.
|
||||
|
||||
To use a non-declarative whitelist, enable the whitelist and don't fill in this value.
|
||||
As long as it is empty, no whitelist file is generated.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||
username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
symlinks = mkOption {
|
||||
type = types.attrsOf (types.either types.path types.package);
|
||||
default = {};
|
||||
description = ''
|
||||
Things to symlink into this server's data directory.
|
||||
Can be used to declaratively manage arbitrary files (e.g., mods, configs).
|
||||
'';
|
||||
};
|
||||
|
||||
operators = mkOption {
|
||||
type = types.attrsOf (
|
||||
types.coercedTo minecraftUUID (v: {uuid = v;}) (
|
||||
types.submodule {
|
||||
options = {
|
||||
uuid = mkOption {
|
||||
type = minecraftUUID;
|
||||
description = "The operator's UUID";
|
||||
example = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
|
||||
};
|
||||
level = mkOption {
|
||||
type = types.ints.between 0 4;
|
||||
description = "The operator's permission level";
|
||||
default = 4;
|
||||
};
|
||||
bypassesPlayerLimit = mkOption {
|
||||
type = types.bool;
|
||||
description = "If true, the operator can join the server even if the player limit has been reached";
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
default = {};
|
||||
description = "Operators with permission levels.";
|
||||
};
|
||||
|
||||
properties = mkOption {
|
||||
type = types.submodule {
|
||||
options = {
|
||||
serverPort = mkOption {
|
||||
type = types.int;
|
||||
default = 25565;
|
||||
description = "Server Port";
|
||||
};
|
||||
difficulty = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = "Difficulty in numbers: 0=Peaceful, 1=Easy, 2=Normal, 3=Hard";
|
||||
};
|
||||
gamemode = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = "Gamemode: 0=Survival, 1=Creative";
|
||||
};
|
||||
maxPlayers = mkOption {
|
||||
type = types.int;
|
||||
default = 5;
|
||||
description = "How many players can join the server";
|
||||
};
|
||||
motd = mkOption {
|
||||
type = types.str;
|
||||
default = "NixOS Minecraft server!";
|
||||
description = "Message displayed when selecting the server";
|
||||
};
|
||||
rconPassword = mkOption {
|
||||
type = types.str;
|
||||
default = "superSecret";
|
||||
description = "Password for Rcon";
|
||||
};
|
||||
hardcore = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable Hardcore mode";
|
||||
};
|
||||
levelSeed = mkOption {
|
||||
type = types.str;
|
||||
default = "42";
|
||||
description = "World seed (default is the answer to the universe)";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
description = "Declarative Minecraft server.properties values.";
|
||||
};
|
||||
|
||||
schedules = mkOption {
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether this schedule is active.";
|
||||
};
|
||||
|
||||
timer = mkOption {
|
||||
type = types.str;
|
||||
example = "hourly";
|
||||
description = "Systemd timer unit specifier (e.g., hourly, daily, weekly).";
|
||||
};
|
||||
|
||||
code = mkOption {
|
||||
type = types.lines;
|
||||
description = "Shell code to execute when the schedule fires.";
|
||||
};
|
||||
|
||||
# Not properly enough tested
|
||||
#customEnviorment = mkOption {
|
||||
# type = types.lines;
|
||||
# description = "to expose binaries or other things to the SystemD service";
|
||||
# example = "ZSTD_BIN=${pkgs.zstd}/bin/zstd";
|
||||
#};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
description = "Scheduled jobs for this Minecraft server.";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = {};
|
||||
description = "Servers to run under the enhanced Minecraft service.";
|
||||
};
|
||||
};
|
||||
|
||||
# Wrapper for nix-minecraft
|
||||
config = mkIf cfg.enable {
|
||||
services.minecraft-servers = {
|
||||
enable = true;
|
||||
eula = cfg.eula;
|
||||
openFirewall = true;
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
dataDir = cfg.dataDir;
|
||||
|
||||
servers =
|
||||
lib.mapAttrs (serverName: serverCfg: {
|
||||
enable = serverCfg.enable;
|
||||
package = serverCfg.package;
|
||||
jvmOpts = "-Xmx${serverCfg.memory.max} -Xms${serverCfg.memory.min}";
|
||||
autoStart = serverCfg.autoStart;
|
||||
|
||||
symlinks = serverCfg.symlinks;
|
||||
whitelist = serverCfg.whitelist;
|
||||
operators = serverCfg.operators;
|
||||
|
||||
serverProperties = {
|
||||
enable-rcon = true;
|
||||
enable-command-block = true;
|
||||
allow-flight = true;
|
||||
enable-query = true;
|
||||
server-port = serverCfg.properties.serverPort;
|
||||
difficulty = serverCfg.properties.difficulty;
|
||||
gamemode = serverCfg.properties.gamemode;
|
||||
max-players = serverCfg.properties.maxPlayers;
|
||||
motd = serverCfg.properties.motd;
|
||||
"rcon.password" = serverCfg.properties.rconPassword;
|
||||
"rcon.port" = serverCfg.properties.serverPort + 100;
|
||||
"query.port" = serverCfg.properties.serverPort + 200;
|
||||
|
||||
hardcore = serverCfg.properties.hardcore;
|
||||
level-seed = serverCfg.properties.levelSeed;
|
||||
};
|
||||
})
|
||||
cfg.servers;
|
||||
};
|
||||
|
||||
# Schedule logic
|
||||
systemd.services = lib.mkMerge (
|
||||
lib.mapAttrsToList (
|
||||
serverName: serverCfg:
|
||||
lib.mapAttrs' (scheduleName: scheduleCfg: let
|
||||
# yes this will be building the scripts twice but thsi
|
||||
# way the path is accessible by the SystemD service
|
||||
rconBin = mkScript serverName serverCfg "rcon";
|
||||
queryBin = mkScript serverName serverCfg "query";
|
||||
backupBin = mkScript serverName serverCfg "backup";
|
||||
sayBin = mkScript serverName serverCfg "say";
|
||||
routineBin = mkScript serverName serverCfg "backup-routine";
|
||||
in {
|
||||
name = "minecraft-${serverName}-${scheduleName}";
|
||||
value = {
|
||||
description = "Minecraft ${serverName} scheduled job: ${scheduleName}";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Environment = [
|
||||
"RCON_BIN=${rconBin}/bin/minecraft-${serverName}-rcon"
|
||||
"QUERY_BIN=${queryBin}/bin/minecraft-${serverName}-query"
|
||||
"BACKUP_BIN=${backupBin}/bin/minecraft-${serverName}-backup"
|
||||
"SAY_BIN=${sayBin}/bin/minecraft-${serverName}-say"
|
||||
"ROUTINE_BIN=${routineBin}/bin/minecraft-${serverName}-backup-routine"
|
||||
"ZSTD_BIN=${pkgs.zstd}/bin/zstd"
|
||||
# add more bin here
|
||||
|
||||
|
||||
|
||||
# Not properly enough tested
|
||||
#
|
||||
#scheduleCfg.customEnviorment
|
||||
];
|
||||
ExecStart = pkgs.writeShellScript "minecraft-${serverName}-${scheduleName}.sh" ''
|
||||
#!/usr/bin/env bash
|
||||
echo "hi — available helpers:"
|
||||
echo "if you want any custom scripts or"
|
||||
echo "packages for your script you need to ExecStart"
|
||||
echo " $RCON_BIN"
|
||||
echo " $QUERY_BIN"
|
||||
echo " $BACKUP_BIN"
|
||||
echo " $SAY_BIN"
|
||||
echo " $ROUTINE_BIN"
|
||||
|
||||
# this is so it can use the scripts the same way you would run them:
|
||||
minecraft-${serverName}-query() { $QUERY_BIN "$@"; }
|
||||
minecraft-${serverName}-rcon() { $RCON_BIN "$@"; }
|
||||
minecraft-${serverName}-backup() { $BACKUP_BIN "$@"; }
|
||||
minecraft-${serverName}-say() { $SAY_BIN "$@"; }
|
||||
minecraft-${serverName}-backup-routine() { $ROUTINE_BIN "$@"; }
|
||||
minecraft-${serverName}-query
|
||||
|
||||
# her your code will go:
|
||||
${scheduleCfg.code}
|
||||
|
||||
'';
|
||||
};
|
||||
|
||||
# If you have this on:
|
||||
# wantedBy = [ "multi-user.target" ];
|
||||
# it will run the service on each rebuild.
|
||||
# i dont want that you can enable it if you like
|
||||
};
|
||||
}) serverCfg.schedules
|
||||
)
|
||||
cfg.servers
|
||||
);
|
||||
|
||||
# the timers to actually run the SystemD service
|
||||
systemd.timers = lib.mkMerge (
|
||||
lib.mapAttrsToList (
|
||||
serverName: serverCfg:
|
||||
lib.mapAttrs' (scheduleName: scheduleCfg: {
|
||||
name = "minecraft-${serverName}-${scheduleName}";
|
||||
value = {
|
||||
description = "Timer for Minecraft ${serverName} schedule ${scheduleName}";
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig.OnCalendar = scheduleCfg.timer;
|
||||
};
|
||||
})
|
||||
serverCfg.schedules
|
||||
)
|
||||
cfg.servers
|
||||
);
|
||||
|
||||
# this is building the scripts for the user
|
||||
# Those are the prewritten scripts from the ./Script dir
|
||||
environment.systemPackages = lib.flatten (
|
||||
lib.mapAttrsToList (serverName: serverCfg: [
|
||||
(mkScript serverName serverCfg "rcon")
|
||||
(mkScript serverName serverCfg "query")
|
||||
(mkScript serverName serverCfg "backup")
|
||||
(mkScript serverName serverCfg "say")
|
||||
(mkScript serverName serverCfg "backup-routine")
|
||||
])
|
||||
cfg.servers
|
||||
);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue