improvments to winecmd, dso_cleanup, formatting, and comments

This commit is contained in:
greenseeker 2024-02-18 18:51:32 -05:00
parent a67dc7bb48
commit 0ca42d26f9

View file

@ -10,32 +10,26 @@ from requests import get
from threading import Thread from threading import Thread
from t2support import * from t2support import *
winecmd=["/usr/bin/wineconsole", "--backend=curses"] winecmd = ["/usr/bin/wine"]
argbase=["Tribes2.exe","-dedicated"] argbase = ["Tribes2.exe","-dedicated"]
basecmd=winecmd+argbase basecmd = winecmd + argbase
parent=getppid() parent = getppid()
def dso_cleanup(): def dso_cleanup():
""" """
This function finds all .dso files (compiled .cs scripts) and deletes them This function finds all .dso files (compiled .cs scripts) and deletes them
if they are older than their associated .cs script so that Tribes 2 will so that Tribes 2 will recompile them at startup.
recompile them at startup.
""" """
for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True): for dso_file in iglob(f"{install_dir}/**/*.dso", recursive = True):
cs_file=dso_file[:-4] print(f"Deleting {dso_file} so it can be rebuilt.")
if isfile(cs_file): unlink(dso_file)
cs_mtime=getmtime(cs_file)
dso_mtime=getmtime(dso_file)
if cs_mtime > dso_mtime:
print(f"Deleting {dso_file} so it can be rebuilt.")
unlink(dso_file)
def build_args(basecmd,config): def build_args(basecmd,config):
""" """
This function assembles the command line to launch the server based on the This function assembles the command line to launch the server based on the
config.yaml file. config.yaml file.
""" """
server_command=basecmd server_command = basecmd
if config['Public']: if config['Public']:
server_command.append("-online") server_command.append("-online")
else: else:
@ -63,12 +57,12 @@ def server_files(config):
print(f"Deleting {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") print(f"Deleting {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
unlink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") unlink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
print(f"Linking {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs -> {etc_dir}/serverprefs/{config['ServerPrefs']}") print(f"Linking {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs -> {etc_dir}/serverprefs/{config['ServerPrefs']}")
makedirs(f"{install_dir}/GameData/{config['Mod']}/prefs", mode=0o755, exist_ok=True) makedirs(f"{install_dir}/GameData/{config['Mod']}/prefs", mode = 0o755, exist_ok = True)
symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
if config["MapList"] and config["MissionType"]: if config["MapList"] and config["MissionType"]:
print(f"Writing {install_dir}/GameData/base/prefs/missions.txt") print(f"Writing {install_dir}/GameData/base/prefs/missions.txt")
makedirs(f"{install_dir}/GameData/base/prefs", mode=0o755, exist_ok=True) makedirs(f"{install_dir}/GameData/base/prefs", mode = 0o755, exist_ok = True)
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist: with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
for mission in config["MapList"]: for mission in config["MapList"]:
mlist.write(f"{config['MissionType']} {mission}\n") mlist.write(f"{config['MissionType']} {mission}\n")
@ -78,7 +72,6 @@ def server_files(config):
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist: with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
mlist.write("") mlist.write("")
def runaway_control(runaway_proc_cmd): def runaway_control(runaway_proc_cmd):
""" """
When run in the background, wine will spawn two 'wineconsole' When run in the background, wine will spawn two 'wineconsole'
@ -88,9 +81,9 @@ def runaway_control(runaway_proc_cmd):
for x in range(20): for x in range(20):
sleep(15) sleep(15)
print(f"Checking for runaway '{runaway_proc_cmd}' process...") print(f"Checking for runaway '{runaway_proc_cmd}' process...")
runaway_pid=run(["/usr/bin/pgrep","-f", runaway_proc_cmd],stdout=PIPE).stdout runaway_pid = run(["/usr/bin/pgrep","-f", runaway_proc_cmd],stdout = PIPE).stdout
if runaway_pid: if runaway_pid:
runaway_pid=str(int(runaway_pid)) runaway_pid = str(int(runaway_pid))
print(f"Limiting runaway wineconsole process: {runaway_pid}") print(f"Limiting runaway wineconsole process: {runaway_pid}")
run(["/usr/bin/cpulimit","-bp", runaway_pid,"-l2"]) run(["/usr/bin/cpulimit","-bp", runaway_pid,"-l2"])
break break
@ -99,7 +92,7 @@ def master_heartbeat():
""" """
A public Tribes 2 server should send a regular heartbeat to the TribexNext A public Tribes 2 server should send a regular heartbeat to the TribexNext
master server so that it appears in the list, however this seems master server so that it appears in the list, however this seems
inconsistent, so this function takes over that responsibility. inconsistent, so this function takes over that responsibility if enabled.
""" """
print("Starting TribesNext heartbeat thread...") print("Starting TribesNext heartbeat thread...")
while True: while True:
@ -110,43 +103,24 @@ def is_valid_ip(ip):
"""Check if an ip looks like a valid IP address.""" """Check if an ip looks like a valid IP address."""
return bool(match(r"^(\d{1,3}\.){3}\d{1,3}$", ip)) return bool(match(r"^(\d{1,3}\.){3}\d{1,3}$", ip))
def override_mitm():
"""
Tribes 2 servers try to detect descrepencies between their own IP and the IP
that the client believes it's connecting to as possible man-in-the-middle
attacks, however this often interfers with connections when the server is
NATed or multi-homed. This function gets the public-facing IP of the host
and writes an autoexec script to effectively disable this detection.
"""
for ip_service in ["http://api.ipify.org","http://ifconfig.me","http://ipinfo.io/ip"]:
r=get(ip_service)
if r.status_code == 200 and is_valid_ip(r.text):
print(f"Got public IP address {r.text}")
break
if r.status_code != 200: bail("Could not get this server's public IP address.")
print(f"Overriding Man-in-the-Middle attack detection.")
with open(f'{install_dir}/GameData/base/scripts/autoexec/noMITM.cs', 'w') as nomitm_script:
nomitm_script.write(f'$IPv4::InetAddress = "{r.text}";\n')
if __name__ == "__main__": if __name__ == "__main__":
# Get the version from the release file and print it.
if isfile(release_file):
with open(release_file, 'r') as rf:
version = rf.read().rstrip()
else:
version = None
if version: print(f"t2server version {version}")
# If run interactively, warn user that they probably don't want to do this unless troubleshooting. # If run interactively, warn user that they probably don't want to do this unless troubleshooting.
if parent == 1: if parent == 1:
print(f"Started by init") print(f"Started by init")
else: else:
interactive_run=menu(['[Y]es, run t2server interactively.',"~~[N]o, abort."],header="Running t2server directly can be helpful for troubleshooting but generally it's best to manage your server with 'systemctl'. Do you still want to run t2server?",footer="Choose [N] and run 't2help' if you're unsure of what to do.") interactive_run = menu([f'[Y]es, run t2server interactively.',"~~[N]o, abort."], header = "Running t2server directly can be helpful for troubleshooting but generally it's best to manage your server with 'systemctl'. Do you still want to run t2server?", footer = "Choose [N] and run 't2help' if you're unsure of what to do.")
if interactive_run == 'N': bail() if interactive_run == 'N': bail()
chdir(f"{install_dir}/GameData") chdir(f"{install_dir}/GameData")
# Set default configuration
config_defaults = {
'ServerPrefs' : 'Classic_CTF.cs',
'Mod' : 'Classic',
'Public' : False,
'OverrideMITM': True,
'MissionType' : 'CTF',
'MapList' : False
}
# Read configuration from config.yaml # Read configuration from config.yaml
with open(f'{etc_dir}/config.yaml', 'r') as f: with open(f'{etc_dir}/config.yaml', 'r') as f:
loaded_config = yaml.full_load(f) loaded_config = yaml.full_load(f)
@ -161,33 +135,55 @@ if __name__ == "__main__":
if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"): if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"):
bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}") bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}")
# Delete any pre-existing noMITM script/dso. It will be recreated below, if needed. # Delete any pre-existing 00_t2server_opts script. It will be recreated below, if needed.
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs") if isfile(f"{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs")
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso")
# Create serverprefs symlink and missions.txt (if appropriate), clean out stale dso files, then assemble the command line arguments to launch the server # Create serverprefs symlink and missions.txt (if appropriate), clean out dso files, then assemble the command line arguments to launch the server
server_files(config) server_files(config)
dso_cleanup() if config['DSOCleanup']: dso_cleanup()
server_command=build_args(basecmd,config) server_command = build_args(basecmd,config)
# If this is a public server, start a hearbeat thread. Also write the MITM override file if configured. # Start the opts script with a comment at the top indicating this is auto-generated and shouldn't be edited, and also disable PureServer.
opts_script_content = "// This file is updated automatically by t2server based on /etc/t2server/config.yaml.\n// Do not edit this file directly.\n$Host::PureServer = 0;\n"
# If this is a public server...
if config['Public']: if config['Public']:
print("Starting heartbeat...") # Start a heartbeat thread, if configured.
if config['OverrideMITM']: override_mitm() if config['Heartbeat']:
heartbeat=Thread(target=master_heartbeat) heartbeat = Thread(target = master_heartbeat)
heartbeat.daemon=True heartbeat.daemon = True
heartbeat.start() heartbeat.start()
# Capture the public IP for MITM override, if configured.
if config['OverrideMITM']:
for ip_service in ["http://api.ipify.org","http://ifconfig.me","http://ipinfo.io/ip"]:
r = get(ip_service)
if r.status_code == 200 and is_valid_ip(r.text):
print(f"Got public IP address {r.text}")
break
if r.status_code != 200: bail("Could not get this server's public IP address.")
opts_script_content += f'$IPv4::InetAddress = "{r.text}";\n'
print(f"Overriding Man-in-the-Middle attack detection.")
# If MissionType and MapList are defined, write the first map into 00_t2server_opts.cs
if config['MissionType'] and config['MapList']:
opts_script_content += f'$Host::MissionType = "' + config['MissionType'] + '";\n$Host::Map = "' + config['MapList'][0] + '";\n'
if config['MissionType'] and config['MissionType'].lower() != 'teamrabbit':
opts_script_content += "$Host::LoadTR2Gametype = 0;\n"
# Write all options to the 00_t2server_opts file
with open(f'{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs', 'w') as opts_script:
opts_script.write(opts_script_content)
# Cap the CPU of the runaway wineconsole processes # Cap the CPU of the runaway wineconsole processes
wcpid1_limit=Thread(target=runaway_control, args=("wineconsole --use-event=52",)) wcpid1_limit = Thread(target = runaway_control, args = ("wineconsole --use-event=52",))
wcpid1_limit.start() wcpid1_limit.start()
wcpid2_limit=Thread(target=runaway_control, args=("wineconsole --use-event=188",)) wcpid2_limit = Thread(target = runaway_control, args = ("wineconsole --use-event=188",))
wcpid2_limit.start() wcpid2_limit.start()
# Open the console log file if running as service and start the Tribes 2 server # Open the console log file if running as service and start the Tribes 2 server
print(f"Starting Tribes 2 server: " + " ".join(server_command)) print(f"Starting Tribes 2 server: " + " ".join(server_command))
if parent == 1: if parent == 1:
with open(f"{log_dir}/console.log", 'w') as consolelog: with open(f"{log_dir}/console.log", 'w') as consolelog:
run(server_command,stdout=consolelog) run(server_command,stdout = consolelog)
else: else:
run(server_command) run(server_command)