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,
lib,
inputs,
access,
gensokyo-zone,
...
}: let
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.lists) filter concatLists;
inherit (lib.lists) filter optional singleton concatLists;
inherit (lib.strings) hasPrefix replaceStrings concatStringsSep;
inherit (lib.trivial) mapNullable;
inherit (lib.trivial) mapNullable flip;
cfg = config.services.dnsmasq;
inherit (inputs.self.lib) systems;
inherit (gensokyo-zone) systems;
localSystems = filterAttrs (_: system:
system.config.access.online.enable && system.config.network.networks.local.enable or false
) systems;
@ -27,11 +28,11 @@
address6 = system.config.network.networks.local.address6 or null;
in concatStringsSep "," ([
system.config.access.fqdn
] ++ lib.optional (address4 != null)
] ++ optional (address4 != null)
(toString (mapNullable mapDynamic4 address4))
++ lib.optional (address6 != null)
++ optional (address6 != null)
(toString (mapNullable mapDynamic6 address6))
++ lib.singleton
++ singleton
cfg.dynamic.interface
);
mkHostRecordPair = network: system: let
@ -41,9 +42,9 @@
in nameValuePair
(if fqdn != null then fqdn else "${network}.${system.config.access.fqdn}")
(concatStringsSep "," (
lib.optional (address4 != null)
optional (address4 != null)
(toString address4)
++ lib.optional (address6 != null)
++ optional (address6 != null)
(toString address6)
));
systemHosts = filterAttrs (_: value: value != "") (
@ -63,13 +64,38 @@ in {
type = str;
default = config.systemd.network.networks._00-local.name or "eth0";
};
bedrockConnect = {
address = mkOption {
type = nullOr str;
};
address6 = mkOption {
type = nullOr str;
};
};
};
config = {
services.dnsmasq = {
enable = mkDefault true;
resolveLocalQueries = mkForce false;
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;
server =
if config.networking.nameservers' != [ ] then map (ns: ns.address) (filter filterns' config.networking.nameservers')
@ -77,6 +103,12 @@ in {
;
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 {
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 {
default = nixlib.composeManyExtensions [
barcodebuddy
minecraft
krb5
nfs
nginx
@ -13,6 +14,7 @@ in rec {
];
barcodebuddy = import ./barcodebuddy.nix;
krb5 = import ./krb5.nix;
minecraft = import ./minecraft.nix;
nfs = import ./nfs.nix;
nginx = import ./nginx.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 = {
sshd.enable = true;
tailscale.enable = true;
minecraft-bedrock-server.enable = true;
};
};
}

View file

@ -3,6 +3,7 @@
"lxc.mount.entry": [
"/rpool/shared/nix/store nix/store 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"
],
"lxc.idmap": [

View file

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

View file

@ -160,6 +160,8 @@ mkshared plex 100193 100193 0750
mkshared postgresql 100071 100071 0750
mkshared unifi 100990 100990 0750
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/
mkdir -p /etc/systemd/system/auth-rpcgss-module.service.d