feat(minecraft): bedrock server

This commit is contained in:
arcnmx 2024-04-27 13:30:33 -07:00
parent f05b50b53e
commit 9f98934a17
11 changed files with 383 additions and 11 deletions

View file

@ -0,0 +1,153 @@
{ config, lib, pkgs, ... }: let
# see https://gist.github.com/datakurre/cfdf627fb23ed8ff62bb7b3520b92674
inherit (lib.options) mkOption mkPackageOption;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) concatStringsSep;
inherit (lib.trivial) boolToString;
cfg = config.services.minecraft-bedrock-server;
cfgToString = v: if builtins.isBool v then boolToString v else toString v;
serverPropertiesFile = pkgs.writeText "server.properties" (''
# server.properties managed by NixOS configuration
'' + concatStringsSep "\n" (mapAttrsToList
(n: v: "${n}=${cfgToString v}") cfg.serverProperties));
in {
options.services.minecraft-bedrock-server = with lib.types; {
enable = mkOption {
type = bool;
default = false;
description = ''
If enabled, start a Minecraft Bedrock Server. The server
data will be loaded from and saved to
<option>services.minecraft-bedrock-server.dataDir</option>.
'';
};
dataDir = mkOption {
type = path;
default = "/var/lib/minecraft-bedrock";
description = ''
Directory to store Minecraft Bedrock database and other state/data files.
'';
};
serverProperties = mkOption {
type = attrsOf (oneOf [ bool int str float ]);
default = {
server-name = "Dedicated Server";
gamemode = "survival";
difficulty = "easy";
allow-cheats = false;
max-players = 10;
online-mode = false;
white-list = false;
server-port = 19132;
server-portv6 = 19133;
view-distance = 32;
tick-distance = 4;
player-idle-timeout = 30;
max-threads = 8;
level-name = "Bedrock level";
level-seed = "";
default-player-permission-level = "member";
texturepack-required = false;
content-log-file-enabled = false;
compression-threshold = 1;
server-authoritative-movement = "server-auth";
player-movement-score-threshold = 20;
player-movement-distance-threshold = 0.3;
player-movement-duration-threshold-in-ms = 500;
correct-player-movement = false;
};
example = literalExample ''
{
server-name = "Dedicated Server";
gamemode = "survival";
difficulty = "easy";
allow-cheats = false;
max-players = 10;
online-mode = false;
white-list = false;
server-port = 19132;
server-portv6 = 19133;
view-distance = 32;
tick-distance = 4;
player-idle-timeout = 30;
max-threads = 8;
level-name = "Bedrock level";
level-seed = "";
default-player-permission-level = "member";
texturepack-required = false;
content-log-file-enabled = false;
compression-threshold = 1;
server-authoritative-movement = "server-auth";
player-movement-score-threshold = 20;
player-movement-distance-threshold = 0.3;
player-movement-duration-threshold-in-ms = 500;
correct-player-movement = false;
}
'';
description = ''
Minecraft Bedrock server properties for the server.properties file.
'';
};
package = mkPackageOption pkgs "minecraft-bedrock-server" { }// {
description = "Version of minecraft-bedrock-server to run.";
};
openFirewall = mkOption {
type = bool;
default = false;
};
user = mkOption {
type = str;
default = "minecraft-bedrock";
};
group = mkOption {
type = str;
default = cfg.user;
};
};
config = mkIf cfg.enable {
users.users.${cfg.user} = {
inherit (cfg) group;
description = "Minecraft server service user";
home = cfg.dataDir;
createHome = true;
isSystemUser = true;
};
users.groups.${cfg.group} = {};
systemd.services.minecraft-bedrock-server = {
description = "Minecraft Bedrock Server Service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStart = [
"${cfg.package}/bin/bedrock_server"
];
Restart = "always";
User = cfg.user;
WorkingDirectory = cfg.dataDir;
};
preStart = ''
cp -a -n ${cfg.package}/var/lib/* .
cp -f ${serverPropertiesFile} server.properties
chmod +w server.properties
'';
};
networking.firewall = let
ports = [ cfg.serverProperties.server-port cfg.serverProperties.server-portv6 ];
in mkIf cfg.openFirewall {
allowedUDPPorts = ports;
};
};
}

View file

@ -0,0 +1,40 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.minecraft-bedrock-server = { config, ... }: let
mkAssertion = f: nixosConfig: let
cfg = nixosConfig.services.minecraft-bedrock-server;
in f nixosConfig cfg;
in {
nixos = {
serviceAttr = "minecraft-bedrock-server";
assertions = mkIf config.enable [
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.default.port == cfg.serverProperties.server-port;
message = "server-port mismatch";
}))
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.v6.port == cfg.serverProperties.server-portv6;
message = "server-portv6 mismatch";
}))
];
};
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 19132;
transport = "udp";
};
tcp = {
port = config.ports.default.port;
transport = "tcp";
};
v6 = {
port = 19133;
transport = "udp";
};
};
};
}

View file

@ -1,17 +1,18 @@
{ {
config, config,
lib, lib,
inputs, access,
gensokyo-zone,
... ...
}: let }: let
inherit (lib.options) mkOption; inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkBefore mkDefault mkForce; inherit (lib.modules) mkIf mkMerge mkBefore mkDefault mkForce;
inherit (lib.attrsets) filterAttrs mapAttrsToList nameValuePair listToAttrs; inherit (lib.attrsets) filterAttrs mapAttrsToList nameValuePair listToAttrs;
inherit (lib.lists) filter concatLists; inherit (lib.lists) filter optional singleton concatLists;
inherit (lib.strings) hasPrefix replaceStrings concatStringsSep; inherit (lib.strings) hasPrefix replaceStrings concatStringsSep;
inherit (lib.trivial) mapNullable; inherit (lib.trivial) mapNullable flip;
cfg = config.services.dnsmasq; cfg = config.services.dnsmasq;
inherit (inputs.self.lib) systems; inherit (gensokyo-zone) systems;
localSystems = filterAttrs (_: system: localSystems = filterAttrs (_: system:
system.config.access.online.enable && system.config.network.networks.local.enable or false system.config.access.online.enable && system.config.network.networks.local.enable or false
) systems; ) systems;
@ -27,11 +28,11 @@
address6 = system.config.network.networks.local.address6 or null; address6 = system.config.network.networks.local.address6 or null;
in concatStringsSep "," ([ in concatStringsSep "," ([
system.config.access.fqdn system.config.access.fqdn
] ++ lib.optional (address4 != null) ] ++ optional (address4 != null)
(toString (mapNullable mapDynamic4 address4)) (toString (mapNullable mapDynamic4 address4))
++ lib.optional (address6 != null) ++ optional (address6 != null)
(toString (mapNullable mapDynamic6 address6)) (toString (mapNullable mapDynamic6 address6))
++ lib.singleton ++ singleton
cfg.dynamic.interface cfg.dynamic.interface
); );
mkHostRecordPair = network: system: let mkHostRecordPair = network: system: let
@ -41,9 +42,9 @@
in nameValuePair in nameValuePair
(if fqdn != null then fqdn else "${network}.${system.config.access.fqdn}") (if fqdn != null then fqdn else "${network}.${system.config.access.fqdn}")
(concatStringsSep "," ( (concatStringsSep "," (
lib.optional (address4 != null) optional (address4 != null)
(toString address4) (toString address4)
++ lib.optional (address6 != null) ++ optional (address6 != null)
(toString address6) (toString address6)
)); ));
systemHosts = filterAttrs (_: value: value != "") ( systemHosts = filterAttrs (_: value: value != "") (
@ -63,13 +64,38 @@ in {
type = str; type = str;
default = config.systemd.network.networks._00-local.name or "eth0"; default = config.systemd.network.networks._00-local.name or "eth0";
}; };
bedrockConnect = {
address = mkOption {
type = nullOr str;
};
address6 = mkOption {
type = nullOr str;
};
};
}; };
config = { config = {
services.dnsmasq = { services.dnsmasq = {
enable = mkDefault true; enable = mkDefault true;
resolveLocalQueries = mkForce false; resolveLocalQueries = mkForce false;
settings = { settings = {
host-record = mapAttrsToList mkHostRecord systemHosts; host-record = let
bedrockRecord = concatStringsSep "," (
optional (cfg.bedrockConnect.address != null) cfg.bedrockConnect.address
++ optional (cfg.bedrockConnect.address6 != null) cfg.bedrockConnect.address6
);
bedrockRecordNames = [
# https://github.com/Pugmatt/BedrockConnect?tab=readme-ov-file#using-your-own-dns-server
"geo.hivebedrock.network"
"hivebedrock.network"
"play.inpvp.net"
"mco.lbsg.net"
"play.galaxite.net"
];
bedrockRecords = map (flip mkHostRecord bedrockRecord) bedrockRecordNames;
in mkMerge [
(mapAttrsToList mkHostRecord systemHosts)
(mkIf (cfg.bedrockConnect.address != null || cfg.bedrockConnect.address6 != null) bedrockRecords)
];
dynamic-host = mapAttrsToList mkDynamicHostRecord localSystems; dynamic-host = mapAttrsToList mkDynamicHostRecord localSystems;
server = server =
if config.networking.nameservers' != [ ] then map (ns: ns.address) (filter filterns' config.networking.nameservers') if config.networking.nameservers' != [ ] then map (ns: ns.address) (filter filterns' config.networking.nameservers')
@ -77,6 +103,12 @@ in {
; ;
max-cache-ttl = 60; max-cache-ttl = 60;
}; };
bedrockConnect = let
system = access.systemForService "minecraft-bedrock-server";
in {
address = mkDefault (access.getAddress4For system.name "local");
address6 = mkDefault (access.getAddress6For system.name "local");
};
}; };
services.resolved = mkIf cfg.enable { services.resolved = mkIf cfg.enable {
extraConfig = '' extraConfig = ''

View file

@ -0,0 +1,43 @@
{config, lib, ...}: let
inherit (lib.modules) mkIf mkDefault;
cfg = config.services.minecraft-bedrock-server;
in {
services.minecraft-bedrock-server = {
enable = mkDefault true;
serverProperties = {
server-name = "Kat's Server";
gamemode = "survival";
difficulty = "easy";
allow-cheats = false;
max-players = 10;
online-mode = true;
white-list = false;
server-port = 19132;
server-portv6 = 19133;
view-distance = 32;
tick-distance = 4;
player-idle-timeout = 30;
max-threads = 8;
level-name = "Bedrock level";
level-seed = "";
default-player-permission-level = "member";
texturepack-required = false;
content-log-file-enabled = false;
compression-threshold = 1;
server-authoritative-movement = "server-auth";
player-movement-score-threshold = 20;
player-movement-distance-threshold = 0.3;
player-movement-duration-threshold-in-ms = 500;
correct-player-movement = false;
};
};
users = mkIf cfg.enable {
users.${cfg.user}.uid = 913;
groups.${cfg.group}.gid = config.users.users.${cfg.user}.uid;
};
networking.firewall.interfaces.local = let
ports = [ cfg.serverProperties.server-port cfg.serverProperties.server-portv6 ];
in mkIf cfg.enable {
allowedUDPPorts = ports;
};
}

View file

@ -6,6 +6,7 @@
in rec { in rec {
default = nixlib.composeManyExtensions [ default = nixlib.composeManyExtensions [
barcodebuddy barcodebuddy
minecraft
krb5 krb5
nfs nfs
nginx nginx
@ -13,6 +14,7 @@ in rec {
]; ];
barcodebuddy = import ./barcodebuddy.nix; barcodebuddy = import ./barcodebuddy.nix;
krb5 = import ./krb5.nix; krb5 = import ./krb5.nix;
minecraft = import ./minecraft.nix;
nfs = import ./nfs.nix; nfs = import ./nfs.nix;
nginx = import ./nginx.nix; nginx = import ./nginx.nix;
samba = import ./samba.nix; samba = import ./samba.nix;

57
overlays/minecraft.nix Normal file
View file

@ -0,0 +1,57 @@
final: prev: let
inherit (final) lib;
in {
minecraft-bedrock-server-libCrypto = let
inherit (final) minecraft-bedrock-server;
in minecraft-bedrock-server.stdenv.mkDerivation {
pname = "${minecraft-bedrock-server.pname}-libcrypto";
inherit (minecraft-bedrock-server) version src sourceRoot;
nativeBuildInputs = with final; [
autoPatchelfHook
curl
gcc-unwrapped
openssl
unzip
];
installPhase = ''
install -m755 -D libCrypto.so $out/lib/libCrypto.so
'';
fixupPhase = ''
autoPatchelf $out/lib/libCrypto.so
'';
meta.broken = true;
};
minecraft-bedrock-server-patchdebug = let
# https://github.com/minecraft-linux/server-modloader/tree/master?tab=readme-ov-file#getting-mods-to-work-on-newer-versions-116
python = final.python3.withPackages (p: [ p.lief ]);
script = ''
import lief
import sys
lib_symbols = lief.parse(sys.argv[1])
for s in filter(lambda e: e.exported, lib_symbols.static_symbols):
lib_symbols.add_dynamic_symbol(s)
lib_symbols.write(sys.argv[2])
'';
name = "minecraft-bedrock-server-patchdebug";
in final.writeTextFile {
name = "${name}.py";
destination = "/bin/${name}";
executable = true;
text = ''
#!${lib.getExe python}
${script}
'';
meta.mainProgram = name;
};
minecraft-bedrock-server-patchelf = prev.patchelf.overrideDerivation (old: {
postPatch = ''
substituteInPlace src/patchelf.cc \
--replace "32 * 1024 * 1024" "512 * 1024 * 1024"
'';
});
minecraft-bedrock-server = final.callPackage ../packages/minecraft-bedrock.nix { };
}

View file

@ -0,0 +1,40 @@
{ stdenv
, fetchurl
, minecraft-bedrock-server-patchelf
, minecraft-bedrock-server-patchdebug
#, minecraft-bedrock-server-libCrypto
, autoPatchelfHook
, curl, gcc-unwrapped, openssl, unzip
}: stdenv.mkDerivation rec {
pname = "minecraft-bedrock-server";
version = "1.20.80.05";
src = fetchurl {
url = "https://minecraft.azureedge.net/bin-linux/bedrock-server-${version}.zip";
sha256 = "sha256-6vZx29FOXRR7Rzx82Axo3a/Em+9cpK7Hj3cuDRnW9+8=";
};
sourceRoot = ".";
nativeBuildInputs = [
minecraft-bedrock-server-patchelf
minecraft-bedrock-server-patchdebug
autoPatchelfHook
curl
gcc-unwrapped
#minecraft-bedrock-server-libCrypto
openssl
unzip
];
buildPhase = ''
minecraft-bedrock-server-patchdebug bedrock_server_symbols.debug bedrock_server_symbols_patched.debug
'';
installPhase = ''
install -m755 -D bedrock_server $out/bin/bedrock_server
rm bedrock_server
rm server.properties
mkdir -p $out/var
cp -a . $out/var/lib
'';
fixupPhase = ''
autoPatchelf $out/bin/bedrock_server
'';
dontStrip = true;
}

View file

@ -11,6 +11,7 @@ _: {
services = { services = {
sshd.enable = true; sshd.enable = true;
tailscale.enable = true; tailscale.enable = true;
minecraft-bedrock-server.enable = true;
}; };
}; };
} }

View file

@ -3,6 +3,7 @@
"lxc.mount.entry": [ "lxc.mount.entry": [
"/rpool/shared/nix/store nix/store none bind,create=dir", "/rpool/shared/nix/store nix/store none bind,create=dir",
"/rpool/shared/nix/var nix/var none bind,create=dir", "/rpool/shared/nix/var nix/var none bind,create=dir",
"/rpool/shared/minecraft/bedrock mnt/shared/minecraft/bedrock none bind,optional,create=dir",
"/dev/net/tun dev/net/tun none bind,optional,create=file" "/dev/net/tun dev/net/tun none bind,optional,create=file"
], ],
"lxc.idmap": [ "lxc.idmap": [

View file

@ -8,6 +8,7 @@
nixos.nixbld nixos.nixbld
nixos.tailscale nixos.tailscale
nixos.github-runner.zone nixos.github-runner.zone
nixos.minecraft.bedrock
]; ];
nix.gc = { nix.gc = {

View file

@ -160,6 +160,8 @@ mkshared plex 100193 100193 0750
mkshared postgresql 100071 100071 0750 mkshared postgresql 100071 100071 0750
mkshared unifi 100990 100990 0750 mkshared unifi 100990 100990 0750
mkshared zigbee2mqtt 100317 100317 0700 mkshared zigbee2mqtt 100317 100317 0700
mkshared minecraft 0 0 0750
mkshared minecraft/bedrock 100913 100913 0750
ln -sf /lib/systemd/system/auth-rpcgss-module.service /etc/systemd/system/ ln -sf /lib/systemd/system/auth-rpcgss-module.service /etc/systemd/system/
mkdir -p /etc/systemd/system/auth-rpcgss-module.service.d mkdir -p /etc/systemd/system/auth-rpcgss-module.service.d