bonfire/nixosModules/services/papermc.nix

259 lines
7.2 KiB
Nix
Raw Normal View History

{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.services.papermc;
eulaFile = builtins.toFile "eula.txt" ''
# eula.txt managed by NixOS Configuration
eula=true
'';
whitelistFile =
pkgs.writeText "whitelist.json"
(builtins.toJSON cfg.whitelist);
opsFile =
pkgs.writeText "ops.json"
(builtins.toJSON cfg.ops);
cfgToString = v:
if builtins.isBool v
then boolToString v
else toString v;
serverPropertiesFile = let
serverProperties' =
if (cfg.rconPasswordFile == null)
then cfg.serverProperties
else (removeAttrs cfg.serverProperties ["rcon.password"]);
in
pkgs.writeText "server.properties" (''
# server.properties managed by NixOS configuration
''
+ concatStringsSep "\n" (mapAttrsToList
(n: v: "${n}=${cfgToString v}")
serverProperties')
+ lib.optionalString (cfg.rconPasswordFile != null) "\nrcon.password=#rconpass#");
stopScript = pkgs.writeShellScript "minecraft-server-stop" ''
echo stop > ${config.systemd.sockets.papermc.socketConfig.ListenFIFO}
# Wait for the PID of the minecraft server to disappear before
# returning, so systemd doesn't attempt to SIGKILL it.
while kill -0 "$1" 2> /dev/null; do
sleep 1s
done
'';
defaultServerPort = 25565;
serverPort = cfg.serverProperties.server-port or defaultServerPort;
rconPort =
if cfg.serverProperties.enable-rcon or false
then cfg.serverProperties."rcon.port" or 25575
else null;
queryPort =
if cfg.serverProperties.enable-query or false
then cfg.serverProperties."query.port" or 25565
else null;
in {
options.services.papermc = {
enable = mkEnableOption "PaperMC service";
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Whether to open ports in the firewall for the server.
'';
};
2024-01-25 18:09:52 +05:00
eula = mkOption {
type = types.bool;
default = false;
description = ''
Whether you agree to [Mojangs EULA](https://account.mojang.com/documents/minecraft_eula).
This option must be set to `true` to run Minecraft server.
'';
};
2024-01-25 18:09:52 +05:00
dataDir = mkOption {
type = types.path;
default = "/var/lib/papermc";
description = ''
Directory to store Minecraft database and other state/data files.
'';
};
2024-01-25 18:09:52 +05:00
whitelist = mkOption {
type = types.listOf types.attrs;
default = {};
description = ''
This is a mapping from Minecraft usernames to UUIDs.
'';
};
2024-01-25 18:09:52 +05:00
ops = mkOption {
type = types.listOf types.attrs;
description = "Whitelist with players / operators.";
default = [];
};
2024-01-25 18:09:52 +05:00
serverProperties = mkOption {
type = with types; attrsOf (oneOf [bool int str]);
default = {
"rcon.password" = mkIf (cfg.rconPasswordFile != null) "#rconpass#";
};
example = literalExpression ''
{
server-port = 43000;
difficulty = 3;
gamemode = 1;
max-players = 5;
motd = "NixOS Minecraft server!";
white-list = true;
enable-rcon = true;
"rcon.password" = "hunter2";
}
'';
description = ''
Minecraft server properties for the server.properties file. See
<https://minecraft.gamepedia.com/Server.properties#Java_Edition_3>
for documentation on these values.
'';
};
2024-01-25 18:09:52 +05:00
rconPasswordFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "Path to file with rcon password.";
example = "/var/lib/secrets/papermc/rconpw";
};
2024-01-25 18:09:52 +05:00
package = mkPackageOption pkgs "papermc" {};
jvmOpts = mkOption {
type = types.separatedString " ";
default = "-Xmx2048M -Xms2048M";
# Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
example =
"-Xms4092M -Xmx4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+ "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+ "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
description = "JVM options for the Minecraft server.";
};
2024-01-25 18:09:52 +05:00
extraPreStart = mkOption {
type = types.lines;
description = "Extra shell commands for service pre-start hook.";
default = '''';
};
};
config = mkIf cfg.enable {
users.users.papermc = {
description = "Minecraft server service user.";
home = cfg.dataDir;
createHome = true;
isSystemUser = true;
group = "papermc";
};
users.groups.papermc = {};
systemd.sockets.papermc = {
bindsTo = ["papermc.service"];
socketConfig = {
ListenFIFO = "/run/papermc.stdin";
SocketMode = "0660";
SocketUser = "papermc";
SocketGroup = "papermc";
RemoveOnStop = true;
FlushPending = true;
};
};
2024-01-25 18:09:52 +05:00
systemd.services.papermc = {
description = "PaperMC Service";
wantedBy = ["multi-user.target"];
requires = ["papermc.socket"];
after = ["network.target" "papermc.socket"];
serviceConfig = {
ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
ExecStop = "${stopScript} $MAINPID";
Restart = "always";
User = "papermc";
WorkingDirectory = cfg.dataDir;
StandardInput = "socket";
StandardOutput = "journal";
StandardError = "journal";
# Hardening
CapabilityBoundingSet = [""];
DeviceAllow = [""];
LockPersonality = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
RestrictAddressFamilies = ["AF_INET" "AF_INET6"];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
UMask = "0077";
};
preStart = let
replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
in
''
ln -sf ${eulaFile} eula.txt
cp -b --suffix=.stateful ${whitelistFile} whitelist.json
cp -b --suffix=.stateful ${opsFile} ops.json
cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
chmod +w whitelist.json ops.json server.properties
${lib.optionalString (cfg.rconPasswordFile != null) ''
${replaceSecretBin} '#rconpass#' '${cfg.rconPasswordFile}' server.properties
''}
''
+ cfg.extraPreStart;
2024-01-25 18:09:52 +05:00
};
networking.firewall = mkIf cfg.openFirewall {
allowedUDPPorts = [serverPort];
allowedTCPPorts =
[serverPort]
++ optional (queryPort != null) queryPort
++ optional (rconPort != null) rconPort;
2024-01-25 18:09:52 +05:00
};
assertions = [
{
assertion = cfg.eula;
message =
"You must agree to Mojangs EULA to run minecraft-server."
+ " Read https://account.mojang.com/documents/minecraft_eula and"
+ " set `services.minecraft-server.eula` to `true` if you agree.";
}
];
};
2024-01-25 18:09:52 +05:00
}