70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Linux server kit entrypoint. Configuration and worlds are never pack-owned."""
|
|
import argparse
|
|
import fcntl
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import signal
|
|
import subprocess
|
|
import tomllib
|
|
from urllib.request import urlopen
|
|
|
|
|
|
def update(root, platform, url, java):
|
|
# packwiz updates mods, not Minecraft/Loader. Refuse an implicit platform migration.
|
|
with urlopen(url, timeout=30) as response:
|
|
manifest = tomllib.loads(response.read(65536).decode('utf-8'))
|
|
if manifest['versions'] != platform['versions']:
|
|
raise SystemExit('Minecraft/Fabric changed: prepare an explicit migration before updating.')
|
|
tools = root / '.tools'
|
|
for name, digest in platform['sha256'].items():
|
|
if hashlib.sha256((tools / name).read_bytes()).hexdigest() != digest:
|
|
raise SystemExit('Server tool hash mismatch: ' + name)
|
|
subprocess.run([java, '-jar', str(tools / 'packwiz-installer-bootstrap.jar'),
|
|
'--bootstrap-main-jar', str(tools / 'packwiz-installer.jar'),
|
|
'--bootstrap-no-update', '-g', '-s', 'server',
|
|
'--pack-folder', str(root), url], cwd=root, check=True)
|
|
|
|
|
|
def run_minecraft(root, java, memory):
|
|
process = subprocess.Popen([java, '-Xms512M', '-Xmx' + memory, '-jar',
|
|
str(root / '.tools/fabric-server-launch.jar'), 'nogui'], cwd=root)
|
|
def stop(signum, _frame):
|
|
process.send_signal(signum)
|
|
previous = {s: signal.signal(s, stop) for s in (signal.SIGTERM, signal.SIGINT)}
|
|
try:
|
|
# Keep the update lock until Java has finished saving during shutdown.
|
|
return process.wait()
|
|
finally:
|
|
for signum, handler in previous.items():
|
|
signal.signal(signum, handler)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('command', choices=('update', 'start'), nargs='?', default='start')
|
|
args = parser.parse_args()
|
|
root = Path(__file__).resolve().parents[1]
|
|
platform = json.loads((root / '.tools/platform.json').read_text())
|
|
java = os.environ.get('SANCTUARY_JAVA', 'java')
|
|
with (root / '.sanctuary-server.lock').open('a') as lock:
|
|
try:
|
|
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except BlockingIOError:
|
|
raise SystemExit('Server is running or an update is already in progress.')
|
|
update(root, platform, os.environ.get('SANCTUARY_PACK_URL', platform['pack_url']), java)
|
|
if args.command == 'start':
|
|
memory = os.environ.get('SANCTUARY_MEMORY', '4G')
|
|
# No shell evaluation of Java options or credentials.
|
|
raise SystemExit(run_minecraft(root, java, memory))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
|
|
# Do not print environment values or HTTP response bodies.
|
|
raise SystemExit('Server operation failed (' + type(error).__name__ + ').')
|