Files
sanctuary-beta/scripts/server_runtime.py
T
2026-10-26 01:21:39 +01:00

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