mirror of
https://github.com/greenseeker/t2server.git
synced 2026-07-16 08:44:41 +00:00
improvments to winecmd, dso_cleanup, formatting, and comments
This commit is contained in:
parent
a67dc7bb48
commit
0ca42d26f9
1 changed files with 64 additions and 68 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue