533 lines
17 KiB
Nix
533 lines
17 KiB
Nix
{ config, lib, options, pkgs, ... }:
|
|
with lib;
|
|
let
|
|
papermc = pkgs.callPackage ../packages/minecraft/paper.nix { };
|
|
cfg = config.services.minecraft;
|
|
opt = options.services.minecraft;
|
|
serverProperties = pkgs.writeText "server.properties" ''
|
|
${generators.toKeyValue {} cfg.properties.extraConfig}
|
|
'';
|
|
whitelistJson = pkgs.writeText "whitelist.json" ''
|
|
${builtins.toJSON cfg.whitelist}
|
|
'';
|
|
bukkitYaml = pkgs.writeText "bukkit.yml" ''
|
|
${generators.toYAML {} cfg.bukkit-yml}
|
|
'';
|
|
in
|
|
{
|
|
options.services.minecraft = {
|
|
enable = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable minecraft server";
|
|
};
|
|
stateDir = mkOption {
|
|
default = "/var/lib/minecraft";
|
|
type = types.str;
|
|
description = "Path to the minecraft server state directory";
|
|
};
|
|
properties = {
|
|
allow-flight = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Allow flight";
|
|
};
|
|
allow-nether = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Allow nether";
|
|
};
|
|
broadcast-console-to-ops = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Broadcast console to ops";
|
|
};
|
|
broadcast-rcon-to-ops = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Broadcast rcon to ops";
|
|
};
|
|
difficulty = mkOption {
|
|
default = "normal";
|
|
type = types.enum [ "peacful" "easy" "normal" "hard" ];
|
|
description = "Difficulty";
|
|
};
|
|
enable-command-block = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable command block";
|
|
};
|
|
enable-jmx-monitoring = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable JMX monitoring";
|
|
};
|
|
enable-rcon = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable rcon";
|
|
};
|
|
sync-chunk-writes = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Sync chunk writes";
|
|
};
|
|
enable-status = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Enable status";
|
|
};
|
|
enable-query = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enable query";
|
|
};
|
|
entity-broadcast-range-percentage = mkOption {
|
|
default = 100;
|
|
type = types.ints.between 10 1000;
|
|
description = "Entity broadcast range percentage";
|
|
};
|
|
force-gamemode = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Force gamemode";
|
|
};
|
|
function-permission-level = mkOption {
|
|
default = 2;
|
|
type = types.ints.between 1 4;
|
|
description = "Function permission level";
|
|
};
|
|
gamemode = mkOption {
|
|
default = "survival";
|
|
type = types.enum [ "survival" "creative" "adventure" "spectator" ];
|
|
description = "Gamemode";
|
|
};
|
|
generate-structures = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Generate structures";
|
|
};
|
|
generator-settings = mkOption {
|
|
default = "";
|
|
type = types.oneOf [ types.str (types.attrsOf types.anything) ];
|
|
description = "Generator settings";
|
|
apply = val: if (builtins.isString val) then val else builtins.toJSON val;
|
|
};
|
|
hardcore = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Hardcore";
|
|
};
|
|
level-name = mkOption {
|
|
default = "world";
|
|
type = types.str;
|
|
description = "Level name";
|
|
};
|
|
level-seed = mkOption {
|
|
default = "";
|
|
type = types.str;
|
|
description = "Level seed";
|
|
};
|
|
level-type = mkOption {
|
|
default = "default";
|
|
type = types.enum [ "default" "flat" "largeBiomes" "amplified" ];
|
|
description = "Level type";
|
|
};
|
|
max-players = mkOption {
|
|
default = 20;
|
|
type = types.ints.unsigned;
|
|
description = "Max players";
|
|
};
|
|
max-tick-time = mkOption {
|
|
default = 60000;
|
|
type = types.int;
|
|
description = "Max tick time";
|
|
};
|
|
world-size = mkOption {
|
|
default = 29999984;
|
|
type = types.ints.between 1 29999984;
|
|
description = "World size";
|
|
};
|
|
motd = mkOption {
|
|
default = "A Minecraft server";
|
|
type = types.str;
|
|
description = "Message of the day";
|
|
};
|
|
network-compression-threshold = mkOption {
|
|
default = 256;
|
|
type = types.int;
|
|
description = "Network compression threshold";
|
|
};
|
|
online-mode = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Online mode";
|
|
};
|
|
permission-level = mkOption {
|
|
default = 4;
|
|
type = types.ints.between 0 4;
|
|
description = "Permission level";
|
|
};
|
|
player-idle-timeout = mkOption {
|
|
default = 0;
|
|
type = types.ints.unsigned;
|
|
description = "Player idle timeout";
|
|
};
|
|
prevent-proxy-connections = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Prevent proxy connections";
|
|
};
|
|
pvp = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "PvP";
|
|
};
|
|
query-port = mkOption {
|
|
default = 25565;
|
|
type = types.port;
|
|
description = "Query port";
|
|
};
|
|
rate-limit = mkOption {
|
|
default = 0;
|
|
type = types.ints.unsigned;
|
|
description = "Rate limit";
|
|
};
|
|
rcon-password-file = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.str;
|
|
description = "Rcon password file";
|
|
};
|
|
rcon-port = mkOption {
|
|
default = 25575;
|
|
type = types.port;
|
|
description = "Rcon port";
|
|
};
|
|
resource-pack = mkOption {
|
|
default = "";
|
|
type = types.str;
|
|
description = "Resource pack";
|
|
};
|
|
resource-pack-prompt = mkOption {
|
|
default = "";
|
|
type = types.str;
|
|
description = "Resource pack prompt";
|
|
};
|
|
resource-pack-sha1 = mkOption {
|
|
default = "";
|
|
type = types.str;
|
|
description = "Resource pack sha1";
|
|
};
|
|
require-resource-pack = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Require resource pack";
|
|
};
|
|
server-ip = mkOption {
|
|
default = "";
|
|
type = types.str;
|
|
description = "Server ip";
|
|
};
|
|
server-port = mkOption {
|
|
default = 25565;
|
|
type = types.port;
|
|
description = "Server port";
|
|
};
|
|
simulation-distance = mkOption {
|
|
default = 10;
|
|
type = types.ints.between 3 32;
|
|
description = "Simulation distance";
|
|
};
|
|
snooper-enabled = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Snooper enabled";
|
|
};
|
|
spawn-animals = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Enable animals";
|
|
};
|
|
spawn-monsters = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Enable monsters";
|
|
};
|
|
spawn-npcs = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Enable npcs";
|
|
};
|
|
spawn-protection = mkOption {
|
|
default = 16;
|
|
type = types.ints.between 0 256;
|
|
description = "Spawn protection";
|
|
};
|
|
use-native-transport = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Use native transport";
|
|
};
|
|
view-distance = mkOption {
|
|
default = 10;
|
|
type = types.ints.between 3 32;
|
|
description = "View distance";
|
|
};
|
|
white-list = mkOption {
|
|
default = cfg.whitelist != [ ];
|
|
type = types.bool;
|
|
description = "White list";
|
|
};
|
|
enforce-whitelist = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Enforce whitelist";
|
|
};
|
|
extraConfig = mkOption {
|
|
default = { };
|
|
type = types.attrsOf types.str;
|
|
description = "Extra configuration to be added to the minecraft server properties file";
|
|
};
|
|
};
|
|
bukkit-yml = mkOption {
|
|
description = "Bukkit configuration file";
|
|
type = types.submodule {
|
|
options = {
|
|
settings = mkOption {
|
|
descriptions = "General CraftBukkit settings";
|
|
type = types.submodule {
|
|
options = {
|
|
allow-end = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Allow end";
|
|
};
|
|
warn-on-overload = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Warn on overload";
|
|
};
|
|
permissions-file = mkOption {
|
|
default = "permissions.yml";
|
|
type = types.str;
|
|
description = "Permissions file";
|
|
};
|
|
update-folder = mkOption {
|
|
default = "update";
|
|
type = types.str;
|
|
description = "Update folder";
|
|
};
|
|
ping-packet-limit = mkOption {
|
|
default = 100;
|
|
type = types.ints.between 0 1000;
|
|
description = "Ping packet limit";
|
|
};
|
|
use-exact-login-location = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Use exact login location";
|
|
};
|
|
world-container = mkOption {
|
|
default = "world";
|
|
type = types.str;
|
|
description = "World container";
|
|
};
|
|
plugin-profiling = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Plugin profiling";
|
|
};
|
|
connection-throttle = mkOption {
|
|
default = 0;
|
|
type = types.ints.unsigned;
|
|
description = "Connection throttle";
|
|
};
|
|
query-plugins = mkOption {
|
|
default = true;
|
|
type = types.bool;
|
|
description = "Query plugins";
|
|
};
|
|
deprecated-verbose = mkOption {
|
|
default = "default";
|
|
type = types.enum [ true false "default" ];
|
|
description = "Deprecated verbose";
|
|
};
|
|
shutdown-message = mkOption {
|
|
default = "Server closed";
|
|
type = types.str;
|
|
description = "Shutdown message";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
spawn-limits = mkOption {
|
|
description = "Spawn limits";
|
|
type = types.submodule {
|
|
options = {
|
|
monsters = mkOption {
|
|
default = 70;
|
|
type = types.ints.unsigned;
|
|
description = "Max monsters";
|
|
};
|
|
animals = mkOption {
|
|
default = 15;
|
|
type = types.ints.unsigned;
|
|
description = "Max animals";
|
|
};
|
|
water-animals = mkOption {
|
|
default = 5;
|
|
type = types.ints.unsigned;
|
|
description = "Max water animals";
|
|
};
|
|
ambient = mkOption {
|
|
default = 15;
|
|
type = types.ints.unsigned;
|
|
description = "Max ambient";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
chunk-gc = mkOption {
|
|
description = "Chunk garbage collection";
|
|
type = types.submodule {
|
|
options = {
|
|
period-in-ticks = mkOption {
|
|
default = 600;
|
|
type = types.ints.unsigned;
|
|
description = "Period in ticks";
|
|
};
|
|
load-threshold = mkOption {
|
|
default = 0;
|
|
type = types.ints.unsigned;
|
|
description = "Load threshold";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
ticks-per = mkOption {
|
|
description = "Tick delays";
|
|
type = types.submodule {
|
|
options = {
|
|
animal-spawns = mkOption {
|
|
default = 400;
|
|
type = types.ints.unsigned;
|
|
description = "Animal spawns";
|
|
};
|
|
monster-spawns = mkOption {
|
|
default = 1;
|
|
type = types.ints.unsigned;
|
|
description = "Monster spawns";
|
|
};
|
|
autosave = mkOption {
|
|
default = 6000;
|
|
type = types.ints.unsigned;
|
|
description = "Autosave";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
whitelist = mkOption {
|
|
default = [ ];
|
|
type = types.listOf (types.submodule {
|
|
options = {
|
|
uuid = mkOption {
|
|
type = types.str;
|
|
description = "UUID of the whitelist entry";
|
|
};
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Name of the whitelist entry";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
};
|
|
config = mkIf cfg.enable {
|
|
services.minecraft.properties.extraConfig = with cfg.properties; lib.mkDefault {
|
|
inherit allow-flight allow-nether broadcast-console-to-ops broadcast-rcon-to-ops;
|
|
inherit difficulty enable-command-block enable-jmx-monitoring enable-rcon sync-chunk-writes;
|
|
inherit enable-status enable-query entity-broadcast-range-percentage force-gamemode;
|
|
inherit function-permission-level gamemode generate-structures generator-settings hardcore;
|
|
inherit level-name level-seed level-type max-players max-tick-time motd;
|
|
inherit network-compression-threshold online-mode permission-level player-idle-timeout;
|
|
inherit prevent-proxy-connections pvp rate-limit resource-pack resource-pack-prompt;
|
|
inherit resource-pack-sha1 require-resource-pack server-ip server-port;
|
|
inherit simulation-distance snooper-enabled spawn-animals spawn-monsters spawn-npcs;
|
|
inherit spawn-protection use-native-transport view-distance white-list enforce-whitelist;
|
|
|
|
"query.port" = queryPort;
|
|
"rcon.port" = rconPort;
|
|
};
|
|
systemd.services.minecraft = {
|
|
description = "Minecraft Server";
|
|
after = [ "network.target" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
path = [ papermc ];
|
|
preStart = ''
|
|
cd $HOME
|
|
# Agree to the EULA
|
|
echo "eula=true" > eula.txt
|
|
# Update the server properties
|
|
cat ${serverProperties} > server.properties
|
|
${if cfg.rcon-password-file != "" then ''
|
|
echo "rcon.password=$(cat ${cfg.rcon-password-file})" >> server.properties
|
|
'' else "" }
|
|
# Update the whitelist
|
|
cat ${whitelistJson} > whitelist.json
|
|
# Update the bukkit yml
|
|
cat ${bukkitYaml} > bukkit.yml
|
|
'';
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "minecraft";
|
|
Group = "minecraft";
|
|
WorkingDirectory = cfg.stateDir;
|
|
ExecStart = "${papermc}/bin/minecraft-server";
|
|
Restart = "always";
|
|
RuntimeDirectory = "minecraft";
|
|
RuntimeDirectoryMode = "0755";
|
|
UMask = "0027";
|
|
ReadWritePaths = [ cfg.stateDir ]; # Grant access to the state directory
|
|
CapabilityBoundingSet = "";
|
|
# Security
|
|
NoNewPrivileges = true;
|
|
# Sandboxing
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
PrivateTmp = true;
|
|
PrivateDevices = true;
|
|
PrivateUsers = true;
|
|
ProtectHostname = true;
|
|
ProtectClock = true;
|
|
ProtectKernelTunables = true;
|
|
ProtectKernelModules = true;
|
|
ProtectKernelLogs = true;
|
|
ProtectControlGroups = true;
|
|
RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
|
|
LockPersonality = true;
|
|
RestrictRealtime = true;
|
|
RestrictSUIDSGID = true;
|
|
PrivateMounts = true;
|
|
};
|
|
environment = {
|
|
USER = "minecraft";
|
|
HOME = cfg.stateDir;
|
|
};
|
|
};
|
|
users.users.minecraft = {
|
|
description = "Minecraft Server";
|
|
home = cfg.stateDir;
|
|
useDefaultShell = true;
|
|
group = "minecraft";
|
|
isSystemUser = true;
|
|
};
|
|
users.groups.minecraft = { };
|
|
systemd.tmpfiles.rules = [
|
|
"d '${cfg.stateDir}' 0750 minecraft minecraft - -"
|
|
];
|
|
};
|
|
}
|
|
|