diff --git a/bundles/minecraft/files/minecraft.service b/bundles/minecraft/files/minecraft.service new file mode 100644 index 0000000..d9a77af --- /dev/null +++ b/bundles/minecraft/files/minecraft.service @@ -0,0 +1,39 @@ +[Unit] +Description=Minecraft Server +Requires=network.target +After=network.target + +[Service] +Type=simple +NotifyAccess=main +ExecStart=/usr/bin/java ${java_opts} -jar /home/minecraft/minecraft_server.jar --nogui +Restart=always +RestartSec=10 + +User=minecraft +Group=minecraft + +WorkingDirectory=/home/minecraft + +LimitNOFILE=8192 +LimitNPROC=256 + +UMask=0022 + +NoNewPrivileges=true +ProtectSystem=true +ReadOnlyPaths=/ +ReadWritePaths=/home/minecraft +PrivateTmp=true +PrivateDevices=true +PrivateUsers=true +ProtectHostname=true +ProtectClock=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true + + +[Install] +WantedBy=multi-user.target diff --git a/bundles/minecraft/items.py b/bundles/minecraft/items.py new file mode 100644 index 0000000..6f64243 --- /dev/null +++ b/bundles/minecraft/items.py @@ -0,0 +1,91 @@ +from json import dumps + +java_opts = set() + +for k, v in node.metadata.get('minecraft/java_opts').items(): + if v is None: + java_opts.add(k) + else: + java_opts.add(f'{k}={v}') + +directories['/home/minecraft/world'] = { + 'owner': 'minecraft', + 'group': 'minecraft', +} + +directories['/home/minecraft/world_nether'] = { + 'owner': 'minecraft', + 'group': 'minecraft', +} + +directories['/home/minecraft/world_the_end'] = { + 'owner': 'minecraft', + 'group': 'minecraft', +} + +files['/usr/local/lib/systemd/system/minecraft.service'] = { + 'content_type': 'mako', + 'context': { + 'java_opts': ' '.join(sorted(java_opts)), + }, + 'triggers': { + 'action:systemd-reload', + 'svc_systemd:minecraft:restart', + }, +} + +files['/home/minecraft/eula.txt'] = { + 'content': 'eula=true\n', +} + +files['/home/minecraft/server.properties'] = { + # get it from data/minecraft/files/{node.name} + 'source': node.name, +} + +version, build_nr = node.metadata.get('minecraft/version') + +files['/home/minecraft/minecraft_server.jar'] = { + 'content_type': 'download', + 'source': f'https://api.papermc.io/v2/projects/paper/versions/{version}/builds/{build_nr}/downloads/paper-{version}-{build_nr}.jar', + 'content_hash': node.metadata.get('minecraft/sha1', None), +} + +svc_systemd['minecraft'] = { + 'needs': { + 'directory:/home/minecraft/world', + 'directory:/home/minecraft/world_nether', + 'directory:/home/minecraft/world_the_end', + 'file:/home/minecraft/eula.txt', + 'file:/home/minecraft/minecraft_server.jar', + 'file:/home/minecraft/server.properties', + 'file:/usr/local/lib/systemd/system/minecraft.service', + 'pkg_apt:openjdk-17-jre', + }, +} + +files['/home/minecraft/ops.json'] = { + 'content': dumps( + [ + { + 'uuid': uuid, + 'name': name, + 'level': 4 + } for name, uuid in sorted(node.metadata.get('minecraft/ops', {}).items()) + ], + indent=4, + ) +} + +if node.metadata.get('minecraft/allowlist', {}): + files['/home/minecraft/whitelist.json'] = { + 'content': dumps( + [ + { + 'uuid': uuid, + 'name': name, + } for name, uuid in sorted(node.metadata.get('minecraft/allowlist').items()) + ], + indent=4, + ) + } diff --git a/bundles/minecraft/metadata.py b/bundles/minecraft/metadata.py new file mode 100644 index 0000000..f207e45 --- /dev/null +++ b/bundles/minecraft/metadata.py @@ -0,0 +1,96 @@ +from bundlewrap.metadata import atomic + +defaults = { + 'apt': { + 'packages': { + 'openjdk-17-jre': {}, + }, + }, + 'backups': { + 'paths': { + '/home/minecraft', + }, + }, + 'minecraft': { + 'java_opts': { + # https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/ + '-Daikars.new.flags': 'true', + '-Dusing.aikars.flags': 'https://mcflags.emc.gs', + '-XX:+AlwaysPreTouch': None, + '-XX:+DisableExplicitGC': None, + '-XX:+ParallelRefProcEnabled': None, + '-XX:+PerfDisableSharedMem': None, + '-XX:+UnlockExperimentalVMOptions': None, + '-XX:G1HeapRegionSize': '16M', + '-XX:G1HeapWastePercent': 5, + '-XX:G1MaxNewSizePercent': 50, + '-XX:G1MixedGCCountTarget': 4, + '-XX:G1MixedGCLiveThresholdPercent': 90, + '-XX:G1NewSizePercent': 40, + '-XX:G1RSetUpdatingPauseTimePercent': 5, + '-XX:G1ReservePercent': 15, + '-XX:InitiatingHeapOccupancyPercent': 20, + '-XX:MaxGCPauseMillis': 200, + '-XX:MaxTenuringThreshold': 1, + '-XX:SurvivorRatio': 32, + }, + }, + 'users': { + 'minecraft': {}, + }, + 'zfs': { + 'datasets': { + 'tank/minecraft': {}, + 'tank/minecraft/overworld': { + 'mountpoint': '/home/minecraft/world', + 'compression': 'on', + 'needed_by': { + 'directory:/home/minecraft/world', + }, + }, + 'tank/minecraft/nether': { + 'mountpoint': '/home/minecraft/world_nether', + 'compression': 'on', + 'needed_by': { + 'directory:/home/minecraft/world_nether', + }, + }, + 'tank/minecraft/end': { + 'mountpoint': '/home/minecraft/world_the_end', + 'compression': 'on', + 'needed_by': { + 'directory:/home/minecraft/world_the_end', + }, + }, + }, + }, +} + + +@metadata_reactor.provides( + 'minecraft/java_opts', +) +def heap_to_java_opts(metadata): + heap_mb = metadata.get('minecraft/heap_mb') + + return { + 'minecraft': { + 'java_opts': { + f'-Xms{heap_mb}M': None, + f'-Xmx{heap_mb}M': None, + }, + }, + } + + +@metadata_reactor.provides( + 'firewall/port_rules/25565', +) +def firewall(metadata): + return { + 'firewall': { + 'port_rules': { + '25565': atomic(metadata.get('minecraft/restrict-to', set())), + }, + }, + } diff --git a/data/minecraft/files/rx300 b/data/minecraft/files/rx300 new file mode 100644 index 0000000..ca39740 --- /dev/null +++ b/data/minecraft/files/rx300 @@ -0,0 +1,59 @@ +#Minecraft server properties +#Sun Nov 06 13:56:13 UTC 2022 +allow-flight=true +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +debug=false +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=true +enable-status=true +enforce-secure-profile=true +enforce-whitelist=true +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=creative +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +level-name=/home/minecraft/world +level-seed= +level-type=minecraft\:normal +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=CutieMC +network-compression-threshold=256 +online-mode=true +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +previews-chat=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password= +rcon.port=25575 +require-resource-pack=false +resource-pack-prompt= +resource-pack-sha1= +resource-pack= +server-ip= +server-port=25565 +simulation-distance=10 +snooper-enabled=false +spawn-animals=true +spawn-monsters=false +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=32 +white-list=true diff --git a/nodes/rx300.py b/nodes/rx300.py index c9e5257..5078002 100644 --- a/nodes/rx300.py +++ b/nodes/rx300.py @@ -20,6 +20,7 @@ nodes['rx300'] = { 'mautrix-telegram', 'mautrix-whatsapp', 'miniflux', + 'minecraft', 'mx-puppet-discord', 'netbox', 'nodejs', @@ -280,6 +281,20 @@ nodes['rx300'] = { 'miniflux': { 'domain': 'rss.franzi.business', }, + 'minecraft': { + 'heap_mb': 16*1024, + 'sha1': '82be5e1bbdfd1bcb001644780562282fd42ee5a9', + 'version': ('1.19.2', '261'), + 'allowlist': { + # use https://mcuuid.net/ + 'kunsi': 'a2b93640-9dff-4c3c-a6c7-bd75329d8997', + 'sophie': '7e593cbb-9d61-4d46-a416-6edbcf8a2109', + }, + 'ops': { + 'kunsi': 'a2b93640-9dff-4c3c-a6c7-bd75329d8997', + }, + 'restrict-to': {'*'}, + }, 'mx-puppet-discord': { 'homeserver': { 'domain': 'franzi.business',