#!/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__ + ').')