From 8ffa4dacbdae5dd828a8423960b1cb407743f5af Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sat, 2 Jan 2021 23:30:39 -0500 Subject: [PATCH 01/21] First Commit --- etc/systemd/system/t2bouncer.service | 10 + etc/systemd/system/t2bouncer.timer | 10 + etc/systemd/system/t2server.service | 36 +++ etc/t2server/config.yaml | 44 ++++ etc/t2server/serverprefs/Classic_CTF.cs | 52 ++++ setup | 304 ++++++++++++++++++++++++ sla/tribes2.txt | 196 +++++++++++++++ sla/tribesnext.txt | 104 ++++++++ t2support.py | 1 + usr/local/bin/t2bouncer | 31 +++ usr/local/bin/t2fixer | 103 ++++++++ usr/local/bin/t2help | 22 ++ usr/local/bin/t2remove | 64 +++++ usr/local/bin/t2server | 192 +++++++++++++++ usr/local/bin/t2support.py | 132 ++++++++++ winbin/install_wrapper.au3 | 82 +++++++ 16 files changed, 1383 insertions(+) create mode 100644 etc/systemd/system/t2bouncer.service create mode 100644 etc/systemd/system/t2bouncer.timer create mode 100644 etc/systemd/system/t2server.service create mode 100644 etc/t2server/config.yaml create mode 100644 etc/t2server/serverprefs/Classic_CTF.cs create mode 100755 setup create mode 100644 sla/tribes2.txt create mode 100644 sla/tribesnext.txt create mode 120000 t2support.py create mode 100755 usr/local/bin/t2bouncer create mode 100755 usr/local/bin/t2fixer create mode 100755 usr/local/bin/t2help create mode 100755 usr/local/bin/t2remove create mode 100755 usr/local/bin/t2server create mode 100644 usr/local/bin/t2support.py create mode 100644 winbin/install_wrapper.au3 diff --git a/etc/systemd/system/t2bouncer.service b/etc/systemd/system/t2bouncer.service new file mode 100644 index 0000000..e93a9ec --- /dev/null +++ b/etc/systemd/system/t2bouncer.service @@ -0,0 +1,10 @@ +[Unit] +Description=t2bouncer (restarts t2server at configured time) +Wants=t2bouncer.timer + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/t2bouncer + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/etc/systemd/system/t2bouncer.timer b/etc/systemd/system/t2bouncer.timer new file mode 100644 index 0000000..40359d7 --- /dev/null +++ b/etc/systemd/system/t2bouncer.timer @@ -0,0 +1,10 @@ +[Unit] +Description=t2bouncer timer +BindsTo=t2server.service + +[Timer] +Unit=t2bouncer.service +OnCalendar=*:00:00 + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/etc/systemd/system/t2server.service b/etc/systemd/system/t2server.service new file mode 100644 index 0000000..70b87cb --- /dev/null +++ b/etc/systemd/system/t2server.service @@ -0,0 +1,36 @@ +[Unit] +Description=Tribes 2 Dedicated Server +Requires=network.target +After=network.target +Wants=t2bouncer.timer +Before=t2bouncer.timer + +[Service] +Type=simple +Environment=TERM=xterm-256color +Environment=PYTHONUNBUFFERED=1 +User=t2server +CPUAffinity=0 +ExecStart=/usr/local/bin/t2server +ExecStop=/usr/bin/wineserver -k +Restart=on-failure +RestartSec=15s +TimeoutStopSec=60s +WorkingDirectory=/opt/t2server/GameData +LogsDirectory=t2server + +# Below settings help lock down the service for security +ProtectSystem=full +ProtectHome=true +SystemCallFilter=@system-service +NoNewPrivileges=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/etc/t2server/config.yaml b/etc/t2server/config.yaml new file mode 100644 index 0000000..c6d38c6 --- /dev/null +++ b/etc/t2server/config.yaml @@ -0,0 +1,44 @@ +--- +## ServerPrefs indicates the server config file in /etc/t2server/serverprefs +## to use. This is case-sensitive and must match the filename exactly. +ServerPrefs: Classic_CTF.cs + +## Tribes 2 servers tend to get unstable after a couple weeks of being online. +## Here you can specify a day and hour to automatically bounce the server. +## Set RestartTime to the hour of the day, 0-23, to cycle the server +## (eg. 4=4:00am, 16=4:00pm) or False to disable. Set RestartDay to the +## three-letter abbreviation of the day on which the server should be restarted +## (Sun, Mon, Tue, Wed, Thu, Fri, or Sat). This will be ignored if +## RestartTime = False. +RestartTime: False +RestartDay: Mon + +## Set Mod to the directory name of the mod to be loaded, or 'base' for +## vanilla (but why?). This is case-sensitive and must match the subdirectory +## name exactly. +Mod: Classic + +## Set Public to False to host a LAN-only game, or to True to host a public +## game which will be registered with the TribesNext master. +Public: False + +## 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. Enable OverrideMITM to effectively disable this +## detection. This setting has no effect if Public = False. +OverrideMITM: True + +## Configure a custom map rotation list. The standard Mission Types are +## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM" +## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege", +## "TeamHunters", and "TeamRabbit". Your server will always launch with the +## MissionType and Map specified in your serverprefs file ($Host::MissionType +## and $Host::Map), so make sure MissionType matches $Host::MissionType and +## the first map in MapList matches $Host::Map. If you're running a mod that +## handles map rotation, set these to 'False'. +## Example: +## MissionType: CTF +## MapList: ["Katabatic", "Minotaur", "Tombstone"] +MissionType: False +MapList: False \ No newline at end of file diff --git a/etc/t2server/serverprefs/Classic_CTF.cs b/etc/t2server/serverprefs/Classic_CTF.cs new file mode 100644 index 0000000..9cc7c46 --- /dev/null +++ b/etc/t2server/serverprefs/Classic_CTF.cs @@ -0,0 +1,52 @@ +$Host::AdminPassword = "changethis"; +$Host::allowAdminPlayerVotes = "0"; +$Host::AllowMapScript = 1; +$Host::BanTime = 1800; +$Host::BotCount = 7; +$Host::BotsEnabled = "0"; +$Host::ClassicSuperAdminPassword = "changethis"; +$Host::CRCTextures = 0; +$Host::FloodProtectionEnabled = 1; +$Host::GameName = "Tribes 2 Classic CTF Server"; +$Host::HiVisibility = "1"; +$Host::holoName1 = "Storm"; +$Host::holoName2 = "Inferno"; +$Host::holoName3 = "Starwolf"; +$Host::holoName4 = "DSword"; +$Host::holoName5 = "BloodEagle"; +$Host::holoName6 = "Harbinger"; +$Host::Info = "This is a Tribes 2 Classic Server."; +$Host::KickBanTime = 300; +$Host::Map = "Minotaur"; +$Host::MarkDnDObjectives = 1; +$Host::MaxMessageLen = 120; +$Host::MaxPlayers = "64"; +$Host::MissionType = "CTF"; +$Host::NoSmurfs = 0; +$Host::PlayerRespawnTimeout = "60"; +$Host::Port = "28000"; +$Host::PureServer = 0; +$Host::TeamDamageOn = "1"; +$Host::TeamName0 = "Unassigned"; +$Host::TeamName1 = "Storm"; +$Host::TeamName2 = "Inferno"; +$Host::TeamName3 = "Starwolf"; +$Host::TeamName4 = "Diamond Sword"; +$Host::TeamName5 = "Blood Eagle"; +$Host::TeamName6 = "Phoenix"; +$Host::TeamSkin0 = "blank"; +$Host::TeamSkin1 = "base"; +$Host::TeamSkin2 = "baseb"; +$Host::TeamSkin3 = "swolf"; +$Host::TeamSkin4 = "dsword"; +$Host::TeamSkin5 = "beagle"; +$Host::TeamSkin6 = "cotp"; +$Host::TimeLimit = "200"; +$Host::TN::beat = 3; +$Host::TN::echo = 1; +$Host::TournamentMode = "0"; +$Host::UseHighPerformanceCounter = 0; +$Host::VotePassPercent = "60"; +$Host::VoteSpread = 20; +$Host::VoteTime = "30"; +$Host::warmupTime = "20"; diff --git a/setup b/setup new file mode 100755 index 0000000..533426e --- /dev/null +++ b/setup @@ -0,0 +1,304 @@ +#!/usr/bin/env -S python3 -B +from os import system, unlink, symlink, makedirs, geteuid, getcwd, chmod, rename +from os.path import isfile, isdir +from time import time +from shutil import copyfile, rmtree +from glob import iglob +from pwd import getpwnam as getuser +from t2support import * + +if geteuid() != 0: + bail(f"This script must be run with sudo or as root.\n") + +pwd = getcwd() + +installer_mirror_list = [ + "https://www.the-construct.net/downloads/tribes2/tribes2gsi.exe", + "http://spinfusor.ch/tribes2/setup/tribes2_gsi.exe", + "https://adamantis.keybase.pub/Abandonware/Tribes2/tribes2gsi.exe?dl=1", + "http://xfer1.the-construct.net/tribes2/tribes2gsi.exe", + "http://dl.rawr32.net/tribes2gsi.exe", + "https://files.playt2.com/Install/tribes2gsi.exe", + "https://gamestand.net/dl/tribes-2/?ind=1527034109041&filename=tribes2_gsi.exe&wpdmdl=165&refresh=5fb884009d78b1605927936", + "http://www.tribes2stats.com/files/tribes2_gsi.exe" +] + +tnpatch_mirror_list = [ + "http://www.tribesnext.com/files/TribesNext_rc2a.exe", + "https://keybase.pub/adamantis/Abandonware/Tribes2/TribesNext_rc2a.exe", + "https://files.playt2.com/Install/TribesNext_rc2a.exe", + "https://gamestand.net/dl/tribes-2/?ind=1527034087554&filename=TribesNext_rc2a.exe&wpdmdl=165&refresh=5fb884009d73d1605927936", + "http://www.tribes2stats.com/files/patches/TribesNext_rc2a.exe", + "http://files.nastyhobbit.org/t2-installer/TribesNext_rc2a.exe", + "http://www.the-flet.com/dynamix/t2/TribesNext_rc2a.exe", + "http://cdn.net-load.com/TribesNext_rc2a.exe", + "https://starsiege.pw/_tribes2/TribesNext_rc2a.exe" +] + +installer_checksum = "93460541ddd3bdff9b30829ba04f2186" +tnpatch_checksum = "3bec757215cd29b37d85b567edf8d693" + +def md5sum(filename): + """ Return the md5 checksum of the given file """ + with open(filename, "rb") as file: + file_hash = md5() + chunk = file.read(8192) + while chunk: + file_hash.update(chunk) + chunk = file.read(8192) + return file_hash.hexdigest() + +def download_file(url, filename): + """ Download url with progress meter and save to filename """ + req = get(url, stream=True) + with open(filename, 'wb') as outfile: + pbar = tqdm(total=int(req.headers['Content-Length']), unit="B", unit_scale=True, unit_divisor=1024, position=0, desc=filename.split("/")[-1]) + for chunk in req.iter_content(chunk_size=1024*1024): + if chunk: + pbar.update(len(chunk)) + outfile.write(chunk) + pbar.close() + return filename + +if __name__ == "__main__": + + action=menu(["~~[C]ontinue","[Q]uit"],header="This script will install Tribes 2 for use as a dedicated server.") + if action == "Q": bail() + + # Check if user exists + try: + user_info = getuser(user) + except KeyError: + user_info = False + + # Create or repurpose user + if user_info: + if user_info.pw_dir == install_dir: + pwarn(f"User '{user}' exists and will be reused.") + else: + bail(f"ERROR: User '{user}' already exists and may belong to another person or process.") + else: + pinfo(f"Creating {user} user and {install_dir}.") + system(f"useradd -md {install_dir} {user}") + + if not user_info: user_info = getuser(user) + + # Create log_dir + pinfo(f"Creating {log_dir}.") + makedirs(log_dir, mode=0o777, exist_ok=True) + chmod(log_dir, 0o777) + + # Create .wine dir + pinfo(f"Creating {install_dir}/.wine defaults.") + system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'") + + # Map wine I: drive to pwd T: drive to install_dir and L: to log_dir + pinfo(f"Mapping I: in wine for {user}.") + try: + symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:") + except FileExistsError: + pass + + pinfo(f"Mapping L: in wine for {user}.") + try: + symlink(log_dir, f"{install_dir}/.wine/dosdevices/l:") + except FileExistsError: + pass + + pinfo(f"Mapping T: in wine for {user}.") + try: + symlink(install_parent, f"{install_dir}/.wine/dosdevices/t:") + except FileExistsError: + pass + + + # Check for needed exe/zip/dll files in winbin dir + needed_files=[] + if isfile(f"{pwd}/winbin/tribes2gsi.exe"): + pinfo("tribes2gsi.exe found.") + installer_exe = f"{pwd}/winbin/tribes2gsi.exe" + elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"): + pinfo("tribes2_gsi.exe found.") + installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" + else: + pwarn("Tribes 2 installer not found.") + needed_files.append("tribes2_gsi.exe") + installer_exe = False + + if isfile(f"{pwd}/winbin/TribesNext_rc2a.exe"): + pinfo("TribesNext_rc2a.exe found.") + tnpatch_exe = f"{pwd}/winbin/TribesNext_rc2a.exe" + else: + pwarn("Tribes Next patch not found.") + needed_files.append("TribesNext_rc2a.exe") + tnpatch_exe = False + + ruby_dll = f"{pwd}/winbin/msvcrt-ruby191.dll" + instwrap_exe = f"{pwd}/winbin/install_wrapper.exe" + + # Download files if needed + if not installer_exe or not tnpatch_exe: + action=menu(["~~[D]ownload automatically","[Q]uit"],header="One or more needed files were not found. Download automatically or quit so they can be manually placed in the 'winbin' subdirectory?") + if needed_files == 2: + needed_files=f"{needed_files[0]} and {needed_files[1]}" + if action=="Q": bail(f"Manually place {needed_files} in the 'winbin' subdirectory then rerun setup.") + + if not installer_exe: + for url in installer_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + installer_exe = download_file(url, f"{pwd}/winbin/tribes2_gsi.exe") + if md5sum(installer_exe) == installer_checksum: + pinfo("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror.") + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not installer_exe: + bail("ERROR: Tribes 2 installer could not be downloaded.") + + if not tnpatch_exe: + for url in tnpatch_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + tnpatch_exe = download_file(url, f"{pwd}/winbin/TribesNext_rc2a.exe") + if md5sum(tnpatch_exe) == tnpatch_checksum: + pinfo("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror." ) + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not tnpatch_exe: + bail("ERROR: Tribes Next patch could not be downloaded.") + + # Present SLAs before beginning install + sla = None + while not sla: + sla=menu(["[V]iew Tribes 2 and TribesNext License Agreements", "[A]ccept License Agreements", "[Q]uit"], header="Please take a moment to review and accept the Tribes 2 and TribeNext License Agreements before beginning automated install.") + if sla == "V": + print(color.DY) + system(f"/usr/bin/less {pwd}/sla/tribes2.txt") + print(color.DP) + system(f"/usr/bin/less {pwd}/sla/tribesnext.txt") + sla = None + elif sla == "A": + break + elif sla == "Q": + bail("You must accept the License Agreements to install.") + + # Ensure sufficient permissions on winbin and its contents + chmod(f"{pwd}/winbin", 0o777) + chmod(installer_exe, 0o777) + chmod(tnpatch_exe, 0o777) + chmod(instwrap_exe, 0o777) + chmod(ruby_dll, 0o777) + + # Execute install wrapper + pinfo(f"\nInstalling Tribes 2 and the TribesNext patch in wine. Please wait...") + chowner(install_dir, user) + system(f"su - {user} -c'xvfb-run -as " + '"-fbdir /var/tmp"' + " wine I:/install_wrapper.exe > /dev/null 2>&1'") + + # Rudamentary check to see if T2 install succeeded + if not isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): bail(f"ERROR: Tribes 2 installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Rudamentary check to see if TN install succeeded + if not isfile(f"{install_dir}/GameData/TN_Uninstall.exe"): bail(f"ERROR: Tribes Next installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Replace msvcrt-ruby190.dll with msvcrt-ruby191.dll + pinfo("Updating msvcrt-ruby190.dll to msvcrt-ruby191.dll.\n") + copyfile(ruby_dll,f"{install_dir}/GameData/msvcrt-ruby191.dll") + unlink(f"{install_dir}/GameData/msvcrt-ruby190.dll") + symlink(f"{install_dir}/GameData/msvcrt-ruby191.dll", f"{install_dir}/GameData/msvcrt-ruby190.dll") + + # Install addons + for addon in iglob(f"{pwd}/addons/*"): + if addon.endswith(".zip"): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"unzip -qqd {install_dir}/GameData {addon}") + elif addon.endswith((".tar",".tgz",".tar.gz",".txz",".tar.xz",".tbz",".tar.bz")): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"tar -C {install_dir}/GameData -xf {addon}") + elif addon.endswith(".vl2"): + pinfo(f"Copying {addon} to {install_dir}/GameData/base.") + copyfile(addon,f"{install_dir}/GameData/base/{addon.split('/')[-1]}") + elif addon.endswith("readme.txt"): + pass + else: + pwarn(f"Ignoring {addon}.") + + # Copy t2server and t2bouncer to /usr/local/bin/ + pinfo("Installing t2server script.") + copyfile(f"{pwd}/usr/local/bin/t2server",f"{bin_dir}/t2server") + pinfo("Installing t2bouncer script.") + copyfile(f"{pwd}/usr/local/bin/t2bouncer",f"{bin_dir}/t2bouncer") + + # Set owner/group on install_dir + chowner(install_dir, user) + + # Clean up temp dir and some unneeded files + pinfo("A little housekeeping...") + if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk") + if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk") + if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE") + if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt") + if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat") + if isfile(f"{install_dir}/GameData/Classic_dedicated_server.bat"): unlink(f"{install_dir}/GameData/Classic_dedicated_server.bat") + if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat") + if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt") + if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt") + if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual") + if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp") + if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt") + if isfile(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt") + if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt") + if isfile(f"{install_dir}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt") + if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.txt") + + # Create config directory and files + pinfo(f"\nCreating {etc_dir}, default config, and installing prefs files.") + makedirs(f"{etc_dir}/serverprefs", mode=0o775, exist_ok=True) + if isfile(f"{etc_dir}/config.yaml"): + timestamp = int(time()) + rename(f"{etc_dir}/config.yaml",f"{etc_dir}/config.yaml.{timestamp}") + pwarn(f"Existing {etc_dir}/config.yaml renamed to {etc_dir}/config.yaml.{timestamp}. Be sure to compare with and update the new config.yaml file.") + pinfo(f"Writing default {etc_dir}/config.yaml.") + copyfile(f"{pwd}/etc/t2server/config.yaml", f"{etc_dir}/config.yaml") + for pfile in iglob(f"{pwd}/etc/t2server/serverprefs/*"): + pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.") + copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}") + + # Create systemd units + pinfo("\nCreating systemd units:") + pinfo("- t2server service") + copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/t2server.service") + pinfo("- t2bouncer service") + copyfile(f"{pwd}/etc/systemd/system/t2bouncer.service",f"{unit_dir}/t2bouncer.service") + pinfo("- t2bouncer timer") + copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer") + system("systemctl daemon-reload") + + # Install utility scripts + pinfo("\nInstalling utilities:") + pinfo("- t2bouncer") + copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer") + pinfo("- t2remove") + copyfile(f"{pwd}/usr/local/bin/t2remove",f"{bin_dir}/t2remove") + pinfo("- t2help") + copyfile(f"{pwd}/usr/local/bin/t2help",f"{bin_dir}/t2help") + + # Install python module + copyfile(f"{pwd}/usr/local/bin/t2support.py",f"{bin_dir}/t2support.py") + chmod(f"{bin_dir}/t2fixer",0o777) + system(f"{bin_dir}/t2fixer") + + # Show help + system(f"{bin_dir}/t2help") + menu(['~~[E]xit'],header="You can run 't2help' at any time to view the info above again.") + + print(f"{color.X}\n") \ No newline at end of file diff --git a/sla/tribes2.txt b/sla/tribes2.txt new file mode 100644 index 0000000..a0430be --- /dev/null +++ b/sla/tribes2.txt @@ -0,0 +1,196 @@ + _______ __ __ _______ + | |.----.|__|| |--..-----..-----. | | + |.| | || _|| || _ || -__||__ --| |___| | + `-|. |-'|__| |__||_____||_____||_____| / ___/ + |: | |: 1 \ +--------------- |::.| ------- License Agreement -------- |::.. . | ------------- + `---' `-------' + +YOU SHOULD CAREFULLY READ THE FOLLOWING END USER LICENSE AGREEMENT BEFORE +INSTALLING THIS SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE +PROGRAM, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT +AGREE TO THE TERMS OF THIS AGREEMENT, PROMPTLY RETURN THE UNUSED SOFTWARE +PROGRAM TO THE PLACE OF PURCHASE OR CONTACT SIERRA ON-LINE, INC. CUSTOMER +SERVICE AT (425) 746-5771 FOR A FULL REFUND OF THE PURCHASE PRICE WITHIN 30 DAYS +OF THE ORIGINAL PURCHASE. + +This software program (the "Program"), any printed materials, any on-line or +electronic documentation, and any and all copies and derivative works of such +software program (including materials created with a so called level editor, if +included) and materials are the copyrighted work of Sierra On-Line, Inc., a +division of Havas Interactive, Inc. and/or its wholly owned subsidiaries, or its +suppliers. All rights reserved, except as expressly stated herein. All use of +the Program is governed by the terms of this End User License Agreement provided +below ("License Agreement"). The Program is solely for use by end users +according to the terms of the License Agreement. Any use, reproduction or +redistribution of the Program not in accordance with the terms of the License +Agreement is expressly prohibited. + +END USER LICENSE AGREEMENT + +1. Limited Use License. Sierra On-Line, Inc. ("Sierra ") hereby grants, and by +installing the Program you thereby accept, a limited, non-exclusive license and +right to install and use one (1) copy of the Program for your use on either a +home, business or portable computer. In addition, the Program has a multi-player +capability that allows users to utilize the Program over the Internet via +Sierra's online game network Sierra.com. Use of the Program over Sierra.com is +subject to your acceptance of Sierra.com's Terms of Use Agreement. Sierra +On-Line, Inc. reserves the right to update, modify or change the Sierra.com +Terms of Use Agreement at any time. The Program may also contain a Level Editor +(the "Editor") that allows you to create custom levels or other materials for +your personal use in connection with the Program ("New Materials"). All use of +the Editor or any New Materials is subject to this License Agreement. The +Program is licensed, not sold. Your license confers no title or ownership in the +Program. + +2. Ownership. All title, ownership rights and intellectual property rights in +and to the Program and any and all copies thereof (including but not limited to +any titles, computer code, themes, objects, characters, character names, +stories, dialog, catch phrases, locations, concepts, artwork, animations, +sounds, musical compositions, audio-visual effects, methods of operation, moral +rights, any related documentation, and "applets" incorporated into the Program) +are owned by Sierra On-Line, Inc. or its licensors. The Program is protected by +the copyright laws of the United States, international copyright treaties and +conventions and other laws. All rights are reserved. The Program contains +certain licensed materials and Sierra 's licensors may protect their rights in +the event of any violation of this Agreement. + +3. Responsibilities of End User. + A. Subject to the Grant of License hereinabove, you may not, in whole or in + part, copy, photocopy, reproduce, translate, reverse engineer, derive source + code, modify, disassemble, decompile, create derivative works based on the + Program, or remove any proprietary notices or labels on the Program without + the prior consent, in writing, of Sierra. + B. The Program is licensed to you as a single product. Its component parts + may not be separated for use on more than one computer. + C. You are entitled to use the Program for your own use, but you are not + entitled to: + (i) sell, grant a security interest in or transfer reproductions of the + Program to other parties in any way, nor to rent, lease or license the + Program to others without the prior written consent of Sierra. + (ii) exploit the Program or any of its parts for any commercial purpose + including, but not limited to, use at a cyber café, computer gaming center + or any other location-based site. Sierra may offer a separate Site License + Agreement to permit you to make the Program available for commercial use; + contact Sierra for details; + (iii) host or provide matchmaking services for the Program or emulate or + redirect the communication protocols used by Sierra in the network feature + of the Program, through protocol emulation, tunneling, modifying or adding + components to the Program, use of a utility program or any other + techniques now known or hereafter developed, for any purpose including, + but not limited to network play over the Internet, network play utilizing + commercial or non-commercial gaming networks or as part of content + aggregation networks without the prior written consent of Sierra ; + (iv) create or maintain, under any circumstance, more than one + simultaneous connection to Sierra.com. All such connections to Sierra.com, + whether created by the Program or by other tools and utilities, may only + be made through methods and means expressly approved by Sierra On-Line, + Inc. Under no circumstances may you connect, or create tools that allow + you to connect to Sierra.com's private binary interface or interfaces + other than those explicitly provided by Sierra On-Line, Inc. for public + use. + +4. Program Transfer. You may permanently transfer all of your rights under this +License Agreement, provided the recipient agrees to the terms of this License +Agreement and you agree to remove the Program from your home or portable +computer. + +5. Termination. This License Agreement is effective until terminated. You may +terminate the License Agreement at any time by destroying the Program. Sierra +may, at its discretion, terminate this License Agreement in the event that you +fail to comply with the terms and conditions contained herein. In such event, +you must immediately destroy the Program. + +6. Export Controls. The Program may not be re-exported, downloaded or otherwise +exported into (or to a national or resident of) any country to which the U.S. +has embargoed goods, or to anyone on the U.S. Treasury Department's list of +Specially Designated Nationals or the U.S. Commerce Department's Table of Denial +Orders. By installing the Program, you are agreeing to the foregoing and you are +representing and warranting that you are not located in, under the control of, +or a national or resident of any such country or on any such list. + +7. Limited Warranty. Sierra expressly disclaims any warranty for the Program, +Editor and Manual(s). The Program, Editor and Manual(s) are provided "as is" +without warranty of any kind, either express or implied, including, without +limitation, the implied warranties of merchantability, fitness for a particular +purpose, or noninfringement. The entire risk arising out of use or performance +of the Program and Manual(s) remains with the User, however Sierra warrants up +to and including 90 days from the date of your purchase of the Program that the +media containing the Program shall be free from defects in material and +workmanship. In the event that the media proves to be defective during that time +period, and upon presentation to Sierra of proof of purchase of the defective +Program, Sierra will at its option 1) correct any defect, 2) provide you with a +product of equal or lesser value, or 3) refund your money. Some states do not +allow the exclusion or limitation of implied warranties or liability for +incidental damages, so the above limitations may not apply to you. + +8. Limitation of Liability. NEITHER SIERRA, HAVAS INTERACTIVE, INC., ITS PARENT, +SUBSIDIARIES OR AFFILIATES SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY +KIND RESULTING FROM THE USE OF THE PROGRAM OR USE OF SIERRA ON-LINE, INC.'S +ONLINE GAME NETWORK, SIERRA.COM INCLUDING, BUT NOT LIMITED TO, LOSS OF +GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER +COMMERCIAL DAMAGES OR LOSSES. SIERRA FURTHER DISCLAIMS ALL WARRANTIES WITH +REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. SPECIFICALLY, SIERRA MAKES NO +WARRANTIES THAT THE PERFORMANCE OR FUNCTIONALITY OF THE PROGRAM WILL NOT BE +AFFECTED BY DATES PRIOR TO, DURING OR AFTER THE YEAR 2000, OR THAT THE PROGRAM +WILL BE CAPABLE OF CORRECTLY PROCESSING, PROVIDING, AND/OR RECEIVING DATE +INFORMATION WITHIN AND BETWEEN CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE +INFORMATION BETWEEN PRODUCTS OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT +THAT MAY BE PROVIDED IN SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR +IN ANY OTHER COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, Sierra +On-Line, Inc. SHALL NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER +CHARACTERS, ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON +SIERRA.COM. I UNDERSTAND AND ACKNOWLEDGE THAT SIERRA ON-LINE, INC. CANNOT AND +WILL NOT BE RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON SIERRA.COM INCLUDING, +BUT NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER +EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some states +do not allow the exclusion or limitation of incidental or consequential damages, +or allow limitations on how long an implied warranty lasts, so the above +limitations may not apply. + +9. Equitable Remedies. You hereby agree that Sierra would be irreparably damaged +if the terms of this License Agreement were not specifically enforced, and +therefore you agree that Sierra shall be entitled, without bond, other security, +or proof of damages, to appropriate equitable remedies with respect to breaches +of this License Agreement, in addition to such other remedies as Sierra may +otherwise have available to it under applicable laws. In the event any +litigation is brought by either party in connection with this License Agreement, +the prevailing party in such litigation shall be entitled to recover from the +other party all the costs, attorneys' fees and other expenses incurred by such +prevailing party in the litigation. + +10. Limitations on License. Nothing in this License Agreement shall preclude +you from making or authorizing the making of another copy or adaptation of the +Program provided, however, that (1) such new copy or adaptation is created as an +essential step in your utilization of the Program in accordance with the terms +of this License Agreement and for NO OTHER PURPOSE; or (2) such new copy or +adaptation is for archival purposes ONLY and all archival copies are destroyed +in the event of your Transfer of the Program, the Termination of this Agreement +or other circumstances under which your continued use of the Program ceases to +be rightful. + +11. Miscellaneous. This License Agreement shall be deemed to have been made and +executed in the State of California and any dispute arising hereunder shall be +resolved in accordance with the law of California. You agree that any claim +asserted in any legal proceeding by one of the parties against the other shall +be commenced and maintained in any state or federal court located in the State +of California, County of Los Angeles, having subject matter jurisdiction with +respect to the dispute between the parties. This License Agreement may be +amended, altered or modified only by an instrument in writing, specifying such +amendment, alteration or modification, executed by both parties. In the event +that any provision of this License Agreement shall be held by a court or other +tribunal of competent jurisdiction to be unenforceable, such provision will be +enforced to the maximum extent permissible and the remaining portions of this +License Agreement shall remain in full force and effect. This License Agreement +constitutes and contains the entire agreement between the parties with respect +to the subject matter hereof and supersedes any prior oral or written +agreements. + +I hereby acknowledge that I have read and understand the foregoing License +Agreement and agree that the action of installing the Program is an +acknowledgment of my agreement to be bound by the terms and conditions of the +License Agreement contained herein. I also acknowledge and agree that this +License Agreement is the complete and exclusive statement of the agreement +between Sierra and I and that the License Agreement supersedes any prior or +contemporaneous agreement, either oral or written, and any other communications +between Sierra and myself. diff --git a/sla/tribesnext.txt b/sla/tribesnext.txt new file mode 100644 index 0000000..3c4fb47 --- /dev/null +++ b/sla/tribesnext.txt @@ -0,0 +1,104 @@ + _______ __ __ _______ _______ ___ ___ _______ + |_ _|.----.|__|| |--..-----..-----.| | || ___|| | ||_ _| + | | | _|| || _ || -__||__ --|| || ___||- -| | | + |___| |__| |__||_____||_____||_____||__|____||_______||___|___| |___| + +----------------------------- License Agreement -------------------------------- + +YOU SHOULD CAREFULLY READ THE FOLLOWING LICENSE AGREEMENT BEFORE INSTALLING THIS +SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE PROGRAM, YOU +AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE +TERMS OF THIS AGREEMENT, PROMPTLY DELETE THE UNUSED SOFTWARE PROGRAM. + +Definitions: + 1. "This software program" is defined as the integrated, machine readable, + executable code, and application tools included as part of the TribesNext + match making service. + 2. "Associated intepreted script code" is defined as the human readable, + executable code, and all intermediate representations included as part of or + generated during utilization of the TribesNext match making service. + +This software program (the “Program”), associated interpreted script code, any +on-line or electronic documentation, and any and all copies and derivative works +of such software program are the copyrighted work of the TribesNext team, All +rights reserved, except as expressly stated herein. All use of the Program is +governed by the terms of this License Agreement provided below. The Program is +solely for use by end users according to the terms of the License Agreement. Any +use, reproduction or redistribution of the Program not in accordance with the +terms of the License Agreement is expressly prohibited. + + 1. Ownership. All title, ownership rights and intellectual property rights in + and to the Program and any and all copies thereof (including but not limited + to any titles, computer code, objects, concepts, artwork, methods of + operation, moral rights, any related documentation, and “applets” + incorporated into the Program) are owned by the TribesNext team or its + licensors. The Program is protected by the copyright laws of the United + States, international copyright treaties and conventions and other laws. All + rights are reserved. The Program contains certain licensed materials and + TribesNext's licensors may protect their rights in the event of any violation + of this Agreement. + 2. Termination. This License Agreement is effective until terminated. You may + terminate the License Agreement at any time by destroying the Program. + TribesNext may, at its discretion, terminate this License Agreement in the + event that you fail to comply with the terms and conditions contained herein. + In such event, you must immediately destroy the Program. + 3. Export Controls. The Program contains strong cryptography, thus the + Program may not be re-exported, downloaded or otherwise exported into (or to + a national or resident of) any country to which the U.S. has embargoed goods, + or to anyone on the U.S. Treasury Department’s list of Specially Designated + Nationals or the U.S. Commerce Department’s Table of Denial Orders. By + installing the Program, you are agreeing to the foregoing and you are + representing and warranting that you are not located in, under the control + of, or a national or resident of any such country or on any such list. + 4. Limited Warranty. TribesNext expressly disclaims any warranty for the + Program, components and any assorted documentation. The Program, components + and any assorted documentation are provided "as is" without warranty of any + kind, either express or implied, including, without limitation, the implied + warranties of merchantability, fitness for a particular purpose, or + noninfringement. The entire risk arising out of use or performance of the + Program, components and any assorted documentation remains with the User. + Some states do not allow the exclusion or limitation of implied warranties or + liability for incidental damages, so the above limitations may not apply to + you. + 5. Limitation of Liability. NEITHER TRIBESNEXT, SUBSIDIARIES OR AFFILIATES + SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY KIND RESULTING FROM THE + USE OF THE PROGRAM OR USE OF ASSOCIATED ONLINE SERVICES INCLUDING, BUT NOT + LIMITED TO, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, + OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES. TRIBESNEXT FURTHER + DISCLAIMS ALL WARRANTIES WITH REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. + SPECIFICALLY, TRIBESNEXT MAKES NO WARRANTIES THAT THE PERFORMANCE OR + FUNCTIONALITY OF THE PROGRAM WILL NOT BE AFFECTED BY DATES PRIOR TO, DURING + OR AFTER THE YEAR 2000, OR THAT THE PROGRAM WILL BE CAPABLE OF CORRECTLY + PROCESSING, PROVIDING, AND/OR RECEIVING DATE INFORMATION WITHIN AND BETWEEN + CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE INFORMATION BETWEEN PRODUCTS + OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT THAT MAY BE PROVIDED IN + SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR IN ANY OTHER + COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, TRIBESNEXT SHALL + NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER CHARACTERS, + ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON TribesNext.com. I + UNDERSTAND AND ACKNOWLEDGE THAT TRIBESNEXT. CANNOT AND WILL NOT BE + RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON TRIBESNEXT.COM INCLUDING, BUT + NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER + EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some + states do not allow the exclusion or limitation of incidental or + consequential damages, or allow limitations on how long an implied warranty + lasts, so the above limitations may not apply. + 6. Equitable Remedies. You hereby agree that TribesNext would be irreparably + damaged if the terms of this License Agreement were not specifically + enforced, and therefore you agree that TribesNext shall be entitled, without + bond, other security, or proof of damages, to appropriate equitable remedies + with respect to breaches of this License Agreement, in addition to such other + remedies as TribesNext may otherwise have available to it under applicable + laws. In the event any litigation is brought by either party in connection + with this License Agreement, the prevailing party in such litigation shall be + entitled to recover from the other party all the costs, attorneys’ fees and + other expenses incurred by such prevailing party in the litigation. + +I hereby acknowledge that I have read and understand the foregoing License +Agreement and agree that the action of installing the Program is an +acknowledgment of my agreement to be bound by the terms and conditions of the +License Agreement contained herein. I also acknowledge and agree that this +License Agreement is the complete and exclusive statement of the agreement +between TribesNext and I and that the License Agreement supersedes any prior or +contemporaneous agreement, either oral or written, and any other communications +between TribesNext and myself diff --git a/t2support.py b/t2support.py new file mode 120000 index 0000000..cce9e65 --- /dev/null +++ b/t2support.py @@ -0,0 +1 @@ +usr/local/bin/t2support.py \ No newline at end of file diff --git a/usr/local/bin/t2bouncer b/usr/local/bin/t2bouncer new file mode 100755 index 0000000..2098954 --- /dev/null +++ b/usr/local/bin/t2bouncer @@ -0,0 +1,31 @@ +#!/usr/bin/env -S python3 -B +import yaml +from datetime import datetime +from os import system +from t2support import * + +# Set default configuration +config_defaults = { + 'RestartTime' : False, + 'RestartDay' : 'Mon', +} + +# Read configuration from config.yaml +with open(f'{etc_dir}/config.yaml', 'r') as f: + loaded_config = yaml.full_load(f) + +# Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts. +# This ensures there are no undefined values in the case of a user removing one from config.yaml. +config = {**config_defaults, **loaded_config} + +now=datetime.now() + +if not config['RestartTime']: + print("RestartTime is disabled.") + +elif config['RestartTime'] > 0 and config['RestartTime'] < 25: + if config['RestartTime'] == int(now.strftime('%H')) and config['RestartDay'][:3].upper() == now.strftime('%a').upper(): + print("RestartTime and RestartDay match current time and day. Restarting t2server.service.") + system("/usr/bin/systemctl try-restart t2server.service") + else: + print("RestartTime and RestartDay do not match current time and day.") diff --git a/usr/local/bin/t2fixer b/usr/local/bin/t2fixer new file mode 100755 index 0000000..85fcfdc --- /dev/null +++ b/usr/local/bin/t2fixer @@ -0,0 +1,103 @@ +#!/usr/bin/env -S python3 -B +from os import geteuid, chmod, walk +from os.path import isdir, islink, join +from shutil import chown +from sys import exit as bail +from t2support import * + +if geteuid() != 0: + bail("This script must be run with sudo or as root.\n") + +def setperm(file): + if islink(file): + pass + elif isdir(file): + log.write(f"Setting mode 775 on directory {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.endswith("t2support.py"): + log.write(f"Setting mode 664 on file {file}") + try: + chmod(file,0o664) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.startswith(bin_dir): + log.write(f"Setting mode 775 on script {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.endswith(".exe"): + log.write(f"Setting mode 775 on exe {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + else: + log.write(f"Setting mode 664 on file {file}") + try: + chmod(file,0o664) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + + if islink(file): + pass + else: + try: + log.write(f"Setting owner/group on {file}") + chown(file, "t2server", "t2server") + log.write(f"\n") + except: + pwarn(f"Failed to set owner/group on {file}") + log.write(f"... FAILED!\n") + pass + + +with open(f'{log_dir}/t2fixer.log', 'w') as log: + setperm(install_dir) + setperm(etc_dir) + + for r, d, f in walk(f"{install_dir}/GameData"): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + for r, d, f in walk(etc_dir): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + for r, d, f in walk(log_dir): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + setperm(f"{unit_dir}/t2server.service") + setperm(f"{unit_dir}/t2bouncer.service") + setperm(f"{unit_dir}/t2bouncer.timer") + setperm(f"{bin_dir}/t2server") + setperm(f"{bin_dir}/t2bouncer") + setperm(f"{bin_dir}/t2remove") + setperm(f"{bin_dir}/t2fixer") + setperm(f"{bin_dir}/t2help") + setperm(f"{bin_dir}/t2support.py") \ No newline at end of file diff --git a/usr/local/bin/t2help b/usr/local/bin/t2help new file mode 100755 index 0000000..b5dcee2 --- /dev/null +++ b/usr/local/bin/t2help @@ -0,0 +1,22 @@ +#!/usr/bin/env -S python3 -B +from t2support import color + +print(f"\n{color.DC}The follow commands can be used to manage your Tribes 2 server:") + +print(f"""\n{color.BW}systemctl t2server{color.DC}: The t2server service can be managed with the +standard Linux systemctl command.""") + +print(f"""\n{color.BW}t2fixer{color.DC}: This command resets the owner and permissions on all files associated +with t2server. It's recommended to run this command any time you make changes +or add files (such as mods or map packs) to your server to ensure they can be +properly read and accessed. In the future, this command may also resolve other +common issues.""") + +print(f"""\n{color.BW}t2remove{color.DC}: This command uninstalls t2server and removes most associated files. +Configuration and serverprefs files will be left alone.""") + +print(f"\n{color.DC}Tribes 2 is installed in {color.BW}/opt/t2server{color.DC}.") +print(f"{color.DC}The {color.BW}/etc/t2server/config.yaml{color.DC} file holds basic service configuration values.") +print(f"{color.DC}The {color.BW}/etc/t2server/serverprefs{color.DC} directory holds your T2 serverprefs .cs files.") + +print(color.X) \ No newline at end of file diff --git a/usr/local/bin/t2remove b/usr/local/bin/t2remove new file mode 100755 index 0000000..bc64ccb --- /dev/null +++ b/usr/local/bin/t2remove @@ -0,0 +1,64 @@ +#!/usr/bin/env -S python3 -B +from os import system, unlink, geteuid +from sys import argv +from t2support import * + +if geteuid() != 0: + bail(f"This script must be run with sudo or as root.\n") + +if menu(["[Y]es, remove Tribes 2", "~~[N]o, exit t2remove"],header=f"This script will remove Tribes 2, service files, and utilities. {etc_dir} will be left as it is.") == "N": bail() + +system("/usr/bin/systemctl stop t2server.service") +system("/usr/sbin/userdel -fr t2server > /dev/null 2>&1") +system("/usr/sbin/groupdel t2server > /dev/null 2>&1") + +systemd_delete_failed=0 + +try: + unlink(f"{unit_dir}/t2bouncer.service") +except: + pwarn(f"Failed to delete {unit_dir}/t2bouncer.service") + systemd_delete_failed+=1 + +try: + unlink(f"{unit_dir}/t2bouncer.timer") +except: + pwarn(f"Failed to delete {unit_dir}/t2bouncer.timer") + systemd_delete_failed+=1 + +try: + unlink(f"{unit_dir}/t2server.service") +except: + pwarn(f"Failed to delete {unit_dir}/t2server.service") + systemd_delete_failed+=1 + + +if systemd_delete_failed > 0: + pinfo("After manually removing the files above, run 'systemctl daemon-reload' to refresh systemd.") +else: + system("/usr/bin/systemctl daemon-reload") + +try: + unlink(f"{bin_dir}/t2bouncer") +except: + pwarn(f"Failed to delete {bin_dir}/t2bouncer") + +try: + unlink(f"{bin_dir}/t2fixer") +except: + pwarn(f"Failed to delete {bin_dir}/t2fixer") + +try: + unlink(f"{bin_dir}/t2server") +except: + pwarn(f"Failed to delete {bin_dir}/t2server") + +try: + unlink(f"{bin_dir}/t2support.py") +except: + pwarn(f"Failed to delete {bin_dir}/t2support.py") + +try: + if argv[0].startswith(bin_dir): unlink(argv[0]) +except: + pwarn(f"Failed to delete {argv[0]}") \ No newline at end of file diff --git a/usr/local/bin/t2server b/usr/local/bin/t2server new file mode 100755 index 0000000..bc0498e --- /dev/null +++ b/usr/local/bin/t2server @@ -0,0 +1,192 @@ +#!/usr/bin/env -S python3 -B +import yaml +from os import unlink, symlink, chdir, getppid, makedirs +from os.path import isfile, islink, isdir, getmtime +from re import match +from glob import iglob +from subprocess import run, PIPE +from time import sleep +from requests import get +from threading import Thread +from t2support import * + +winecmd=["/usr/bin/wineconsole", "--backend=curses"] +argbase=["Tribes2.exe","-dedicated"] +basecmd=winecmd+argbase +parent=getppid() + +def dso_cleanup(): + """ + 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 + recompile them at startup. + """ + for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True): + cs_file=dso_file[:-4] + if isfile(cs_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): + """ + This function assembles the command line to launch the server based on the + config.yaml file. + """ + server_command=basecmd + if config['Public']: + server_command.append("-online") + else: + server_command.append("-nologin") + + if config['Mod'] != "base": + server_command.extend(["-mod", config['Mod']]) + + if config['ServerPrefs']: + server_command.extend(["-serverprefs","prefs\\ServerPrefs.cs"]) + + return server_command + +def server_files(config): + """ + This function creates a symlink to the specified /etc/t2server/serverprefs + file in the GameData//prefs directory where Tribes 2 expects to find it. + If MapList and MissionType are also configured, this funtion writes + GameData/base/prefs/missions.txt with the appropriate values for the + GameData/base/scripts/autoexec/missioncycle.cs script. If either MapList or + MissionType is False, an empty missions.txt is written. + """ + if config['ServerPrefs']: + if isfile(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") or islink(f"{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") + 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) + symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") + + if config["MapList"] and config["MissionType"]: + print(f"Writing {install_dir}/GameData/base/prefs/missions.txt") + 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: + for mission in config["MapList"]: + mlist.write(f"{config['MissionType']} {mission}\n") + else: + if isfile(f"{install_dir}/GameData/base/prefs/missions.txt"): + print(f"Purging {install_dir}/GameData/base/prefs/missions.txt") + # missions.txt needs to exist or the missioncycle.cs script will hang, so overwrite with an empty file + open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w').close() + + +def runaway_control(): + """ + When run in the background, wine will spawn a 'wineconsole --use-event=52' + process that will consume all available CPU. This function finds that + process and uses cpulimit to keep it under control. + """ + for x in range(20): + sleep(15) + print("Checking for runaway wineconsole process...") + runaway_pid=run(["/usr/bin/pgrep","-f","wineconsole --use-event=52"],stdout=PIPE).stdout + if runaway_pid: + runaway_pid=str(int(runaway_pid)) + print(f"Limiting runaway wineconsole process: {runaway_pid}") + run(["/usr/bin/cpulimit","-bp",runaway_pid,"-l2"]) + break + +def master_heartbeat(): + """ + 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 + inconsistent, so this function takes over that responsibility. + """ + print("Starting TribesNext heartbeat thread...") + while True: + get('http://master.tribesnext.com/add/28000') + sleep(240) + +def is_valid_ip(ip): + """Check if an ip looks like a valid IP address.""" + 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 run interactively, warn user that they probably don't want to do this unless troubleshooting. + if parent == 1: + print(f"Started by init") + 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.") + if interactive_run == 'N': bail() + 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 + with open(f'{etc_dir}/config.yaml', 'r') as f: + loaded_config = yaml.full_load(f) + + # Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts. + # This ensures there are no undefined values in the case of a user removing one from config.yaml. + config = {**config_defaults, **loaded_config} + print(config) + + # Validate the mod directory and serverprefs file + if not isdir(f"{install_dir}/GameData/{config['Mod']}"): + bail(f"Invalid Mod directory: {config['Mod']}") + if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"): + bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}") + + # Delete any pre-existing noMITM script/dso. 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/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 + server_files(config) + dso_cleanup() + server_command=build_args(basecmd,config) + + # If this is a public server, start a hearbeat thread. Also write the MITM override file if configured. + if config['Public']: + print("Starting heartbeat...") + if config['OverrideMITM']: override_mitm() + heartbeat=Thread(target=master_heartbeat) + heartbeat.daemon=True + heartbeat.start() + + # Cap the CPU of the runaway wineconsole process + wcpid_limit=Thread(target=runaway_control) + wcpid_limit.start() + + # Open the console log file if running as service and start the Tribes 2 server + print(f"Starting Tribes 2 server: " + " ".join(server_command)) + if parent == 1: + with open(f"{log_dir}/console.log", 'w') as consolelog: + run(server_command,stdout=consolelog) + else: + run(server_command) diff --git a/usr/local/bin/t2support.py b/usr/local/bin/t2support.py new file mode 100644 index 0000000..4dffe70 --- /dev/null +++ b/usr/local/bin/t2support.py @@ -0,0 +1,132 @@ +from tqdm import tqdm +from re import search +from requests import get +from hashlib import md5 +from os import walk +from os.path import join, islink +from shutil import chown +from sys import exit +from textwrap import wrap +from itertools import chain + +user = "t2server" +install_parent = "/opt" +install_dir = f"{install_parent}/t2server" +etc_dir = "/etc/t2server" +log_dir = "/var/log/t2server" +unit_dir = "/etc/systemd/system" +bin_dir = "/usr/local/bin" + +class color: + X = '\033[m' # Reset + DR = '\033[31m' # Dark Red + DG = '\033[32m' # Dark Green + DY = '\033[33m' # Dark Yellow (Brown) + DU = '\033[34m' # Dark Blue + DP = '\033[35m' # Dark Purple + DC = '\033[36m' # Dark Cyan + DW = '\033[37m' # Dark White (light grey) + BK = '\033[90m' # Bright Black (dark grey) + BR = '\033[91m' # Bright Red + BG = '\033[92m' # Bright Green + BY = '\033[93m' # Bright Yellow + BU = '\033[94m' # Bright Blue + BP = '\033[95m' # Bright Purple + BC = '\033[96m' # Bright Cyan + BW = '\033[97m' # Bright White + +class box: + TR = u'\u2510' + TL = u'\u250c' + BR = u'\u2518' + BL = u'\u2514' + H = u'\u2500' + V = u'\u2502' + +def menu(option_list,header="",footer=""): + """ + This function takes a list of options and make a BBS-style menu out of them. + Each item should have a unique alphanum in square brackets (eg. "[Q]uit" or "[1] Quit") + that represents the letter the user will type to select that option. Additionally, one + option may start with ~~ to indicate that it is the default option. This option will be + selected if the user presses Enter without typing a letter. ~~ will not be displayed on + screen. The function returns the selected letter in uppercase. + """ + default=None + keys=[] + line_list=list(option_list) + + if header: + header_list = wrap(header,width=76) + line_list.extend(header_list) + else: + header_list = None + + if footer: + footer_list = wrap(footer,width=76) + line_list.extend(footer_list) + else: + footer_list = None + + longest = max(list(chain((len(ele) for ele in line_list),[40]))) + + print(f"{color.BG}{box.TL}{box.H}{''.ljust(longest,box.H)}{box.H}{box.TR}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + if header_list: + for line in header_list: + print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + for option in option_list: + try: + key=search(r'\[([0-9a-zA-Z])\]', option).group(1) + except AttributeError: + pass + if option.startswith("~~"): + default = str(key) + keys.append(key.upper()) + option=option[2:] + else: + keys.append(str(key).lower()) + option=option.ljust(longest) + option=option.replace("[", color.BG + "[" + color.BW) + option=option.replace("]", color.BG + "]" + color.DG) + option=color.DG + option + color.X + print(f"{color.BG}{box.V} {option.ljust(longest)} {color.DG}{box.V}{color.X}") + if footer_list: + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + for line in footer_list: + print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + print(f"{color.BG}{box.BL}{color.DG}{box.H}{''.ljust(longest,box.H)}{box.H}{box.BR}{color.X}") + menu_choice=" " + if default: + prompt=f"{color.DG}Choice {color.BK}({color.BG}{default}{color.BK}){color.DG}: {color.X}" + else: + prompt=f"{color.DG}Choice: {color.X}" + while menu_choice.upper() not in [x.upper() for x in keys]: + menu_choice = str(input(prompt)) + if default and menu_choice == "": menu_choice = default + return menu_choice.upper() + +def chowner(path, owner): + """ Recursively chown path with owner as both user and group """ + for dirpath, ignore, filenames in walk(path): + chown(dirpath, owner, owner) + for filename in filenames: + if not islink(join(dirpath, filename)): + chown(join(dirpath, filename), owner, owner) + +def pinfo(message): + print(color.BU + message + color.X) + +def pwarn(message): + print(color.BY + message + color.X) + +def perror(message): + print(color.BR + message + color.X) + +def bail(message=""): + exit(color.BR + message + color.X) + +if __name__ == "__main__": + bail("This file contains only shared functions and variables for the other scripts and is not meant to be run interactively.") \ No newline at end of file diff --git a/winbin/install_wrapper.au3 b/winbin/install_wrapper.au3 new file mode 100644 index 0000000..68cc715 --- /dev/null +++ b/winbin/install_wrapper.au3 @@ -0,0 +1,82 @@ +; Define a logging function +Func WriteLog($ErrorMessage) + FileWriteLine("L:\install_wrapper.log", @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC & " " & $ErrorMessage) +EndFunc + +WriteLog("--- Starting script ---") + +; Run the Tribes 2 installer +WriteLog("Launching tribes2_gsi.exe: " & Run("I:\tribes2_gsi.exe")) + +; SLA +WriteLog("Wait for SLA window: " & WinWait("Software License Agreement", "Please read through the entire license agreement", 60)) +WriteLog("Click [I Agree]: " & ControlClick("Software License Agreement", "Please read through the entire license agreement", 4)) ; click I Agree + +; Welcome window +WriteLog("Wait for Welcome window: " & WinWait("Tribes 2", "Welcome to the Tribes 2 Installation!", 60)) +WriteLog("Click [Skip]: " & ControlClick("Tribes 2", "Welcome to the Tribes 2 Installation!", 4)) ; click "Skip >" + +; Tribes Vengeance preorder +WriteLog("Wait for Tribes Vengeance Preorder window: " & WinWait("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 60)) +WriteLog("Click [Next]: " & ControlClick("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 5)) ; click "Next >" + +; Welcome 2 +WriteLog("Wait for Welcome: " & WinWait("Welcome", "Welcome to the Tribes 2 Setup program", 60)) +WriteLog("Click [Next]: " & ControlClick("Welcome", "Welcome to the Tribes 2 Setup program", 3)) ; click "Next >" + +; Credits +WriteLog("Wait for Credits window: " & WinWait("Credits", "It's not often that the community of a game", 60)) +WriteLog("Click [Next]: " & ControlClick("Credits", "It's not often that the community of a game", 3)) ; click "Next >" + +; Choose Destination Location +WriteLog("Wait for Choose Destination window: " & WinWait("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 60)) +WriteLog("Click [Browse]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 9)) ; click "Browse" +WriteLog("Wait for Browse window: " & WinWait("Select Destination Directory", "", 60)) +WriteLog("Type 'T:\t2server': " & ControlSend("Select Destination Directory", "", 3, "T:\t2server")) ; update the destination path +WriteLog("Click [OK]: " & ControlClick("Select Destination Directory", "", 7)) ; click OK +WriteLog("Click [Yes] to use existing directory: " & ControlClick("Install", "The directory T:\t2server already exists", 1)) ; T:\t2server already exists, install anyway +WriteLog("Click [Next]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 3)) ; click "Next >" + +; Select Program Manager Group +WriteLog("Wait for Start Menu Group window: " & WinWait("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 60)) +WriteLog("Click [Next]: " & ControlClick("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 3)) ; click "Next >" + +; Start Installation +WriteLog("Wait for Start Installation window: " & WinWait("Start Installation", "You are now ready to install Tribes 2.", 60)) +WriteLog("Click [Next]: " & ControlClick("Start Installation", "You are now ready to install Tribes 2.", 3)) ; click "Next >" + +; Register +WriteLog("Wait for Register window: " & WinWait("Register", "You can register Tribes 2 on the World Wide Web.", 60)) +WriteLog("Uncheck 'Register Tribes 2 Now': " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 9)) ; uncheck "Register Tribes 2 Now" +WriteLog("Click [Next]: " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 3)) ; click "Next >" + +; DirectX 8a +WriteLog("Wait for DX8 Install window: " & WinWait("DirectX 8a", "DirectX 8a Install", 60)) +WriteLog("Uncheck 'Install DirectX 8a': " & ControlClick("DirectX 8a", "DirectX 8a Install", 9)) ; uncheck "Install DirectX 8a" +WriteLog("Click [Next]: " & ControlClick("DirectX 8a", "DirectX 8a Install", 3)) ; click "Next >" + +; Installation Complete +WriteLog("Wait for Installation Complete window: " & WinWait("Installation Complete", "Tribes 2 has been successfully installed.", 60)) +WriteLog("Uncheck 'Open the Tribes 2 Readme': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 9)) ; uncheck "Open the Tribes 2 Readme" +WriteLog("Uncheck 'Launch Tribes 2': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 10)) ; uncheck "Launch Tribes 2" +WriteLog("Click [Next]: " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 3)) ; click "Next >" + +; Wait up to a minute for the installer to complete and exit +WriteLog("Wait for tribes2_gsi.exe to exit: " & ProcessWaitClose("tribes2_gsi.exe", 60)) + +; Run the TribesNext Patch +WriteLog("Launching TribesNExt_rc2a.exe: " & Run("I:\TribesNext_rc2a.exe")) + +; SLA +WriteLog("Wait for License Agreement window: " & WinWait("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 60)) +WriteLog("Click [I Agree]: " & ControlClick("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 1)) ; click "I Agree" + +; Installation Folder +WriteLog("Wait for Completed window: " & WinWait("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 60)) +WriteLog("Click [Apply Patch]: " & ControlClick("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 1)) ; click "Apply Patch" + +; Completed +WriteLog("Wait for SLA window: " & WinWait("TribesNext Patcher: Completed", "Completed", 60)) +WriteLog("Click [Close]: " & ControlClick("TribesNext Patcher: Completed", "Completed", 1)) ; click "Close" + +WriteLog("--- Script Complete ---") \ No newline at end of file From 8a9f6b651031832609cd3c5a013e4794e95a395d Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sat, 2 Jan 2021 23:30:39 -0500 Subject: [PATCH 02/21] First Commit --- addons/readme.txt | 24 ++ etc/systemd/system/t2bouncer.service | 10 + etc/systemd/system/t2bouncer.timer | 10 + etc/systemd/system/t2server.service | 36 +++ etc/t2server/config.yaml | 44 ++++ etc/t2server/serverprefs/Classic_CTF.cs | 52 ++++ setup | 304 ++++++++++++++++++++++++ sla/tribes2.txt | 196 +++++++++++++++ sla/tribesnext.txt | 104 ++++++++ t2support.py | 1 + usr/local/bin/t2bouncer | 31 +++ usr/local/bin/t2fixer | 103 ++++++++ usr/local/bin/t2help | 22 ++ usr/local/bin/t2remove | 64 +++++ usr/local/bin/t2server | 192 +++++++++++++++ usr/local/bin/t2support.py | 132 ++++++++++ winbin/install_wrapper.au3 | 82 +++++++ 17 files changed, 1407 insertions(+) create mode 100644 addons/readme.txt create mode 100644 etc/systemd/system/t2bouncer.service create mode 100644 etc/systemd/system/t2bouncer.timer create mode 100644 etc/systemd/system/t2server.service create mode 100644 etc/t2server/config.yaml create mode 100644 etc/t2server/serverprefs/Classic_CTF.cs create mode 100755 setup create mode 100644 sla/tribes2.txt create mode 100644 sla/tribesnext.txt create mode 120000 t2support.py create mode 100755 usr/local/bin/t2bouncer create mode 100755 usr/local/bin/t2fixer create mode 100755 usr/local/bin/t2help create mode 100755 usr/local/bin/t2remove create mode 100755 usr/local/bin/t2server create mode 100644 usr/local/bin/t2support.py create mode 100644 winbin/install_wrapper.au3 diff --git a/addons/readme.txt b/addons/readme.txt new file mode 100644 index 0000000..482092b --- /dev/null +++ b/addons/readme.txt @@ -0,0 +1,24 @@ +This directory can hold addons that you want automatically added to your server. + +Any .vl2 files placed here will be copied into the /opt/t2server/GameData/base +directory. + +Any .zip, .tar, .tgz, or .tar.gz files placed here will be extracted into +/opt/t2server/GameData. This is intended for deploying mods. The setup script +will not create a directory, so be sure your archive contains the parent +directory for the mod. For example: + # tar -tf Version2.tar + Version2/ + Version2/V2_Features.txt + Version2/prefs/ + Version2/prefs/banlist.cs + Version2/prefs/11.29.2020Connect.log + Version2/prefs/ServerPrefs.cs + Version2/prefs/SctfPrefs.cs.dso + Version2/prefs/SctfPrefs.cs + Version2/prefs/ClientPrefs.cs + Version2/Version2.txt + ... + +If your mod archive doesn't start with a directory, it will just make a mess of +GameData when extracted. There are no safety checks in the script. diff --git a/etc/systemd/system/t2bouncer.service b/etc/systemd/system/t2bouncer.service new file mode 100644 index 0000000..e93a9ec --- /dev/null +++ b/etc/systemd/system/t2bouncer.service @@ -0,0 +1,10 @@ +[Unit] +Description=t2bouncer (restarts t2server at configured time) +Wants=t2bouncer.timer + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/t2bouncer + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/etc/systemd/system/t2bouncer.timer b/etc/systemd/system/t2bouncer.timer new file mode 100644 index 0000000..40359d7 --- /dev/null +++ b/etc/systemd/system/t2bouncer.timer @@ -0,0 +1,10 @@ +[Unit] +Description=t2bouncer timer +BindsTo=t2server.service + +[Timer] +Unit=t2bouncer.service +OnCalendar=*:00:00 + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/etc/systemd/system/t2server.service b/etc/systemd/system/t2server.service new file mode 100644 index 0000000..70b87cb --- /dev/null +++ b/etc/systemd/system/t2server.service @@ -0,0 +1,36 @@ +[Unit] +Description=Tribes 2 Dedicated Server +Requires=network.target +After=network.target +Wants=t2bouncer.timer +Before=t2bouncer.timer + +[Service] +Type=simple +Environment=TERM=xterm-256color +Environment=PYTHONUNBUFFERED=1 +User=t2server +CPUAffinity=0 +ExecStart=/usr/local/bin/t2server +ExecStop=/usr/bin/wineserver -k +Restart=on-failure +RestartSec=15s +TimeoutStopSec=60s +WorkingDirectory=/opt/t2server/GameData +LogsDirectory=t2server + +# Below settings help lock down the service for security +ProtectSystem=full +ProtectHome=true +SystemCallFilter=@system-service +NoNewPrivileges=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/etc/t2server/config.yaml b/etc/t2server/config.yaml new file mode 100644 index 0000000..c6d38c6 --- /dev/null +++ b/etc/t2server/config.yaml @@ -0,0 +1,44 @@ +--- +## ServerPrefs indicates the server config file in /etc/t2server/serverprefs +## to use. This is case-sensitive and must match the filename exactly. +ServerPrefs: Classic_CTF.cs + +## Tribes 2 servers tend to get unstable after a couple weeks of being online. +## Here you can specify a day and hour to automatically bounce the server. +## Set RestartTime to the hour of the day, 0-23, to cycle the server +## (eg. 4=4:00am, 16=4:00pm) or False to disable. Set RestartDay to the +## three-letter abbreviation of the day on which the server should be restarted +## (Sun, Mon, Tue, Wed, Thu, Fri, or Sat). This will be ignored if +## RestartTime = False. +RestartTime: False +RestartDay: Mon + +## Set Mod to the directory name of the mod to be loaded, or 'base' for +## vanilla (but why?). This is case-sensitive and must match the subdirectory +## name exactly. +Mod: Classic + +## Set Public to False to host a LAN-only game, or to True to host a public +## game which will be registered with the TribesNext master. +Public: False + +## 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. Enable OverrideMITM to effectively disable this +## detection. This setting has no effect if Public = False. +OverrideMITM: True + +## Configure a custom map rotation list. The standard Mission Types are +## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM" +## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege", +## "TeamHunters", and "TeamRabbit". Your server will always launch with the +## MissionType and Map specified in your serverprefs file ($Host::MissionType +## and $Host::Map), so make sure MissionType matches $Host::MissionType and +## the first map in MapList matches $Host::Map. If you're running a mod that +## handles map rotation, set these to 'False'. +## Example: +## MissionType: CTF +## MapList: ["Katabatic", "Minotaur", "Tombstone"] +MissionType: False +MapList: False \ No newline at end of file diff --git a/etc/t2server/serverprefs/Classic_CTF.cs b/etc/t2server/serverprefs/Classic_CTF.cs new file mode 100644 index 0000000..9cc7c46 --- /dev/null +++ b/etc/t2server/serverprefs/Classic_CTF.cs @@ -0,0 +1,52 @@ +$Host::AdminPassword = "changethis"; +$Host::allowAdminPlayerVotes = "0"; +$Host::AllowMapScript = 1; +$Host::BanTime = 1800; +$Host::BotCount = 7; +$Host::BotsEnabled = "0"; +$Host::ClassicSuperAdminPassword = "changethis"; +$Host::CRCTextures = 0; +$Host::FloodProtectionEnabled = 1; +$Host::GameName = "Tribes 2 Classic CTF Server"; +$Host::HiVisibility = "1"; +$Host::holoName1 = "Storm"; +$Host::holoName2 = "Inferno"; +$Host::holoName3 = "Starwolf"; +$Host::holoName4 = "DSword"; +$Host::holoName5 = "BloodEagle"; +$Host::holoName6 = "Harbinger"; +$Host::Info = "This is a Tribes 2 Classic Server."; +$Host::KickBanTime = 300; +$Host::Map = "Minotaur"; +$Host::MarkDnDObjectives = 1; +$Host::MaxMessageLen = 120; +$Host::MaxPlayers = "64"; +$Host::MissionType = "CTF"; +$Host::NoSmurfs = 0; +$Host::PlayerRespawnTimeout = "60"; +$Host::Port = "28000"; +$Host::PureServer = 0; +$Host::TeamDamageOn = "1"; +$Host::TeamName0 = "Unassigned"; +$Host::TeamName1 = "Storm"; +$Host::TeamName2 = "Inferno"; +$Host::TeamName3 = "Starwolf"; +$Host::TeamName4 = "Diamond Sword"; +$Host::TeamName5 = "Blood Eagle"; +$Host::TeamName6 = "Phoenix"; +$Host::TeamSkin0 = "blank"; +$Host::TeamSkin1 = "base"; +$Host::TeamSkin2 = "baseb"; +$Host::TeamSkin3 = "swolf"; +$Host::TeamSkin4 = "dsword"; +$Host::TeamSkin5 = "beagle"; +$Host::TeamSkin6 = "cotp"; +$Host::TimeLimit = "200"; +$Host::TN::beat = 3; +$Host::TN::echo = 1; +$Host::TournamentMode = "0"; +$Host::UseHighPerformanceCounter = 0; +$Host::VotePassPercent = "60"; +$Host::VoteSpread = 20; +$Host::VoteTime = "30"; +$Host::warmupTime = "20"; diff --git a/setup b/setup new file mode 100755 index 0000000..533426e --- /dev/null +++ b/setup @@ -0,0 +1,304 @@ +#!/usr/bin/env -S python3 -B +from os import system, unlink, symlink, makedirs, geteuid, getcwd, chmod, rename +from os.path import isfile, isdir +from time import time +from shutil import copyfile, rmtree +from glob import iglob +from pwd import getpwnam as getuser +from t2support import * + +if geteuid() != 0: + bail(f"This script must be run with sudo or as root.\n") + +pwd = getcwd() + +installer_mirror_list = [ + "https://www.the-construct.net/downloads/tribes2/tribes2gsi.exe", + "http://spinfusor.ch/tribes2/setup/tribes2_gsi.exe", + "https://adamantis.keybase.pub/Abandonware/Tribes2/tribes2gsi.exe?dl=1", + "http://xfer1.the-construct.net/tribes2/tribes2gsi.exe", + "http://dl.rawr32.net/tribes2gsi.exe", + "https://files.playt2.com/Install/tribes2gsi.exe", + "https://gamestand.net/dl/tribes-2/?ind=1527034109041&filename=tribes2_gsi.exe&wpdmdl=165&refresh=5fb884009d78b1605927936", + "http://www.tribes2stats.com/files/tribes2_gsi.exe" +] + +tnpatch_mirror_list = [ + "http://www.tribesnext.com/files/TribesNext_rc2a.exe", + "https://keybase.pub/adamantis/Abandonware/Tribes2/TribesNext_rc2a.exe", + "https://files.playt2.com/Install/TribesNext_rc2a.exe", + "https://gamestand.net/dl/tribes-2/?ind=1527034087554&filename=TribesNext_rc2a.exe&wpdmdl=165&refresh=5fb884009d73d1605927936", + "http://www.tribes2stats.com/files/patches/TribesNext_rc2a.exe", + "http://files.nastyhobbit.org/t2-installer/TribesNext_rc2a.exe", + "http://www.the-flet.com/dynamix/t2/TribesNext_rc2a.exe", + "http://cdn.net-load.com/TribesNext_rc2a.exe", + "https://starsiege.pw/_tribes2/TribesNext_rc2a.exe" +] + +installer_checksum = "93460541ddd3bdff9b30829ba04f2186" +tnpatch_checksum = "3bec757215cd29b37d85b567edf8d693" + +def md5sum(filename): + """ Return the md5 checksum of the given file """ + with open(filename, "rb") as file: + file_hash = md5() + chunk = file.read(8192) + while chunk: + file_hash.update(chunk) + chunk = file.read(8192) + return file_hash.hexdigest() + +def download_file(url, filename): + """ Download url with progress meter and save to filename """ + req = get(url, stream=True) + with open(filename, 'wb') as outfile: + pbar = tqdm(total=int(req.headers['Content-Length']), unit="B", unit_scale=True, unit_divisor=1024, position=0, desc=filename.split("/")[-1]) + for chunk in req.iter_content(chunk_size=1024*1024): + if chunk: + pbar.update(len(chunk)) + outfile.write(chunk) + pbar.close() + return filename + +if __name__ == "__main__": + + action=menu(["~~[C]ontinue","[Q]uit"],header="This script will install Tribes 2 for use as a dedicated server.") + if action == "Q": bail() + + # Check if user exists + try: + user_info = getuser(user) + except KeyError: + user_info = False + + # Create or repurpose user + if user_info: + if user_info.pw_dir == install_dir: + pwarn(f"User '{user}' exists and will be reused.") + else: + bail(f"ERROR: User '{user}' already exists and may belong to another person or process.") + else: + pinfo(f"Creating {user} user and {install_dir}.") + system(f"useradd -md {install_dir} {user}") + + if not user_info: user_info = getuser(user) + + # Create log_dir + pinfo(f"Creating {log_dir}.") + makedirs(log_dir, mode=0o777, exist_ok=True) + chmod(log_dir, 0o777) + + # Create .wine dir + pinfo(f"Creating {install_dir}/.wine defaults.") + system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'") + + # Map wine I: drive to pwd T: drive to install_dir and L: to log_dir + pinfo(f"Mapping I: in wine for {user}.") + try: + symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:") + except FileExistsError: + pass + + pinfo(f"Mapping L: in wine for {user}.") + try: + symlink(log_dir, f"{install_dir}/.wine/dosdevices/l:") + except FileExistsError: + pass + + pinfo(f"Mapping T: in wine for {user}.") + try: + symlink(install_parent, f"{install_dir}/.wine/dosdevices/t:") + except FileExistsError: + pass + + + # Check for needed exe/zip/dll files in winbin dir + needed_files=[] + if isfile(f"{pwd}/winbin/tribes2gsi.exe"): + pinfo("tribes2gsi.exe found.") + installer_exe = f"{pwd}/winbin/tribes2gsi.exe" + elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"): + pinfo("tribes2_gsi.exe found.") + installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" + else: + pwarn("Tribes 2 installer not found.") + needed_files.append("tribes2_gsi.exe") + installer_exe = False + + if isfile(f"{pwd}/winbin/TribesNext_rc2a.exe"): + pinfo("TribesNext_rc2a.exe found.") + tnpatch_exe = f"{pwd}/winbin/TribesNext_rc2a.exe" + else: + pwarn("Tribes Next patch not found.") + needed_files.append("TribesNext_rc2a.exe") + tnpatch_exe = False + + ruby_dll = f"{pwd}/winbin/msvcrt-ruby191.dll" + instwrap_exe = f"{pwd}/winbin/install_wrapper.exe" + + # Download files if needed + if not installer_exe or not tnpatch_exe: + action=menu(["~~[D]ownload automatically","[Q]uit"],header="One or more needed files were not found. Download automatically or quit so they can be manually placed in the 'winbin' subdirectory?") + if needed_files == 2: + needed_files=f"{needed_files[0]} and {needed_files[1]}" + if action=="Q": bail(f"Manually place {needed_files} in the 'winbin' subdirectory then rerun setup.") + + if not installer_exe: + for url in installer_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + installer_exe = download_file(url, f"{pwd}/winbin/tribes2_gsi.exe") + if md5sum(installer_exe) == installer_checksum: + pinfo("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror.") + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not installer_exe: + bail("ERROR: Tribes 2 installer could not be downloaded.") + + if not tnpatch_exe: + for url in tnpatch_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + tnpatch_exe = download_file(url, f"{pwd}/winbin/TribesNext_rc2a.exe") + if md5sum(tnpatch_exe) == tnpatch_checksum: + pinfo("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror." ) + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not tnpatch_exe: + bail("ERROR: Tribes Next patch could not be downloaded.") + + # Present SLAs before beginning install + sla = None + while not sla: + sla=menu(["[V]iew Tribes 2 and TribesNext License Agreements", "[A]ccept License Agreements", "[Q]uit"], header="Please take a moment to review and accept the Tribes 2 and TribeNext License Agreements before beginning automated install.") + if sla == "V": + print(color.DY) + system(f"/usr/bin/less {pwd}/sla/tribes2.txt") + print(color.DP) + system(f"/usr/bin/less {pwd}/sla/tribesnext.txt") + sla = None + elif sla == "A": + break + elif sla == "Q": + bail("You must accept the License Agreements to install.") + + # Ensure sufficient permissions on winbin and its contents + chmod(f"{pwd}/winbin", 0o777) + chmod(installer_exe, 0o777) + chmod(tnpatch_exe, 0o777) + chmod(instwrap_exe, 0o777) + chmod(ruby_dll, 0o777) + + # Execute install wrapper + pinfo(f"\nInstalling Tribes 2 and the TribesNext patch in wine. Please wait...") + chowner(install_dir, user) + system(f"su - {user} -c'xvfb-run -as " + '"-fbdir /var/tmp"' + " wine I:/install_wrapper.exe > /dev/null 2>&1'") + + # Rudamentary check to see if T2 install succeeded + if not isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): bail(f"ERROR: Tribes 2 installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Rudamentary check to see if TN install succeeded + if not isfile(f"{install_dir}/GameData/TN_Uninstall.exe"): bail(f"ERROR: Tribes Next installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Replace msvcrt-ruby190.dll with msvcrt-ruby191.dll + pinfo("Updating msvcrt-ruby190.dll to msvcrt-ruby191.dll.\n") + copyfile(ruby_dll,f"{install_dir}/GameData/msvcrt-ruby191.dll") + unlink(f"{install_dir}/GameData/msvcrt-ruby190.dll") + symlink(f"{install_dir}/GameData/msvcrt-ruby191.dll", f"{install_dir}/GameData/msvcrt-ruby190.dll") + + # Install addons + for addon in iglob(f"{pwd}/addons/*"): + if addon.endswith(".zip"): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"unzip -qqd {install_dir}/GameData {addon}") + elif addon.endswith((".tar",".tgz",".tar.gz",".txz",".tar.xz",".tbz",".tar.bz")): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"tar -C {install_dir}/GameData -xf {addon}") + elif addon.endswith(".vl2"): + pinfo(f"Copying {addon} to {install_dir}/GameData/base.") + copyfile(addon,f"{install_dir}/GameData/base/{addon.split('/')[-1]}") + elif addon.endswith("readme.txt"): + pass + else: + pwarn(f"Ignoring {addon}.") + + # Copy t2server and t2bouncer to /usr/local/bin/ + pinfo("Installing t2server script.") + copyfile(f"{pwd}/usr/local/bin/t2server",f"{bin_dir}/t2server") + pinfo("Installing t2bouncer script.") + copyfile(f"{pwd}/usr/local/bin/t2bouncer",f"{bin_dir}/t2bouncer") + + # Set owner/group on install_dir + chowner(install_dir, user) + + # Clean up temp dir and some unneeded files + pinfo("A little housekeeping...") + if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk") + if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk") + if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE") + if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt") + if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat") + if isfile(f"{install_dir}/GameData/Classic_dedicated_server.bat"): unlink(f"{install_dir}/GameData/Classic_dedicated_server.bat") + if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat") + if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt") + if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt") + if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual") + if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp") + if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt") + if isfile(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt") + if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt") + if isfile(f"{install_dir}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt") + if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.txt") + + # Create config directory and files + pinfo(f"\nCreating {etc_dir}, default config, and installing prefs files.") + makedirs(f"{etc_dir}/serverprefs", mode=0o775, exist_ok=True) + if isfile(f"{etc_dir}/config.yaml"): + timestamp = int(time()) + rename(f"{etc_dir}/config.yaml",f"{etc_dir}/config.yaml.{timestamp}") + pwarn(f"Existing {etc_dir}/config.yaml renamed to {etc_dir}/config.yaml.{timestamp}. Be sure to compare with and update the new config.yaml file.") + pinfo(f"Writing default {etc_dir}/config.yaml.") + copyfile(f"{pwd}/etc/t2server/config.yaml", f"{etc_dir}/config.yaml") + for pfile in iglob(f"{pwd}/etc/t2server/serverprefs/*"): + pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.") + copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}") + + # Create systemd units + pinfo("\nCreating systemd units:") + pinfo("- t2server service") + copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/t2server.service") + pinfo("- t2bouncer service") + copyfile(f"{pwd}/etc/systemd/system/t2bouncer.service",f"{unit_dir}/t2bouncer.service") + pinfo("- t2bouncer timer") + copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer") + system("systemctl daemon-reload") + + # Install utility scripts + pinfo("\nInstalling utilities:") + pinfo("- t2bouncer") + copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer") + pinfo("- t2remove") + copyfile(f"{pwd}/usr/local/bin/t2remove",f"{bin_dir}/t2remove") + pinfo("- t2help") + copyfile(f"{pwd}/usr/local/bin/t2help",f"{bin_dir}/t2help") + + # Install python module + copyfile(f"{pwd}/usr/local/bin/t2support.py",f"{bin_dir}/t2support.py") + chmod(f"{bin_dir}/t2fixer",0o777) + system(f"{bin_dir}/t2fixer") + + # Show help + system(f"{bin_dir}/t2help") + menu(['~~[E]xit'],header="You can run 't2help' at any time to view the info above again.") + + print(f"{color.X}\n") \ No newline at end of file diff --git a/sla/tribes2.txt b/sla/tribes2.txt new file mode 100644 index 0000000..a0430be --- /dev/null +++ b/sla/tribes2.txt @@ -0,0 +1,196 @@ + _______ __ __ _______ + | |.----.|__|| |--..-----..-----. | | + |.| | || _|| || _ || -__||__ --| |___| | + `-|. |-'|__| |__||_____||_____||_____| / ___/ + |: | |: 1 \ +--------------- |::.| ------- License Agreement -------- |::.. . | ------------- + `---' `-------' + +YOU SHOULD CAREFULLY READ THE FOLLOWING END USER LICENSE AGREEMENT BEFORE +INSTALLING THIS SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE +PROGRAM, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT +AGREE TO THE TERMS OF THIS AGREEMENT, PROMPTLY RETURN THE UNUSED SOFTWARE +PROGRAM TO THE PLACE OF PURCHASE OR CONTACT SIERRA ON-LINE, INC. CUSTOMER +SERVICE AT (425) 746-5771 FOR A FULL REFUND OF THE PURCHASE PRICE WITHIN 30 DAYS +OF THE ORIGINAL PURCHASE. + +This software program (the "Program"), any printed materials, any on-line or +electronic documentation, and any and all copies and derivative works of such +software program (including materials created with a so called level editor, if +included) and materials are the copyrighted work of Sierra On-Line, Inc., a +division of Havas Interactive, Inc. and/or its wholly owned subsidiaries, or its +suppliers. All rights reserved, except as expressly stated herein. All use of +the Program is governed by the terms of this End User License Agreement provided +below ("License Agreement"). The Program is solely for use by end users +according to the terms of the License Agreement. Any use, reproduction or +redistribution of the Program not in accordance with the terms of the License +Agreement is expressly prohibited. + +END USER LICENSE AGREEMENT + +1. Limited Use License. Sierra On-Line, Inc. ("Sierra ") hereby grants, and by +installing the Program you thereby accept, a limited, non-exclusive license and +right to install and use one (1) copy of the Program for your use on either a +home, business or portable computer. In addition, the Program has a multi-player +capability that allows users to utilize the Program over the Internet via +Sierra's online game network Sierra.com. Use of the Program over Sierra.com is +subject to your acceptance of Sierra.com's Terms of Use Agreement. Sierra +On-Line, Inc. reserves the right to update, modify or change the Sierra.com +Terms of Use Agreement at any time. The Program may also contain a Level Editor +(the "Editor") that allows you to create custom levels or other materials for +your personal use in connection with the Program ("New Materials"). All use of +the Editor or any New Materials is subject to this License Agreement. The +Program is licensed, not sold. Your license confers no title or ownership in the +Program. + +2. Ownership. All title, ownership rights and intellectual property rights in +and to the Program and any and all copies thereof (including but not limited to +any titles, computer code, themes, objects, characters, character names, +stories, dialog, catch phrases, locations, concepts, artwork, animations, +sounds, musical compositions, audio-visual effects, methods of operation, moral +rights, any related documentation, and "applets" incorporated into the Program) +are owned by Sierra On-Line, Inc. or its licensors. The Program is protected by +the copyright laws of the United States, international copyright treaties and +conventions and other laws. All rights are reserved. The Program contains +certain licensed materials and Sierra 's licensors may protect their rights in +the event of any violation of this Agreement. + +3. Responsibilities of End User. + A. Subject to the Grant of License hereinabove, you may not, in whole or in + part, copy, photocopy, reproduce, translate, reverse engineer, derive source + code, modify, disassemble, decompile, create derivative works based on the + Program, or remove any proprietary notices or labels on the Program without + the prior consent, in writing, of Sierra. + B. The Program is licensed to you as a single product. Its component parts + may not be separated for use on more than one computer. + C. You are entitled to use the Program for your own use, but you are not + entitled to: + (i) sell, grant a security interest in or transfer reproductions of the + Program to other parties in any way, nor to rent, lease or license the + Program to others without the prior written consent of Sierra. + (ii) exploit the Program or any of its parts for any commercial purpose + including, but not limited to, use at a cyber café, computer gaming center + or any other location-based site. Sierra may offer a separate Site License + Agreement to permit you to make the Program available for commercial use; + contact Sierra for details; + (iii) host or provide matchmaking services for the Program or emulate or + redirect the communication protocols used by Sierra in the network feature + of the Program, through protocol emulation, tunneling, modifying or adding + components to the Program, use of a utility program or any other + techniques now known or hereafter developed, for any purpose including, + but not limited to network play over the Internet, network play utilizing + commercial or non-commercial gaming networks or as part of content + aggregation networks without the prior written consent of Sierra ; + (iv) create or maintain, under any circumstance, more than one + simultaneous connection to Sierra.com. All such connections to Sierra.com, + whether created by the Program or by other tools and utilities, may only + be made through methods and means expressly approved by Sierra On-Line, + Inc. Under no circumstances may you connect, or create tools that allow + you to connect to Sierra.com's private binary interface or interfaces + other than those explicitly provided by Sierra On-Line, Inc. for public + use. + +4. Program Transfer. You may permanently transfer all of your rights under this +License Agreement, provided the recipient agrees to the terms of this License +Agreement and you agree to remove the Program from your home or portable +computer. + +5. Termination. This License Agreement is effective until terminated. You may +terminate the License Agreement at any time by destroying the Program. Sierra +may, at its discretion, terminate this License Agreement in the event that you +fail to comply with the terms and conditions contained herein. In such event, +you must immediately destroy the Program. + +6. Export Controls. The Program may not be re-exported, downloaded or otherwise +exported into (or to a national or resident of) any country to which the U.S. +has embargoed goods, or to anyone on the U.S. Treasury Department's list of +Specially Designated Nationals or the U.S. Commerce Department's Table of Denial +Orders. By installing the Program, you are agreeing to the foregoing and you are +representing and warranting that you are not located in, under the control of, +or a national or resident of any such country or on any such list. + +7. Limited Warranty. Sierra expressly disclaims any warranty for the Program, +Editor and Manual(s). The Program, Editor and Manual(s) are provided "as is" +without warranty of any kind, either express or implied, including, without +limitation, the implied warranties of merchantability, fitness for a particular +purpose, or noninfringement. The entire risk arising out of use or performance +of the Program and Manual(s) remains with the User, however Sierra warrants up +to and including 90 days from the date of your purchase of the Program that the +media containing the Program shall be free from defects in material and +workmanship. In the event that the media proves to be defective during that time +period, and upon presentation to Sierra of proof of purchase of the defective +Program, Sierra will at its option 1) correct any defect, 2) provide you with a +product of equal or lesser value, or 3) refund your money. Some states do not +allow the exclusion or limitation of implied warranties or liability for +incidental damages, so the above limitations may not apply to you. + +8. Limitation of Liability. NEITHER SIERRA, HAVAS INTERACTIVE, INC., ITS PARENT, +SUBSIDIARIES OR AFFILIATES SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY +KIND RESULTING FROM THE USE OF THE PROGRAM OR USE OF SIERRA ON-LINE, INC.'S +ONLINE GAME NETWORK, SIERRA.COM INCLUDING, BUT NOT LIMITED TO, LOSS OF +GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER +COMMERCIAL DAMAGES OR LOSSES. SIERRA FURTHER DISCLAIMS ALL WARRANTIES WITH +REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. SPECIFICALLY, SIERRA MAKES NO +WARRANTIES THAT THE PERFORMANCE OR FUNCTIONALITY OF THE PROGRAM WILL NOT BE +AFFECTED BY DATES PRIOR TO, DURING OR AFTER THE YEAR 2000, OR THAT THE PROGRAM +WILL BE CAPABLE OF CORRECTLY PROCESSING, PROVIDING, AND/OR RECEIVING DATE +INFORMATION WITHIN AND BETWEEN CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE +INFORMATION BETWEEN PRODUCTS OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT +THAT MAY BE PROVIDED IN SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR +IN ANY OTHER COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, Sierra +On-Line, Inc. SHALL NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER +CHARACTERS, ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON +SIERRA.COM. I UNDERSTAND AND ACKNOWLEDGE THAT SIERRA ON-LINE, INC. CANNOT AND +WILL NOT BE RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON SIERRA.COM INCLUDING, +BUT NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER +EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some states +do not allow the exclusion or limitation of incidental or consequential damages, +or allow limitations on how long an implied warranty lasts, so the above +limitations may not apply. + +9. Equitable Remedies. You hereby agree that Sierra would be irreparably damaged +if the terms of this License Agreement were not specifically enforced, and +therefore you agree that Sierra shall be entitled, without bond, other security, +or proof of damages, to appropriate equitable remedies with respect to breaches +of this License Agreement, in addition to such other remedies as Sierra may +otherwise have available to it under applicable laws. In the event any +litigation is brought by either party in connection with this License Agreement, +the prevailing party in such litigation shall be entitled to recover from the +other party all the costs, attorneys' fees and other expenses incurred by such +prevailing party in the litigation. + +10. Limitations on License. Nothing in this License Agreement shall preclude +you from making or authorizing the making of another copy or adaptation of the +Program provided, however, that (1) such new copy or adaptation is created as an +essential step in your utilization of the Program in accordance with the terms +of this License Agreement and for NO OTHER PURPOSE; or (2) such new copy or +adaptation is for archival purposes ONLY and all archival copies are destroyed +in the event of your Transfer of the Program, the Termination of this Agreement +or other circumstances under which your continued use of the Program ceases to +be rightful. + +11. Miscellaneous. This License Agreement shall be deemed to have been made and +executed in the State of California and any dispute arising hereunder shall be +resolved in accordance with the law of California. You agree that any claim +asserted in any legal proceeding by one of the parties against the other shall +be commenced and maintained in any state or federal court located in the State +of California, County of Los Angeles, having subject matter jurisdiction with +respect to the dispute between the parties. This License Agreement may be +amended, altered or modified only by an instrument in writing, specifying such +amendment, alteration or modification, executed by both parties. In the event +that any provision of this License Agreement shall be held by a court or other +tribunal of competent jurisdiction to be unenforceable, such provision will be +enforced to the maximum extent permissible and the remaining portions of this +License Agreement shall remain in full force and effect. This License Agreement +constitutes and contains the entire agreement between the parties with respect +to the subject matter hereof and supersedes any prior oral or written +agreements. + +I hereby acknowledge that I have read and understand the foregoing License +Agreement and agree that the action of installing the Program is an +acknowledgment of my agreement to be bound by the terms and conditions of the +License Agreement contained herein. I also acknowledge and agree that this +License Agreement is the complete and exclusive statement of the agreement +between Sierra and I and that the License Agreement supersedes any prior or +contemporaneous agreement, either oral or written, and any other communications +between Sierra and myself. diff --git a/sla/tribesnext.txt b/sla/tribesnext.txt new file mode 100644 index 0000000..3c4fb47 --- /dev/null +++ b/sla/tribesnext.txt @@ -0,0 +1,104 @@ + _______ __ __ _______ _______ ___ ___ _______ + |_ _|.----.|__|| |--..-----..-----.| | || ___|| | ||_ _| + | | | _|| || _ || -__||__ --|| || ___||- -| | | + |___| |__| |__||_____||_____||_____||__|____||_______||___|___| |___| + +----------------------------- License Agreement -------------------------------- + +YOU SHOULD CAREFULLY READ THE FOLLOWING LICENSE AGREEMENT BEFORE INSTALLING THIS +SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE PROGRAM, YOU +AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE +TERMS OF THIS AGREEMENT, PROMPTLY DELETE THE UNUSED SOFTWARE PROGRAM. + +Definitions: + 1. "This software program" is defined as the integrated, machine readable, + executable code, and application tools included as part of the TribesNext + match making service. + 2. "Associated intepreted script code" is defined as the human readable, + executable code, and all intermediate representations included as part of or + generated during utilization of the TribesNext match making service. + +This software program (the “Program”), associated interpreted script code, any +on-line or electronic documentation, and any and all copies and derivative works +of such software program are the copyrighted work of the TribesNext team, All +rights reserved, except as expressly stated herein. All use of the Program is +governed by the terms of this License Agreement provided below. The Program is +solely for use by end users according to the terms of the License Agreement. Any +use, reproduction or redistribution of the Program not in accordance with the +terms of the License Agreement is expressly prohibited. + + 1. Ownership. All title, ownership rights and intellectual property rights in + and to the Program and any and all copies thereof (including but not limited + to any titles, computer code, objects, concepts, artwork, methods of + operation, moral rights, any related documentation, and “applets” + incorporated into the Program) are owned by the TribesNext team or its + licensors. The Program is protected by the copyright laws of the United + States, international copyright treaties and conventions and other laws. All + rights are reserved. The Program contains certain licensed materials and + TribesNext's licensors may protect their rights in the event of any violation + of this Agreement. + 2. Termination. This License Agreement is effective until terminated. You may + terminate the License Agreement at any time by destroying the Program. + TribesNext may, at its discretion, terminate this License Agreement in the + event that you fail to comply with the terms and conditions contained herein. + In such event, you must immediately destroy the Program. + 3. Export Controls. The Program contains strong cryptography, thus the + Program may not be re-exported, downloaded or otherwise exported into (or to + a national or resident of) any country to which the U.S. has embargoed goods, + or to anyone on the U.S. Treasury Department’s list of Specially Designated + Nationals or the U.S. Commerce Department’s Table of Denial Orders. By + installing the Program, you are agreeing to the foregoing and you are + representing and warranting that you are not located in, under the control + of, or a national or resident of any such country or on any such list. + 4. Limited Warranty. TribesNext expressly disclaims any warranty for the + Program, components and any assorted documentation. The Program, components + and any assorted documentation are provided "as is" without warranty of any + kind, either express or implied, including, without limitation, the implied + warranties of merchantability, fitness for a particular purpose, or + noninfringement. The entire risk arising out of use or performance of the + Program, components and any assorted documentation remains with the User. + Some states do not allow the exclusion or limitation of implied warranties or + liability for incidental damages, so the above limitations may not apply to + you. + 5. Limitation of Liability. NEITHER TRIBESNEXT, SUBSIDIARIES OR AFFILIATES + SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY KIND RESULTING FROM THE + USE OF THE PROGRAM OR USE OF ASSOCIATED ONLINE SERVICES INCLUDING, BUT NOT + LIMITED TO, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, + OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES. TRIBESNEXT FURTHER + DISCLAIMS ALL WARRANTIES WITH REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. + SPECIFICALLY, TRIBESNEXT MAKES NO WARRANTIES THAT THE PERFORMANCE OR + FUNCTIONALITY OF THE PROGRAM WILL NOT BE AFFECTED BY DATES PRIOR TO, DURING + OR AFTER THE YEAR 2000, OR THAT THE PROGRAM WILL BE CAPABLE OF CORRECTLY + PROCESSING, PROVIDING, AND/OR RECEIVING DATE INFORMATION WITHIN AND BETWEEN + CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE INFORMATION BETWEEN PRODUCTS + OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT THAT MAY BE PROVIDED IN + SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR IN ANY OTHER + COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, TRIBESNEXT SHALL + NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER CHARACTERS, + ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON TribesNext.com. I + UNDERSTAND AND ACKNOWLEDGE THAT TRIBESNEXT. CANNOT AND WILL NOT BE + RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON TRIBESNEXT.COM INCLUDING, BUT + NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER + EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some + states do not allow the exclusion or limitation of incidental or + consequential damages, or allow limitations on how long an implied warranty + lasts, so the above limitations may not apply. + 6. Equitable Remedies. You hereby agree that TribesNext would be irreparably + damaged if the terms of this License Agreement were not specifically + enforced, and therefore you agree that TribesNext shall be entitled, without + bond, other security, or proof of damages, to appropriate equitable remedies + with respect to breaches of this License Agreement, in addition to such other + remedies as TribesNext may otherwise have available to it under applicable + laws. In the event any litigation is brought by either party in connection + with this License Agreement, the prevailing party in such litigation shall be + entitled to recover from the other party all the costs, attorneys’ fees and + other expenses incurred by such prevailing party in the litigation. + +I hereby acknowledge that I have read and understand the foregoing License +Agreement and agree that the action of installing the Program is an +acknowledgment of my agreement to be bound by the terms and conditions of the +License Agreement contained herein. I also acknowledge and agree that this +License Agreement is the complete and exclusive statement of the agreement +between TribesNext and I and that the License Agreement supersedes any prior or +contemporaneous agreement, either oral or written, and any other communications +between TribesNext and myself diff --git a/t2support.py b/t2support.py new file mode 120000 index 0000000..cce9e65 --- /dev/null +++ b/t2support.py @@ -0,0 +1 @@ +usr/local/bin/t2support.py \ No newline at end of file diff --git a/usr/local/bin/t2bouncer b/usr/local/bin/t2bouncer new file mode 100755 index 0000000..2098954 --- /dev/null +++ b/usr/local/bin/t2bouncer @@ -0,0 +1,31 @@ +#!/usr/bin/env -S python3 -B +import yaml +from datetime import datetime +from os import system +from t2support import * + +# Set default configuration +config_defaults = { + 'RestartTime' : False, + 'RestartDay' : 'Mon', +} + +# Read configuration from config.yaml +with open(f'{etc_dir}/config.yaml', 'r') as f: + loaded_config = yaml.full_load(f) + +# Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts. +# This ensures there are no undefined values in the case of a user removing one from config.yaml. +config = {**config_defaults, **loaded_config} + +now=datetime.now() + +if not config['RestartTime']: + print("RestartTime is disabled.") + +elif config['RestartTime'] > 0 and config['RestartTime'] < 25: + if config['RestartTime'] == int(now.strftime('%H')) and config['RestartDay'][:3].upper() == now.strftime('%a').upper(): + print("RestartTime and RestartDay match current time and day. Restarting t2server.service.") + system("/usr/bin/systemctl try-restart t2server.service") + else: + print("RestartTime and RestartDay do not match current time and day.") diff --git a/usr/local/bin/t2fixer b/usr/local/bin/t2fixer new file mode 100755 index 0000000..85fcfdc --- /dev/null +++ b/usr/local/bin/t2fixer @@ -0,0 +1,103 @@ +#!/usr/bin/env -S python3 -B +from os import geteuid, chmod, walk +from os.path import isdir, islink, join +from shutil import chown +from sys import exit as bail +from t2support import * + +if geteuid() != 0: + bail("This script must be run with sudo or as root.\n") + +def setperm(file): + if islink(file): + pass + elif isdir(file): + log.write(f"Setting mode 775 on directory {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.endswith("t2support.py"): + log.write(f"Setting mode 664 on file {file}") + try: + chmod(file,0o664) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.startswith(bin_dir): + log.write(f"Setting mode 775 on script {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + elif file.endswith(".exe"): + log.write(f"Setting mode 775 on exe {file}") + try: + chmod(file,0o775) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + else: + log.write(f"Setting mode 664 on file {file}") + try: + chmod(file,0o664) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass + + if islink(file): + pass + else: + try: + log.write(f"Setting owner/group on {file}") + chown(file, "t2server", "t2server") + log.write(f"\n") + except: + pwarn(f"Failed to set owner/group on {file}") + log.write(f"... FAILED!\n") + pass + + +with open(f'{log_dir}/t2fixer.log', 'w') as log: + setperm(install_dir) + setperm(etc_dir) + + for r, d, f in walk(f"{install_dir}/GameData"): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + for r, d, f in walk(etc_dir): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + for r, d, f in walk(log_dir): + for file in d: + setperm(join(r, file)) + for file in f: + setperm(join(r, file)) + + setperm(f"{unit_dir}/t2server.service") + setperm(f"{unit_dir}/t2bouncer.service") + setperm(f"{unit_dir}/t2bouncer.timer") + setperm(f"{bin_dir}/t2server") + setperm(f"{bin_dir}/t2bouncer") + setperm(f"{bin_dir}/t2remove") + setperm(f"{bin_dir}/t2fixer") + setperm(f"{bin_dir}/t2help") + setperm(f"{bin_dir}/t2support.py") \ No newline at end of file diff --git a/usr/local/bin/t2help b/usr/local/bin/t2help new file mode 100755 index 0000000..b5dcee2 --- /dev/null +++ b/usr/local/bin/t2help @@ -0,0 +1,22 @@ +#!/usr/bin/env -S python3 -B +from t2support import color + +print(f"\n{color.DC}The follow commands can be used to manage your Tribes 2 server:") + +print(f"""\n{color.BW}systemctl t2server{color.DC}: The t2server service can be managed with the +standard Linux systemctl command.""") + +print(f"""\n{color.BW}t2fixer{color.DC}: This command resets the owner and permissions on all files associated +with t2server. It's recommended to run this command any time you make changes +or add files (such as mods or map packs) to your server to ensure they can be +properly read and accessed. In the future, this command may also resolve other +common issues.""") + +print(f"""\n{color.BW}t2remove{color.DC}: This command uninstalls t2server and removes most associated files. +Configuration and serverprefs files will be left alone.""") + +print(f"\n{color.DC}Tribes 2 is installed in {color.BW}/opt/t2server{color.DC}.") +print(f"{color.DC}The {color.BW}/etc/t2server/config.yaml{color.DC} file holds basic service configuration values.") +print(f"{color.DC}The {color.BW}/etc/t2server/serverprefs{color.DC} directory holds your T2 serverprefs .cs files.") + +print(color.X) \ No newline at end of file diff --git a/usr/local/bin/t2remove b/usr/local/bin/t2remove new file mode 100755 index 0000000..bc64ccb --- /dev/null +++ b/usr/local/bin/t2remove @@ -0,0 +1,64 @@ +#!/usr/bin/env -S python3 -B +from os import system, unlink, geteuid +from sys import argv +from t2support import * + +if geteuid() != 0: + bail(f"This script must be run with sudo or as root.\n") + +if menu(["[Y]es, remove Tribes 2", "~~[N]o, exit t2remove"],header=f"This script will remove Tribes 2, service files, and utilities. {etc_dir} will be left as it is.") == "N": bail() + +system("/usr/bin/systemctl stop t2server.service") +system("/usr/sbin/userdel -fr t2server > /dev/null 2>&1") +system("/usr/sbin/groupdel t2server > /dev/null 2>&1") + +systemd_delete_failed=0 + +try: + unlink(f"{unit_dir}/t2bouncer.service") +except: + pwarn(f"Failed to delete {unit_dir}/t2bouncer.service") + systemd_delete_failed+=1 + +try: + unlink(f"{unit_dir}/t2bouncer.timer") +except: + pwarn(f"Failed to delete {unit_dir}/t2bouncer.timer") + systemd_delete_failed+=1 + +try: + unlink(f"{unit_dir}/t2server.service") +except: + pwarn(f"Failed to delete {unit_dir}/t2server.service") + systemd_delete_failed+=1 + + +if systemd_delete_failed > 0: + pinfo("After manually removing the files above, run 'systemctl daemon-reload' to refresh systemd.") +else: + system("/usr/bin/systemctl daemon-reload") + +try: + unlink(f"{bin_dir}/t2bouncer") +except: + pwarn(f"Failed to delete {bin_dir}/t2bouncer") + +try: + unlink(f"{bin_dir}/t2fixer") +except: + pwarn(f"Failed to delete {bin_dir}/t2fixer") + +try: + unlink(f"{bin_dir}/t2server") +except: + pwarn(f"Failed to delete {bin_dir}/t2server") + +try: + unlink(f"{bin_dir}/t2support.py") +except: + pwarn(f"Failed to delete {bin_dir}/t2support.py") + +try: + if argv[0].startswith(bin_dir): unlink(argv[0]) +except: + pwarn(f"Failed to delete {argv[0]}") \ No newline at end of file diff --git a/usr/local/bin/t2server b/usr/local/bin/t2server new file mode 100755 index 0000000..bc0498e --- /dev/null +++ b/usr/local/bin/t2server @@ -0,0 +1,192 @@ +#!/usr/bin/env -S python3 -B +import yaml +from os import unlink, symlink, chdir, getppid, makedirs +from os.path import isfile, islink, isdir, getmtime +from re import match +from glob import iglob +from subprocess import run, PIPE +from time import sleep +from requests import get +from threading import Thread +from t2support import * + +winecmd=["/usr/bin/wineconsole", "--backend=curses"] +argbase=["Tribes2.exe","-dedicated"] +basecmd=winecmd+argbase +parent=getppid() + +def dso_cleanup(): + """ + 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 + recompile them at startup. + """ + for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True): + cs_file=dso_file[:-4] + if isfile(cs_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): + """ + This function assembles the command line to launch the server based on the + config.yaml file. + """ + server_command=basecmd + if config['Public']: + server_command.append("-online") + else: + server_command.append("-nologin") + + if config['Mod'] != "base": + server_command.extend(["-mod", config['Mod']]) + + if config['ServerPrefs']: + server_command.extend(["-serverprefs","prefs\\ServerPrefs.cs"]) + + return server_command + +def server_files(config): + """ + This function creates a symlink to the specified /etc/t2server/serverprefs + file in the GameData//prefs directory where Tribes 2 expects to find it. + If MapList and MissionType are also configured, this funtion writes + GameData/base/prefs/missions.txt with the appropriate values for the + GameData/base/scripts/autoexec/missioncycle.cs script. If either MapList or + MissionType is False, an empty missions.txt is written. + """ + if config['ServerPrefs']: + if isfile(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") or islink(f"{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") + 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) + symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") + + if config["MapList"] and config["MissionType"]: + print(f"Writing {install_dir}/GameData/base/prefs/missions.txt") + 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: + for mission in config["MapList"]: + mlist.write(f"{config['MissionType']} {mission}\n") + else: + if isfile(f"{install_dir}/GameData/base/prefs/missions.txt"): + print(f"Purging {install_dir}/GameData/base/prefs/missions.txt") + # missions.txt needs to exist or the missioncycle.cs script will hang, so overwrite with an empty file + open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w').close() + + +def runaway_control(): + """ + When run in the background, wine will spawn a 'wineconsole --use-event=52' + process that will consume all available CPU. This function finds that + process and uses cpulimit to keep it under control. + """ + for x in range(20): + sleep(15) + print("Checking for runaway wineconsole process...") + runaway_pid=run(["/usr/bin/pgrep","-f","wineconsole --use-event=52"],stdout=PIPE).stdout + if runaway_pid: + runaway_pid=str(int(runaway_pid)) + print(f"Limiting runaway wineconsole process: {runaway_pid}") + run(["/usr/bin/cpulimit","-bp",runaway_pid,"-l2"]) + break + +def master_heartbeat(): + """ + 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 + inconsistent, so this function takes over that responsibility. + """ + print("Starting TribesNext heartbeat thread...") + while True: + get('http://master.tribesnext.com/add/28000') + sleep(240) + +def is_valid_ip(ip): + """Check if an ip looks like a valid IP address.""" + 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 run interactively, warn user that they probably don't want to do this unless troubleshooting. + if parent == 1: + print(f"Started by init") + 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.") + if interactive_run == 'N': bail() + 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 + with open(f'{etc_dir}/config.yaml', 'r') as f: + loaded_config = yaml.full_load(f) + + # Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts. + # This ensures there are no undefined values in the case of a user removing one from config.yaml. + config = {**config_defaults, **loaded_config} + print(config) + + # Validate the mod directory and serverprefs file + if not isdir(f"{install_dir}/GameData/{config['Mod']}"): + bail(f"Invalid Mod directory: {config['Mod']}") + if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"): + bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}") + + # Delete any pre-existing noMITM script/dso. 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/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 + server_files(config) + dso_cleanup() + server_command=build_args(basecmd,config) + + # If this is a public server, start a hearbeat thread. Also write the MITM override file if configured. + if config['Public']: + print("Starting heartbeat...") + if config['OverrideMITM']: override_mitm() + heartbeat=Thread(target=master_heartbeat) + heartbeat.daemon=True + heartbeat.start() + + # Cap the CPU of the runaway wineconsole process + wcpid_limit=Thread(target=runaway_control) + wcpid_limit.start() + + # Open the console log file if running as service and start the Tribes 2 server + print(f"Starting Tribes 2 server: " + " ".join(server_command)) + if parent == 1: + with open(f"{log_dir}/console.log", 'w') as consolelog: + run(server_command,stdout=consolelog) + else: + run(server_command) diff --git a/usr/local/bin/t2support.py b/usr/local/bin/t2support.py new file mode 100644 index 0000000..4dffe70 --- /dev/null +++ b/usr/local/bin/t2support.py @@ -0,0 +1,132 @@ +from tqdm import tqdm +from re import search +from requests import get +from hashlib import md5 +from os import walk +from os.path import join, islink +from shutil import chown +from sys import exit +from textwrap import wrap +from itertools import chain + +user = "t2server" +install_parent = "/opt" +install_dir = f"{install_parent}/t2server" +etc_dir = "/etc/t2server" +log_dir = "/var/log/t2server" +unit_dir = "/etc/systemd/system" +bin_dir = "/usr/local/bin" + +class color: + X = '\033[m' # Reset + DR = '\033[31m' # Dark Red + DG = '\033[32m' # Dark Green + DY = '\033[33m' # Dark Yellow (Brown) + DU = '\033[34m' # Dark Blue + DP = '\033[35m' # Dark Purple + DC = '\033[36m' # Dark Cyan + DW = '\033[37m' # Dark White (light grey) + BK = '\033[90m' # Bright Black (dark grey) + BR = '\033[91m' # Bright Red + BG = '\033[92m' # Bright Green + BY = '\033[93m' # Bright Yellow + BU = '\033[94m' # Bright Blue + BP = '\033[95m' # Bright Purple + BC = '\033[96m' # Bright Cyan + BW = '\033[97m' # Bright White + +class box: + TR = u'\u2510' + TL = u'\u250c' + BR = u'\u2518' + BL = u'\u2514' + H = u'\u2500' + V = u'\u2502' + +def menu(option_list,header="",footer=""): + """ + This function takes a list of options and make a BBS-style menu out of them. + Each item should have a unique alphanum in square brackets (eg. "[Q]uit" or "[1] Quit") + that represents the letter the user will type to select that option. Additionally, one + option may start with ~~ to indicate that it is the default option. This option will be + selected if the user presses Enter without typing a letter. ~~ will not be displayed on + screen. The function returns the selected letter in uppercase. + """ + default=None + keys=[] + line_list=list(option_list) + + if header: + header_list = wrap(header,width=76) + line_list.extend(header_list) + else: + header_list = None + + if footer: + footer_list = wrap(footer,width=76) + line_list.extend(footer_list) + else: + footer_list = None + + longest = max(list(chain((len(ele) for ele in line_list),[40]))) + + print(f"{color.BG}{box.TL}{box.H}{''.ljust(longest,box.H)}{box.H}{box.TR}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + if header_list: + for line in header_list: + print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + for option in option_list: + try: + key=search(r'\[([0-9a-zA-Z])\]', option).group(1) + except AttributeError: + pass + if option.startswith("~~"): + default = str(key) + keys.append(key.upper()) + option=option[2:] + else: + keys.append(str(key).lower()) + option=option.ljust(longest) + option=option.replace("[", color.BG + "[" + color.BW) + option=option.replace("]", color.BG + "]" + color.DG) + option=color.DG + option + color.X + print(f"{color.BG}{box.V} {option.ljust(longest)} {color.DG}{box.V}{color.X}") + if footer_list: + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + for line in footer_list: + print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}") + print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}") + print(f"{color.BG}{box.BL}{color.DG}{box.H}{''.ljust(longest,box.H)}{box.H}{box.BR}{color.X}") + menu_choice=" " + if default: + prompt=f"{color.DG}Choice {color.BK}({color.BG}{default}{color.BK}){color.DG}: {color.X}" + else: + prompt=f"{color.DG}Choice: {color.X}" + while menu_choice.upper() not in [x.upper() for x in keys]: + menu_choice = str(input(prompt)) + if default and menu_choice == "": menu_choice = default + return menu_choice.upper() + +def chowner(path, owner): + """ Recursively chown path with owner as both user and group """ + for dirpath, ignore, filenames in walk(path): + chown(dirpath, owner, owner) + for filename in filenames: + if not islink(join(dirpath, filename)): + chown(join(dirpath, filename), owner, owner) + +def pinfo(message): + print(color.BU + message + color.X) + +def pwarn(message): + print(color.BY + message + color.X) + +def perror(message): + print(color.BR + message + color.X) + +def bail(message=""): + exit(color.BR + message + color.X) + +if __name__ == "__main__": + bail("This file contains only shared functions and variables for the other scripts and is not meant to be run interactively.") \ No newline at end of file diff --git a/winbin/install_wrapper.au3 b/winbin/install_wrapper.au3 new file mode 100644 index 0000000..68cc715 --- /dev/null +++ b/winbin/install_wrapper.au3 @@ -0,0 +1,82 @@ +; Define a logging function +Func WriteLog($ErrorMessage) + FileWriteLine("L:\install_wrapper.log", @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC & " " & $ErrorMessage) +EndFunc + +WriteLog("--- Starting script ---") + +; Run the Tribes 2 installer +WriteLog("Launching tribes2_gsi.exe: " & Run("I:\tribes2_gsi.exe")) + +; SLA +WriteLog("Wait for SLA window: " & WinWait("Software License Agreement", "Please read through the entire license agreement", 60)) +WriteLog("Click [I Agree]: " & ControlClick("Software License Agreement", "Please read through the entire license agreement", 4)) ; click I Agree + +; Welcome window +WriteLog("Wait for Welcome window: " & WinWait("Tribes 2", "Welcome to the Tribes 2 Installation!", 60)) +WriteLog("Click [Skip]: " & ControlClick("Tribes 2", "Welcome to the Tribes 2 Installation!", 4)) ; click "Skip >" + +; Tribes Vengeance preorder +WriteLog("Wait for Tribes Vengeance Preorder window: " & WinWait("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 60)) +WriteLog("Click [Next]: " & ControlClick("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 5)) ; click "Next >" + +; Welcome 2 +WriteLog("Wait for Welcome: " & WinWait("Welcome", "Welcome to the Tribes 2 Setup program", 60)) +WriteLog("Click [Next]: " & ControlClick("Welcome", "Welcome to the Tribes 2 Setup program", 3)) ; click "Next >" + +; Credits +WriteLog("Wait for Credits window: " & WinWait("Credits", "It's not often that the community of a game", 60)) +WriteLog("Click [Next]: " & ControlClick("Credits", "It's not often that the community of a game", 3)) ; click "Next >" + +; Choose Destination Location +WriteLog("Wait for Choose Destination window: " & WinWait("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 60)) +WriteLog("Click [Browse]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 9)) ; click "Browse" +WriteLog("Wait for Browse window: " & WinWait("Select Destination Directory", "", 60)) +WriteLog("Type 'T:\t2server': " & ControlSend("Select Destination Directory", "", 3, "T:\t2server")) ; update the destination path +WriteLog("Click [OK]: " & ControlClick("Select Destination Directory", "", 7)) ; click OK +WriteLog("Click [Yes] to use existing directory: " & ControlClick("Install", "The directory T:\t2server already exists", 1)) ; T:\t2server already exists, install anyway +WriteLog("Click [Next]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 3)) ; click "Next >" + +; Select Program Manager Group +WriteLog("Wait for Start Menu Group window: " & WinWait("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 60)) +WriteLog("Click [Next]: " & ControlClick("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 3)) ; click "Next >" + +; Start Installation +WriteLog("Wait for Start Installation window: " & WinWait("Start Installation", "You are now ready to install Tribes 2.", 60)) +WriteLog("Click [Next]: " & ControlClick("Start Installation", "You are now ready to install Tribes 2.", 3)) ; click "Next >" + +; Register +WriteLog("Wait for Register window: " & WinWait("Register", "You can register Tribes 2 on the World Wide Web.", 60)) +WriteLog("Uncheck 'Register Tribes 2 Now': " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 9)) ; uncheck "Register Tribes 2 Now" +WriteLog("Click [Next]: " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 3)) ; click "Next >" + +; DirectX 8a +WriteLog("Wait for DX8 Install window: " & WinWait("DirectX 8a", "DirectX 8a Install", 60)) +WriteLog("Uncheck 'Install DirectX 8a': " & ControlClick("DirectX 8a", "DirectX 8a Install", 9)) ; uncheck "Install DirectX 8a" +WriteLog("Click [Next]: " & ControlClick("DirectX 8a", "DirectX 8a Install", 3)) ; click "Next >" + +; Installation Complete +WriteLog("Wait for Installation Complete window: " & WinWait("Installation Complete", "Tribes 2 has been successfully installed.", 60)) +WriteLog("Uncheck 'Open the Tribes 2 Readme': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 9)) ; uncheck "Open the Tribes 2 Readme" +WriteLog("Uncheck 'Launch Tribes 2': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 10)) ; uncheck "Launch Tribes 2" +WriteLog("Click [Next]: " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 3)) ; click "Next >" + +; Wait up to a minute for the installer to complete and exit +WriteLog("Wait for tribes2_gsi.exe to exit: " & ProcessWaitClose("tribes2_gsi.exe", 60)) + +; Run the TribesNext Patch +WriteLog("Launching TribesNExt_rc2a.exe: " & Run("I:\TribesNext_rc2a.exe")) + +; SLA +WriteLog("Wait for License Agreement window: " & WinWait("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 60)) +WriteLog("Click [I Agree]: " & ControlClick("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 1)) ; click "I Agree" + +; Installation Folder +WriteLog("Wait for Completed window: " & WinWait("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 60)) +WriteLog("Click [Apply Patch]: " & ControlClick("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 1)) ; click "Apply Patch" + +; Completed +WriteLog("Wait for SLA window: " & WinWait("TribesNext Patcher: Completed", "Completed", 60)) +WriteLog("Click [Close]: " & ControlClick("TribesNext Patcher: Completed", "Completed", 1)) ; click "Close" + +WriteLog("--- Script Complete ---") \ No newline at end of file From abd5c3ea54cd045cd3bdb29ff8eae2514ef6e9bb Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 3 Jan 2021 00:54:11 -0500 Subject: [PATCH 03/21] Create README.md --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3e3403 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# t2server +Run a Tribes 2 server on Linux + +## about +t2server automates the installation of Tribes 2 and the TribesNEXT patch to run under wine on Linux. +After installation, it provides systemd service units and Python scripts for the purpose of managing +your server. + +## Prerequisites +t2server has a handful of dependencies which are not automatically handled at this time. Before you +run the setup script, run the following commands as root depending on your distro: + +### Ubuntu 20.04 LTS +``` +# dpkg --add-architecture i386 +# apt update +# apt install unzip xvfb python3-minimal python3-pip wine32 cpulimit +# pip3 install tqdm requests +``` +(more to be tested and added) From 45588316b93a68061c2dd51bccf8592681c2cb71 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 3 Jan 2021 14:41:19 -0500 Subject: [PATCH 04/21] Update README.md --- README.md | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d3e3403..4a8f1e1 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,51 @@ # t2server -Run a Tribes 2 server on Linux +Scripts to install and run a Tribes 2 server on Linux with Wine. ## about t2server automates the installation of Tribes 2 and the TribesNEXT patch to run under wine on Linux. After installation, it provides systemd service units and Python scripts for the purpose of managing your server. -## Prerequisites +## prerequisites t2server has a handful of dependencies which are not automatically handled at this time. Before you -run the setup script, run the following commands as root depending on your distro: +run the setup script, run the following commands depending on your distro: -### Ubuntu 20.04 LTS +### Debian 10; Ubuntu 20.04 LTS ``` -# dpkg --add-architecture i386 -# apt update -# apt install unzip xvfb python3-minimal python3-pip wine32 cpulimit -# pip3 install tqdm requests +$ sudo dpkg --add-architecture i386 +$ sudo apt update +$ sudo apt install unzip xvfb python3-minimal python3-pip wine32 cpulimit +$ sudo pip3 install tqdm requests pyyaml ``` (more to be tested and added) + +## running and managing t2server +t2server can be controlled using systemctl. + +Start server: `$ sudo systemctl start t2server` + +Stop server: `$ sudo systemctl stop t2server` + +Restart server: `$ sudo systemctl restart t2server` + +The basic config file is /etc/t2server/config.yaml. Here you can specify which server prefs file to load, which mod to use, and whether the server is public or private (LAN). + +Additionally, you can schedule a day and time to auto-restart t2server each week as Tribes 2 servers are known to have stability issues as uptime increases. + +There is also a setting for overriding Tribes 2's man-in-the-middle attack detection, which tends to interfere with multihomed servers or those behind a NAT. + +Finally, you can specify a map rotation here. Some mods provide their own mechanisms for this and some do not. Be sure to only enable one or the other. + +serverprefs files can be kept in /etc/t2server/serverprefs. + +## known issues +If you attempt to install from /root or one of its subdirectories, the t2server user will not be able to access the winbin directory and the automated Tribes 2 and TribesNEXT installation will fail. Place the installer under /home, /tmp, /var/tmp, or some other path that is traversable by unprivileged users. + +RHEL/CentOS 7 and 8 no longer include wine32 in their repos, so installing on them is non-trivial. You would likely need to compile it yourself. + +t2server depends on systemd, so it definitely won't work on any distro that's not using it. + +When wine is run in the background, it spawns a `wineconsole --use-event=52` process that will consume all available CPU. I have been unable to find a proper solution to this, so for now t2server will run cpulimit against this process at startup in order to contain it. + +## legal compliance +Tribes 2 and TribesNEXT are freely available but are not Free Software so the installers have not been bundled in. Instead, the setup script will automatically download and run them during installation. As the automated installation of both prevents you from seeing their license agreements, the setup script will display them in plain text and require you to accept them before installation runs. From 6bcec3a3b0f7530eb734eddcfa0640593e2cf706 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 14:45:06 -0500 Subject: [PATCH 05/21] Fixed an issue with initial creation of missions.txt --- sla/tribes2.txt | 196 --------------------------------------------- sla/tribesnext.txt | 104 ------------------------ t2support.py | 1 - 3 files changed, 301 deletions(-) delete mode 100644 sla/tribes2.txt delete mode 100644 sla/tribesnext.txt delete mode 120000 t2support.py diff --git a/sla/tribes2.txt b/sla/tribes2.txt deleted file mode 100644 index a0430be..0000000 --- a/sla/tribes2.txt +++ /dev/null @@ -1,196 +0,0 @@ - _______ __ __ _______ - | |.----.|__|| |--..-----..-----. | | - |.| | || _|| || _ || -__||__ --| |___| | - `-|. |-'|__| |__||_____||_____||_____| / ___/ - |: | |: 1 \ ---------------- |::.| ------- License Agreement -------- |::.. . | ------------- - `---' `-------' - -YOU SHOULD CAREFULLY READ THE FOLLOWING END USER LICENSE AGREEMENT BEFORE -INSTALLING THIS SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE -PROGRAM, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT -AGREE TO THE TERMS OF THIS AGREEMENT, PROMPTLY RETURN THE UNUSED SOFTWARE -PROGRAM TO THE PLACE OF PURCHASE OR CONTACT SIERRA ON-LINE, INC. CUSTOMER -SERVICE AT (425) 746-5771 FOR A FULL REFUND OF THE PURCHASE PRICE WITHIN 30 DAYS -OF THE ORIGINAL PURCHASE. - -This software program (the "Program"), any printed materials, any on-line or -electronic documentation, and any and all copies and derivative works of such -software program (including materials created with a so called level editor, if -included) and materials are the copyrighted work of Sierra On-Line, Inc., a -division of Havas Interactive, Inc. and/or its wholly owned subsidiaries, or its -suppliers. All rights reserved, except as expressly stated herein. All use of -the Program is governed by the terms of this End User License Agreement provided -below ("License Agreement"). The Program is solely for use by end users -according to the terms of the License Agreement. Any use, reproduction or -redistribution of the Program not in accordance with the terms of the License -Agreement is expressly prohibited. - -END USER LICENSE AGREEMENT - -1. Limited Use License. Sierra On-Line, Inc. ("Sierra ") hereby grants, and by -installing the Program you thereby accept, a limited, non-exclusive license and -right to install and use one (1) copy of the Program for your use on either a -home, business or portable computer. In addition, the Program has a multi-player -capability that allows users to utilize the Program over the Internet via -Sierra's online game network Sierra.com. Use of the Program over Sierra.com is -subject to your acceptance of Sierra.com's Terms of Use Agreement. Sierra -On-Line, Inc. reserves the right to update, modify or change the Sierra.com -Terms of Use Agreement at any time. The Program may also contain a Level Editor -(the "Editor") that allows you to create custom levels or other materials for -your personal use in connection with the Program ("New Materials"). All use of -the Editor or any New Materials is subject to this License Agreement. The -Program is licensed, not sold. Your license confers no title or ownership in the -Program. - -2. Ownership. All title, ownership rights and intellectual property rights in -and to the Program and any and all copies thereof (including but not limited to -any titles, computer code, themes, objects, characters, character names, -stories, dialog, catch phrases, locations, concepts, artwork, animations, -sounds, musical compositions, audio-visual effects, methods of operation, moral -rights, any related documentation, and "applets" incorporated into the Program) -are owned by Sierra On-Line, Inc. or its licensors. The Program is protected by -the copyright laws of the United States, international copyright treaties and -conventions and other laws. All rights are reserved. The Program contains -certain licensed materials and Sierra 's licensors may protect their rights in -the event of any violation of this Agreement. - -3. Responsibilities of End User. - A. Subject to the Grant of License hereinabove, you may not, in whole or in - part, copy, photocopy, reproduce, translate, reverse engineer, derive source - code, modify, disassemble, decompile, create derivative works based on the - Program, or remove any proprietary notices or labels on the Program without - the prior consent, in writing, of Sierra. - B. The Program is licensed to you as a single product. Its component parts - may not be separated for use on more than one computer. - C. You are entitled to use the Program for your own use, but you are not - entitled to: - (i) sell, grant a security interest in or transfer reproductions of the - Program to other parties in any way, nor to rent, lease or license the - Program to others without the prior written consent of Sierra. - (ii) exploit the Program or any of its parts for any commercial purpose - including, but not limited to, use at a cyber café, computer gaming center - or any other location-based site. Sierra may offer a separate Site License - Agreement to permit you to make the Program available for commercial use; - contact Sierra for details; - (iii) host or provide matchmaking services for the Program or emulate or - redirect the communication protocols used by Sierra in the network feature - of the Program, through protocol emulation, tunneling, modifying or adding - components to the Program, use of a utility program or any other - techniques now known or hereafter developed, for any purpose including, - but not limited to network play over the Internet, network play utilizing - commercial or non-commercial gaming networks or as part of content - aggregation networks without the prior written consent of Sierra ; - (iv) create or maintain, under any circumstance, more than one - simultaneous connection to Sierra.com. All such connections to Sierra.com, - whether created by the Program or by other tools and utilities, may only - be made through methods and means expressly approved by Sierra On-Line, - Inc. Under no circumstances may you connect, or create tools that allow - you to connect to Sierra.com's private binary interface or interfaces - other than those explicitly provided by Sierra On-Line, Inc. for public - use. - -4. Program Transfer. You may permanently transfer all of your rights under this -License Agreement, provided the recipient agrees to the terms of this License -Agreement and you agree to remove the Program from your home or portable -computer. - -5. Termination. This License Agreement is effective until terminated. You may -terminate the License Agreement at any time by destroying the Program. Sierra -may, at its discretion, terminate this License Agreement in the event that you -fail to comply with the terms and conditions contained herein. In such event, -you must immediately destroy the Program. - -6. Export Controls. The Program may not be re-exported, downloaded or otherwise -exported into (or to a national or resident of) any country to which the U.S. -has embargoed goods, or to anyone on the U.S. Treasury Department's list of -Specially Designated Nationals or the U.S. Commerce Department's Table of Denial -Orders. By installing the Program, you are agreeing to the foregoing and you are -representing and warranting that you are not located in, under the control of, -or a national or resident of any such country or on any such list. - -7. Limited Warranty. Sierra expressly disclaims any warranty for the Program, -Editor and Manual(s). The Program, Editor and Manual(s) are provided "as is" -without warranty of any kind, either express or implied, including, without -limitation, the implied warranties of merchantability, fitness for a particular -purpose, or noninfringement. The entire risk arising out of use or performance -of the Program and Manual(s) remains with the User, however Sierra warrants up -to and including 90 days from the date of your purchase of the Program that the -media containing the Program shall be free from defects in material and -workmanship. In the event that the media proves to be defective during that time -period, and upon presentation to Sierra of proof of purchase of the defective -Program, Sierra will at its option 1) correct any defect, 2) provide you with a -product of equal or lesser value, or 3) refund your money. Some states do not -allow the exclusion or limitation of implied warranties or liability for -incidental damages, so the above limitations may not apply to you. - -8. Limitation of Liability. NEITHER SIERRA, HAVAS INTERACTIVE, INC., ITS PARENT, -SUBSIDIARIES OR AFFILIATES SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY -KIND RESULTING FROM THE USE OF THE PROGRAM OR USE OF SIERRA ON-LINE, INC.'S -ONLINE GAME NETWORK, SIERRA.COM INCLUDING, BUT NOT LIMITED TO, LOSS OF -GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER -COMMERCIAL DAMAGES OR LOSSES. SIERRA FURTHER DISCLAIMS ALL WARRANTIES WITH -REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. SPECIFICALLY, SIERRA MAKES NO -WARRANTIES THAT THE PERFORMANCE OR FUNCTIONALITY OF THE PROGRAM WILL NOT BE -AFFECTED BY DATES PRIOR TO, DURING OR AFTER THE YEAR 2000, OR THAT THE PROGRAM -WILL BE CAPABLE OF CORRECTLY PROCESSING, PROVIDING, AND/OR RECEIVING DATE -INFORMATION WITHIN AND BETWEEN CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE -INFORMATION BETWEEN PRODUCTS OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT -THAT MAY BE PROVIDED IN SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR -IN ANY OTHER COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, Sierra -On-Line, Inc. SHALL NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER -CHARACTERS, ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON -SIERRA.COM. I UNDERSTAND AND ACKNOWLEDGE THAT SIERRA ON-LINE, INC. CANNOT AND -WILL NOT BE RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON SIERRA.COM INCLUDING, -BUT NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER -EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some states -do not allow the exclusion or limitation of incidental or consequential damages, -or allow limitations on how long an implied warranty lasts, so the above -limitations may not apply. - -9. Equitable Remedies. You hereby agree that Sierra would be irreparably damaged -if the terms of this License Agreement were not specifically enforced, and -therefore you agree that Sierra shall be entitled, without bond, other security, -or proof of damages, to appropriate equitable remedies with respect to breaches -of this License Agreement, in addition to such other remedies as Sierra may -otherwise have available to it under applicable laws. In the event any -litigation is brought by either party in connection with this License Agreement, -the prevailing party in such litigation shall be entitled to recover from the -other party all the costs, attorneys' fees and other expenses incurred by such -prevailing party in the litigation. - -10. Limitations on License. Nothing in this License Agreement shall preclude -you from making or authorizing the making of another copy or adaptation of the -Program provided, however, that (1) such new copy or adaptation is created as an -essential step in your utilization of the Program in accordance with the terms -of this License Agreement and for NO OTHER PURPOSE; or (2) such new copy or -adaptation is for archival purposes ONLY and all archival copies are destroyed -in the event of your Transfer of the Program, the Termination of this Agreement -or other circumstances under which your continued use of the Program ceases to -be rightful. - -11. Miscellaneous. This License Agreement shall be deemed to have been made and -executed in the State of California and any dispute arising hereunder shall be -resolved in accordance with the law of California. You agree that any claim -asserted in any legal proceeding by one of the parties against the other shall -be commenced and maintained in any state or federal court located in the State -of California, County of Los Angeles, having subject matter jurisdiction with -respect to the dispute between the parties. This License Agreement may be -amended, altered or modified only by an instrument in writing, specifying such -amendment, alteration or modification, executed by both parties. In the event -that any provision of this License Agreement shall be held by a court or other -tribunal of competent jurisdiction to be unenforceable, such provision will be -enforced to the maximum extent permissible and the remaining portions of this -License Agreement shall remain in full force and effect. This License Agreement -constitutes and contains the entire agreement between the parties with respect -to the subject matter hereof and supersedes any prior oral or written -agreements. - -I hereby acknowledge that I have read and understand the foregoing License -Agreement and agree that the action of installing the Program is an -acknowledgment of my agreement to be bound by the terms and conditions of the -License Agreement contained herein. I also acknowledge and agree that this -License Agreement is the complete and exclusive statement of the agreement -between Sierra and I and that the License Agreement supersedes any prior or -contemporaneous agreement, either oral or written, and any other communications -between Sierra and myself. diff --git a/sla/tribesnext.txt b/sla/tribesnext.txt deleted file mode 100644 index 3c4fb47..0000000 --- a/sla/tribesnext.txt +++ /dev/null @@ -1,104 +0,0 @@ - _______ __ __ _______ _______ ___ ___ _______ - |_ _|.----.|__|| |--..-----..-----.| | || ___|| | ||_ _| - | | | _|| || _ || -__||__ --|| || ___||- -| | | - |___| |__| |__||_____||_____||_____||__|____||_______||___|___| |___| - ------------------------------ License Agreement -------------------------------- - -YOU SHOULD CAREFULLY READ THE FOLLOWING LICENSE AGREEMENT BEFORE INSTALLING THIS -SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE PROGRAM, YOU -AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE -TERMS OF THIS AGREEMENT, PROMPTLY DELETE THE UNUSED SOFTWARE PROGRAM. - -Definitions: - 1. "This software program" is defined as the integrated, machine readable, - executable code, and application tools included as part of the TribesNext - match making service. - 2. "Associated intepreted script code" is defined as the human readable, - executable code, and all intermediate representations included as part of or - generated during utilization of the TribesNext match making service. - -This software program (the “Program”), associated interpreted script code, any -on-line or electronic documentation, and any and all copies and derivative works -of such software program are the copyrighted work of the TribesNext team, All -rights reserved, except as expressly stated herein. All use of the Program is -governed by the terms of this License Agreement provided below. The Program is -solely for use by end users according to the terms of the License Agreement. Any -use, reproduction or redistribution of the Program not in accordance with the -terms of the License Agreement is expressly prohibited. - - 1. Ownership. All title, ownership rights and intellectual property rights in - and to the Program and any and all copies thereof (including but not limited - to any titles, computer code, objects, concepts, artwork, methods of - operation, moral rights, any related documentation, and “applets” - incorporated into the Program) are owned by the TribesNext team or its - licensors. The Program is protected by the copyright laws of the United - States, international copyright treaties and conventions and other laws. All - rights are reserved. The Program contains certain licensed materials and - TribesNext's licensors may protect their rights in the event of any violation - of this Agreement. - 2. Termination. This License Agreement is effective until terminated. You may - terminate the License Agreement at any time by destroying the Program. - TribesNext may, at its discretion, terminate this License Agreement in the - event that you fail to comply with the terms and conditions contained herein. - In such event, you must immediately destroy the Program. - 3. Export Controls. The Program contains strong cryptography, thus the - Program may not be re-exported, downloaded or otherwise exported into (or to - a national or resident of) any country to which the U.S. has embargoed goods, - or to anyone on the U.S. Treasury Department’s list of Specially Designated - Nationals or the U.S. Commerce Department’s Table of Denial Orders. By - installing the Program, you are agreeing to the foregoing and you are - representing and warranting that you are not located in, under the control - of, or a national or resident of any such country or on any such list. - 4. Limited Warranty. TribesNext expressly disclaims any warranty for the - Program, components and any assorted documentation. The Program, components - and any assorted documentation are provided "as is" without warranty of any - kind, either express or implied, including, without limitation, the implied - warranties of merchantability, fitness for a particular purpose, or - noninfringement. The entire risk arising out of use or performance of the - Program, components and any assorted documentation remains with the User. - Some states do not allow the exclusion or limitation of implied warranties or - liability for incidental damages, so the above limitations may not apply to - you. - 5. Limitation of Liability. NEITHER TRIBESNEXT, SUBSIDIARIES OR AFFILIATES - SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY KIND RESULTING FROM THE - USE OF THE PROGRAM OR USE OF ASSOCIATED ONLINE SERVICES INCLUDING, BUT NOT - LIMITED TO, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, - OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES. TRIBESNEXT FURTHER - DISCLAIMS ALL WARRANTIES WITH REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. - SPECIFICALLY, TRIBESNEXT MAKES NO WARRANTIES THAT THE PERFORMANCE OR - FUNCTIONALITY OF THE PROGRAM WILL NOT BE AFFECTED BY DATES PRIOR TO, DURING - OR AFTER THE YEAR 2000, OR THAT THE PROGRAM WILL BE CAPABLE OF CORRECTLY - PROCESSING, PROVIDING, AND/OR RECEIVING DATE INFORMATION WITHIN AND BETWEEN - CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE INFORMATION BETWEEN PRODUCTS - OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT THAT MAY BE PROVIDED IN - SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR IN ANY OTHER - COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, TRIBESNEXT SHALL - NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER CHARACTERS, - ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON TribesNext.com. I - UNDERSTAND AND ACKNOWLEDGE THAT TRIBESNEXT. CANNOT AND WILL NOT BE - RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON TRIBESNEXT.COM INCLUDING, BUT - NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER - EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some - states do not allow the exclusion or limitation of incidental or - consequential damages, or allow limitations on how long an implied warranty - lasts, so the above limitations may not apply. - 6. Equitable Remedies. You hereby agree that TribesNext would be irreparably - damaged if the terms of this License Agreement were not specifically - enforced, and therefore you agree that TribesNext shall be entitled, without - bond, other security, or proof of damages, to appropriate equitable remedies - with respect to breaches of this License Agreement, in addition to such other - remedies as TribesNext may otherwise have available to it under applicable - laws. In the event any litigation is brought by either party in connection - with this License Agreement, the prevailing party in such litigation shall be - entitled to recover from the other party all the costs, attorneys’ fees and - other expenses incurred by such prevailing party in the litigation. - -I hereby acknowledge that I have read and understand the foregoing License -Agreement and agree that the action of installing the Program is an -acknowledgment of my agreement to be bound by the terms and conditions of the -License Agreement contained herein. I also acknowledge and agree that this -License Agreement is the complete and exclusive statement of the agreement -between TribesNext and I and that the License Agreement supersedes any prior or -contemporaneous agreement, either oral or written, and any other communications -between TribesNext and myself diff --git a/t2support.py b/t2support.py deleted file mode 120000 index cce9e65..0000000 --- a/t2support.py +++ /dev/null @@ -1 +0,0 @@ -usr/local/bin/t2support.py \ No newline at end of file From 436f7f0ea980db3bc87fbdff16ddf524ecaa1f94 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 14:50:55 -0500 Subject: [PATCH 06/21] Fixed an issue with initial creation of missions.txt --- dependencies.txt | 13 +++++++++++++ etc/systemd/system/t2server.service | 2 -- usr/local/bin/t2server | 9 ++++----- 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 dependencies.txt diff --git a/dependencies.txt b/dependencies.txt new file mode 100644 index 0000000..9bf9aac --- /dev/null +++ b/dependencies.txt @@ -0,0 +1,13 @@ +Required OS packages for Ubuntu: +# dpkg --add-architecture i386 +# apt update +# apt install unzip xvfb python3-pip wine32 cpulimit +# pip3 install tqdm + +Required OS packages for RHEL 8: +# dnf install epel-repo +# dnf install wine-core unzip xorg-x11-server-Xvfb + +Required OS packages for RHEL 7: +# yum install epel-repo +# yum install wine-core unzip xorg-x11-server-Xvfb \ No newline at end of file diff --git a/etc/systemd/system/t2server.service b/etc/systemd/system/t2server.service index 70b87cb..355a51e 100644 --- a/etc/systemd/system/t2server.service +++ b/etc/systemd/system/t2server.service @@ -18,8 +18,6 @@ RestartSec=15s TimeoutStopSec=60s WorkingDirectory=/opt/t2server/GameData LogsDirectory=t2server - -# Below settings help lock down the service for security ProtectSystem=full ProtectHome=true SystemCallFilter=@system-service diff --git a/usr/local/bin/t2server b/usr/local/bin/t2server index bc0498e..843e7c6 100755 --- a/usr/local/bin/t2server +++ b/usr/local/bin/t2server @@ -73,10 +73,10 @@ def server_files(config): for mission in config["MapList"]: mlist.write(f"{config['MissionType']} {mission}\n") else: - if isfile(f"{install_dir}/GameData/base/prefs/missions.txt"): - print(f"Purging {install_dir}/GameData/base/prefs/missions.txt") - # missions.txt needs to exist or the missioncycle.cs script will hang, so overwrite with an empty file - open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w').close() + # missions.txt needs to exist or the missioncycle.cs script will hang, so create/overwrite with an empty file + print(f"Purging {install_dir}/GameData/base/prefs/missions.txt") + with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist: + mlist.write("") def runaway_control(): @@ -154,7 +154,6 @@ if __name__ == "__main__": # Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts. # This ensures there are no undefined values in the case of a user removing one from config.yaml. config = {**config_defaults, **loaded_config} - print(config) # Validate the mod directory and serverprefs file if not isdir(f"{install_dir}/GameData/{config['Mod']}"): From 1597bbe8236a372ecb66bc2bc52e9073f293961a Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 14:55:12 -0500 Subject: [PATCH 07/21] Removed binaries --- dependencies.txt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 dependencies.txt diff --git a/dependencies.txt b/dependencies.txt deleted file mode 100644 index 9bf9aac..0000000 --- a/dependencies.txt +++ /dev/null @@ -1,13 +0,0 @@ -Required OS packages for Ubuntu: -# dpkg --add-architecture i386 -# apt update -# apt install unzip xvfb python3-pip wine32 cpulimit -# pip3 install tqdm - -Required OS packages for RHEL 8: -# dnf install epel-repo -# dnf install wine-core unzip xorg-x11-server-Xvfb - -Required OS packages for RHEL 7: -# yum install epel-repo -# yum install wine-core unzip xorg-x11-server-Xvfb \ No newline at end of file From 7c76e5b9cdbe5255df198458a92e45998039dec5 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 15:23:25 -0500 Subject: [PATCH 08/21] .gitignore update --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..113ed70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +addons/ +winbin/install_wrapper.exe +winbin/msvcrt-ruby191.dll +t2support.py From bdfa95bfa5c55940aa17e72edd5093c03d2f9888 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 15:39:49 -0500 Subject: [PATCH 09/21] .gitignore update --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 113ed70..faf59b7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ addons/ winbin/install_wrapper.exe winbin/msvcrt-ruby191.dll t2support.py +sla/ From bf1ad09490bec25698aebd8b2b51dc4e3106c60a Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sun, 3 Jan 2021 18:31:46 -0500 Subject: [PATCH 10/21] Fixed issue executing manually provided tribes2gsi.exe --- README.md | 7 ++----- setup | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4a8f1e1..daa2cbe 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,10 @@ Scripts to install and run a Tribes 2 server on Linux with Wine. ## about -t2server automates the installation of Tribes 2 and the TribesNEXT patch to run under wine on Linux. -After installation, it provides systemd service units and Python scripts for the purpose of managing -your server. +t2server automates the installation of Tribes 2 and the TribesNEXT patch to run under wine on Linux. After installation, it provides systemd service units and Python scripts for the purpose of managing your server. ## prerequisites -t2server has a handful of dependencies which are not automatically handled at this time. Before you -run the setup script, run the following commands depending on your distro: +t2server has a handful of dependencies which are not automatically handled at this time. Before you run the setup script, run the following commands depending on your distro: ### Debian 10; Ubuntu 20.04 LTS ``` diff --git a/setup b/setup index 533426e..661b90c 100755 --- a/setup +++ b/setup @@ -116,7 +116,8 @@ if __name__ == "__main__": needed_files=[] if isfile(f"{pwd}/winbin/tribes2gsi.exe"): pinfo("tribes2gsi.exe found.") - installer_exe = f"{pwd}/winbin/tribes2gsi.exe" + rename(f"{pwd}/winbin/tribes2gsi.exe",f"{pwd}/winbin/tribes2_gsi.exe") + installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"): pinfo("tribes2_gsi.exe found.") installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" From 3127130b19dd638c554f031884209739212785d5 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Tue, 5 Jan 2021 22:25:25 -0500 Subject: [PATCH 11/21] cpu usage improvements and misc --- etc/t2server/serverprefs/Classic_CTF.cs | 52 ------------------------- setup | 2 + usr/local/bin/t2server | 22 ++++++----- usr/local/bin/t2support.py | 2 - 4 files changed, 14 insertions(+), 64 deletions(-) delete mode 100644 etc/t2server/serverprefs/Classic_CTF.cs diff --git a/etc/t2server/serverprefs/Classic_CTF.cs b/etc/t2server/serverprefs/Classic_CTF.cs deleted file mode 100644 index 9cc7c46..0000000 --- a/etc/t2server/serverprefs/Classic_CTF.cs +++ /dev/null @@ -1,52 +0,0 @@ -$Host::AdminPassword = "changethis"; -$Host::allowAdminPlayerVotes = "0"; -$Host::AllowMapScript = 1; -$Host::BanTime = 1800; -$Host::BotCount = 7; -$Host::BotsEnabled = "0"; -$Host::ClassicSuperAdminPassword = "changethis"; -$Host::CRCTextures = 0; -$Host::FloodProtectionEnabled = 1; -$Host::GameName = "Tribes 2 Classic CTF Server"; -$Host::HiVisibility = "1"; -$Host::holoName1 = "Storm"; -$Host::holoName2 = "Inferno"; -$Host::holoName3 = "Starwolf"; -$Host::holoName4 = "DSword"; -$Host::holoName5 = "BloodEagle"; -$Host::holoName6 = "Harbinger"; -$Host::Info = "This is a Tribes 2 Classic Server."; -$Host::KickBanTime = 300; -$Host::Map = "Minotaur"; -$Host::MarkDnDObjectives = 1; -$Host::MaxMessageLen = 120; -$Host::MaxPlayers = "64"; -$Host::MissionType = "CTF"; -$Host::NoSmurfs = 0; -$Host::PlayerRespawnTimeout = "60"; -$Host::Port = "28000"; -$Host::PureServer = 0; -$Host::TeamDamageOn = "1"; -$Host::TeamName0 = "Unassigned"; -$Host::TeamName1 = "Storm"; -$Host::TeamName2 = "Inferno"; -$Host::TeamName3 = "Starwolf"; -$Host::TeamName4 = "Diamond Sword"; -$Host::TeamName5 = "Blood Eagle"; -$Host::TeamName6 = "Phoenix"; -$Host::TeamSkin0 = "blank"; -$Host::TeamSkin1 = "base"; -$Host::TeamSkin2 = "baseb"; -$Host::TeamSkin3 = "swolf"; -$Host::TeamSkin4 = "dsword"; -$Host::TeamSkin5 = "beagle"; -$Host::TeamSkin6 = "cotp"; -$Host::TimeLimit = "200"; -$Host::TN::beat = 3; -$Host::TN::echo = 1; -$Host::TournamentMode = "0"; -$Host::UseHighPerformanceCounter = 0; -$Host::VotePassPercent = "60"; -$Host::VoteSpread = 20; -$Host::VoteTime = "30"; -$Host::warmupTime = "20"; diff --git a/setup b/setup index 661b90c..72d17a4 100755 --- a/setup +++ b/setup @@ -1,4 +1,6 @@ #!/usr/bin/env -S python3 -B +from tqdm import tqdm +from hashlib import md5 from os import system, unlink, symlink, makedirs, geteuid, getcwd, chmod, rename from os.path import isfile, isdir from time import time diff --git a/usr/local/bin/t2server b/usr/local/bin/t2server index 843e7c6..2cd3450 100755 --- a/usr/local/bin/t2server +++ b/usr/local/bin/t2server @@ -79,20 +79,20 @@ def server_files(config): mlist.write("") -def runaway_control(): +def runaway_control(runaway_proc_cmd): """ - When run in the background, wine will spawn a 'wineconsole --use-event=52' - process that will consume all available CPU. This function finds that - process and uses cpulimit to keep it under control. + When run in the background, wine will spawn two 'wineconsole' + processes that will consume all available CPU. This function finds these + processes and uses cpulimit to keep them under control. """ for x in range(20): sleep(15) - print("Checking for runaway wineconsole process...") - runaway_pid=run(["/usr/bin/pgrep","-f","wineconsole --use-event=52"],stdout=PIPE).stdout + print(f"Checking for runaway '{runaway_proc_cmd}' process...") + runaway_pid=run(["/usr/bin/pgrep","-f", runaway_proc_cmd],stdout=PIPE).stdout if runaway_pid: runaway_pid=str(int(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 def master_heartbeat(): @@ -178,9 +178,11 @@ if __name__ == "__main__": heartbeat.daemon=True heartbeat.start() - # Cap the CPU of the runaway wineconsole process - wcpid_limit=Thread(target=runaway_control) - wcpid_limit.start() + # Cap the CPU of the runaway wineconsole processes + wcpid1_limit=Thread(target=runaway_control, args=("wineconsole --use-event=52",)) + wcpid1_limit.start() + wcpid2_limit=Thread(target=runaway_control, args=("wineconsole --use-event=188",)) + wcpid2_limit.start() # Open the console log file if running as service and start the Tribes 2 server print(f"Starting Tribes 2 server: " + " ".join(server_command)) diff --git a/usr/local/bin/t2support.py b/usr/local/bin/t2support.py index 4dffe70..6afd43e 100644 --- a/usr/local/bin/t2support.py +++ b/usr/local/bin/t2support.py @@ -1,7 +1,5 @@ -from tqdm import tqdm from re import search from requests import get -from hashlib import md5 from os import walk from os.path import join, islink from shutil import chown From 3e0a85cf4180e0f82c6bf10aa2018e5b5a7eaf84 Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Tue, 5 Jan 2021 22:47:56 -0500 Subject: [PATCH 12/21] Updated README --- README.md | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index daa2cbe..6a2ed6a 100644 --- a/README.md +++ b/README.md @@ -16,24 +16,29 @@ $ sudo pip3 install tqdm requests pyyaml ``` (more to be tested and added) +## installation +Run the 'setup' script and follow the instructions on screen. + +The Tribes 2 installer (tribes2\_gsi.exe) and the TribesNEXT installer (TribesNext\_rc2a.exe) will be downloaded automatically. If you already have a copy of these, you can place them in the winbin directory and setup will use them instead of downloading. + +Before automatic installation, you'll be asked to review and agree to both the Tribes 2 and TribesNEXT License Agreements. + +A 't2server' user will be created with a home directory of /opt/t2server, which is also where Tribes 2 will be installed. + +A compiled AutoIt script handles automatic installation of Tribes 2 and TribesNEXT via Wine. This script logs to /var/log/t2server/install\_wrapper.log. + +After install, any .vl2 files in the addons directory will be copied to the GameData/base directory. Any .tar, .tgz, .tar.gz, .txz, .tar.xz, .tbz, or .tar.bz files will be extracted into GameData. This can be used to automate the installation of any mods, map packs, texture packs, etc, just make sure the archive file contains the directory that should be created under GameData (eg. if you're including the Version2 mod, your archive file should have only a 'Version2' directory at the top level with all the contents within it). The setup script will not do any safety checks on these archive files, so an improperly set up archive file will make a mess in GameData. + ## running and managing t2server -t2server can be controlled using systemctl. +The basic config file is /etc/t2server/config.yaml. Here you can specify which server prefs file to load, which mod to use, and whether the server is public or private (LAN). Additionally, you can schedule a day and time to auto-restart t2server each week as Tribes 2 servers are known to have stability issues as uptime increases. There is also a setting for overriding Tribes 2's man-in-the-middle attack detection, which tends to interfere with multihomed servers or those behind a NAT. Finally, you can specify a map rotation here. Some mods provide their own mechanisms for this and some do not. Be sure to only enable one or the other. -Start server: `$ sudo systemctl start t2server` +ServerPrefs files can be kept in /etc/t2server/serverprefs: +- t2server can be controlled using systemctl: +- Start server: `$ sudo systemctl start t2server` +- Stop server: `$ sudo systemctl stop t2server` +- Restart server: `$ sudo systemctl restart t2server` -Stop server: `$ sudo systemctl stop t2server` - -Restart server: `$ sudo systemctl restart t2server` - -The basic config file is /etc/t2server/config.yaml. Here you can specify which server prefs file to load, which mod to use, and whether the server is public or private (LAN). - -Additionally, you can schedule a day and time to auto-restart t2server each week as Tribes 2 servers are known to have stability issues as uptime increases. - -There is also a setting for overriding Tribes 2's man-in-the-middle attack detection, which tends to interfere with multihomed servers or those behind a NAT. - -Finally, you can specify a map rotation here. Some mods provide their own mechanisms for this and some do not. Be sure to only enable one or the other. - -serverprefs files can be kept in /etc/t2server/serverprefs. +The Tribes 2 console logs to /var/log/t2server/console.log. ## known issues If you attempt to install from /root or one of its subdirectories, the t2server user will not be able to access the winbin directory and the automated Tribes 2 and TribesNEXT installation will fail. Place the installer under /home, /tmp, /var/tmp, or some other path that is traversable by unprivileged users. @@ -42,7 +47,6 @@ RHEL/CentOS 7 and 8 no longer include wine32 in their repos, so installing on th t2server depends on systemd, so it definitely won't work on any distro that's not using it. -When wine is run in the background, it spawns a `wineconsole --use-event=52` process that will consume all available CPU. I have been unable to find a proper solution to this, so for now t2server will run cpulimit against this process at startup in order to contain it. +When wine is run in the background, it spawns 2 `wineconsole --use-event=*n*` processes that will consume all available CPU. I have been unable to find a proper solution to this, so for now t2server will run cpulimit against these processes at startup in order to contain them. I have not seen this result in any performance issues. -## legal compliance -Tribes 2 and TribesNEXT are freely available but are not Free Software so the installers have not been bundled in. Instead, the setup script will automatically download and run them during installation. As the automated installation of both prevents you from seeing their license agreements, the setup script will display them in plain text and require you to accept them before installation runs. +console.log includes ANSI escape sequences to set colors and terminal size, so your shell will get a little funky after tailing this log. Just run `reset` to reinitialize your session and you'll be back to normal. From c410fcb6457c2e93addb00c6c23a6fa1e6d30ef6 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Wed, 7 Feb 2024 20:51:48 -0500 Subject: [PATCH 13/21] reformatted comments --- etc/t2server/config.yaml | 45 +++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/etc/t2server/config.yaml b/etc/t2server/config.yaml index c6d38c6..95fed6f 100644 --- a/etc/t2server/config.yaml +++ b/etc/t2server/config.yaml @@ -1,21 +1,20 @@ --- -## ServerPrefs indicates the server config file in /etc/t2server/serverprefs -## to use. This is case-sensitive and must match the filename exactly. +## ServerPrefs indicates the server config file in /etc/t2server/serverprefs to +## use. This is case-sensitive and must match the filename exactly. ServerPrefs: Classic_CTF.cs ## Tribes 2 servers tend to get unstable after a couple weeks of being online. -## Here you can specify a day and hour to automatically bounce the server. -## Set RestartTime to the hour of the day, 0-23, to cycle the server -## (eg. 4=4:00am, 16=4:00pm) or False to disable. Set RestartDay to the -## three-letter abbreviation of the day on which the server should be restarted -## (Sun, Mon, Tue, Wed, Thu, Fri, or Sat). This will be ignored if -## RestartTime = False. +## Here you can specify a day and hour to automatically bounce the server. Set +## RestartTime to the hour of the day, 0-23, to cycle the server (eg. 4=4:00am, +## 16=4:00pm) or False to disable. Set RestartDay to the three-letter +## abbreviation of the day on which the server should be restarted (Sun, Mon, +## Tue, Wed, Thu, Fri, or Sat). This will be ignored if RestartTime = False. RestartTime: False RestartDay: Mon -## Set Mod to the directory name of the mod to be loaded, or 'base' for -## vanilla (but why?). This is case-sensitive and must match the subdirectory -## name exactly. +## Set Mod to the directory name of the mod to be loaded, or 'base' for vanilla +## (but why?). This is case-sensitive and must match the subdirectory name +## exactly. Mod: Classic ## Set Public to False to host a LAN-only game, or to True to host a public @@ -29,14 +28,26 @@ Public: False ## detection. This setting has no effect if Public = False. OverrideMITM: True +## The built-in master server heartbeat sometimes works inconsistently. If you +## have this problem, you can enable t2server's heartbeat. This has no effect +## if Public = False. +Heartbeat: False + +## At startup, Tribes 2 compiles cs scripts into a dso binary format. If those +## scripts are later updated, the changes won't take effect because the game +## will continue to use the compiled dso. If DSOCleanup is enabled, t2server +## will delete all dso files that are older than their associated cs file at +## startup. Note that dso files associated with a cs file that only exists +## within a vl2 package won't be touched and may occasionally need manual +## cleanup. +DSOCleanup: True + ## Configure a custom map rotation list. The standard Mission Types are ## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM" -## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege", -## "TeamHunters", and "TeamRabbit". Your server will always launch with the -## MissionType and Map specified in your serverprefs file ($Host::MissionType -## and $Host::Map), so make sure MissionType matches $Host::MissionType and -## the first map in MapList matches $Host::Map. If you're running a mod that -## handles map rotation, set these to 'False'. +## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege", map +## "TeamHunters", and "TeamRabbit". If you're running a mod that handles +## rotation, set these to 'False'. +## ## Example: ## MissionType: CTF ## MapList: ["Katabatic", "Minotaur", "Tombstone"] From 95374b3d2311f6e67dc8838b033ceebbc636fe69 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:36:49 -0500 Subject: [PATCH 14/21] Increase timeouts --- winbin/install_wrapper.au3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/winbin/install_wrapper.au3 b/winbin/install_wrapper.au3 index 68cc715..25b5361 100644 --- a/winbin/install_wrapper.au3 +++ b/winbin/install_wrapper.au3 @@ -21,7 +21,7 @@ WriteLog("Wait for Tribes Vengeance Preorder window: " & WinWait("Tribes: Vengea WriteLog("Click [Next]: " & ControlClick("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 5)) ; click "Next >" ; Welcome 2 -WriteLog("Wait for Welcome: " & WinWait("Welcome", "Welcome to the Tribes 2 Setup program", 60)) +WriteLog("Wait for Welcome: " & WinWait("Welcome", "Welcome to the Tribes 2 Setup program", 180)) WriteLog("Click [Next]: " & ControlClick("Welcome", "Welcome to the Tribes 2 Setup program", 3)) ; click "Next >" ; Credits @@ -46,7 +46,7 @@ WriteLog("Wait for Start Installation window: " & WinWait("Start Installation", WriteLog("Click [Next]: " & ControlClick("Start Installation", "You are now ready to install Tribes 2.", 3)) ; click "Next >" ; Register -WriteLog("Wait for Register window: " & WinWait("Register", "You can register Tribes 2 on the World Wide Web.", 60)) +WriteLog("Wait for Register window: " & WinWait("Register", "You can register Tribes 2 on the World Wide Web.", 180)) WriteLog("Uncheck 'Register Tribes 2 Now': " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 9)) ; uncheck "Register Tribes 2 Now" WriteLog("Click [Next]: " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 3)) ; click "Next >" From d9017910ec53479dad4a85f26abe3121056a237f Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:37:41 -0500 Subject: [PATCH 15/21] include release file with version number --- etc/t2server/release | 1 + 1 file changed, 1 insertion(+) create mode 100644 etc/t2server/release diff --git a/etc/t2server/release b/etc/t2server/release new file mode 100644 index 0000000..8adc70f --- /dev/null +++ b/etc/t2server/release @@ -0,0 +1 @@ +0.8.0 \ No newline at end of file From a67dc7bb48b3e4cd4211d18e3ce96cfb2555a772 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:38:17 -0500 Subject: [PATCH 16/21] add release file --- usr/local/bin/t2fixer | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/usr/local/bin/t2fixer b/usr/local/bin/t2fixer index 85fcfdc..d9d94c0 100755 --- a/usr/local/bin/t2fixer +++ b/usr/local/bin/t2fixer @@ -47,6 +47,15 @@ def setperm(file): pwarn(f"Failed to set permissions on {file}") log.write(f"... FAILED!\n") pass + elif file == f"{etc_dir}/release": + log.write(f"Setting mode 444 on {file}") + try: + chmod(file,0o444) + log.write(f"\n") + except: + pwarn(f"Failed to set permissions on {file}") + log.write(f"... FAILED!\n") + pass else: log.write(f"Setting mode 664 on file {file}") try: @@ -100,4 +109,5 @@ with open(f'{log_dir}/t2fixer.log', 'w') as log: setperm(f"{bin_dir}/t2remove") setperm(f"{bin_dir}/t2fixer") setperm(f"{bin_dir}/t2help") - setperm(f"{bin_dir}/t2support.py") \ No newline at end of file + setperm(f"{bin_dir}/t2support.py") + setperm(f"{etc_dir}/release") \ No newline at end of file From 0ca42d26f91581638550bc077c6232f274027db0 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:51:32 -0500 Subject: [PATCH 17/21] improvments to winecmd, dso_cleanup, formatting, and comments --- usr/local/bin/t2server | 132 ++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/usr/local/bin/t2server b/usr/local/bin/t2server index 2cd3450..ecc72a4 100755 --- a/usr/local/bin/t2server +++ b/usr/local/bin/t2server @@ -10,32 +10,26 @@ from requests import get from threading import Thread from t2support import * -winecmd=["/usr/bin/wineconsole", "--backend=curses"] -argbase=["Tribes2.exe","-dedicated"] -basecmd=winecmd+argbase -parent=getppid() +winecmd = ["/usr/bin/wine"] +argbase = ["Tribes2.exe","-dedicated"] +basecmd = winecmd + argbase +parent = getppid() def dso_cleanup(): """ - 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 - recompile them at startup. + This function finds all .dso files (compiled .cs scripts) and deletes them + so that Tribes 2 will recompile them at startup. """ - for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True): - cs_file=dso_file[:-4] - if isfile(cs_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) + for dso_file in iglob(f"{install_dir}/**/*.dso", recursive = True): + print(f"Deleting {dso_file} so it can be rebuilt.") + unlink(dso_file) def build_args(basecmd,config): """ This function assembles the command line to launch the server based on the config.yaml file. """ - server_command=basecmd + server_command = basecmd if config['Public']: server_command.append("-online") else: @@ -63,12 +57,12 @@ def server_files(config): print(f"Deleting {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']}") - 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") - + if config["MapList"] and config["MissionType"]: 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: for mission in config["MapList"]: 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: mlist.write("") - def runaway_control(runaway_proc_cmd): """ 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): sleep(15) 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: - runaway_pid=str(int(runaway_pid)) + runaway_pid = str(int(runaway_pid)) print(f"Limiting runaway wineconsole process: {runaway_pid}") run(["/usr/bin/cpulimit","-bp", runaway_pid,"-l2"]) break @@ -99,7 +92,7 @@ def master_heartbeat(): """ 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 - inconsistent, so this function takes over that responsibility. + inconsistent, so this function takes over that responsibility if enabled. """ print("Starting TribesNext heartbeat thread...") while True: @@ -110,43 +103,24 @@ def is_valid_ip(ip): """Check if an ip looks like a valid IP address.""" 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__": + # 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 parent == 1: print(f"Started by init") 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() 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 with open(f'{etc_dir}/config.yaml', 'r') as f: loaded_config = yaml.full_load(f) @@ -161,33 +135,55 @@ if __name__ == "__main__": if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"): bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}") - # Delete any pre-existing noMITM script/dso. 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/noMITM.cs.dso"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso") + # Delete any pre-existing 00_t2server_opts script. It will be recreated below, if needed. + 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") - # 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) - dso_cleanup() - server_command=build_args(basecmd,config) + if config['DSOCleanup']: dso_cleanup() + 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']: - print("Starting heartbeat...") - if config['OverrideMITM']: override_mitm() - heartbeat=Thread(target=master_heartbeat) - heartbeat.daemon=True - heartbeat.start() + # Start a heartbeat thread, if configured. + if config['Heartbeat']: + heartbeat = Thread(target = master_heartbeat) + heartbeat.daemon = True + 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 - 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() - 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() # Open the console log file if running as service and start the Tribes 2 server print(f"Starting Tribes 2 server: " + " ".join(server_command)) if parent == 1: with open(f"{log_dir}/console.log", 'w') as consolelog: - run(server_command,stdout=consolelog) + run(server_command,stdout = consolelog) else: - run(server_command) + run(server_command) From aca1b4056b9c6b94c801922be18881a5b70464a7 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:53:10 -0500 Subject: [PATCH 18/21] get version number from /etc/t2server/version and include in output --- usr/local/bin/t2help | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/usr/local/bin/t2help b/usr/local/bin/t2help index b5dcee2..b1f4fa0 100755 --- a/usr/local/bin/t2help +++ b/usr/local/bin/t2help @@ -1,10 +1,20 @@ #!/usr/bin/env -S python3 -B -from t2support import color +from t2support import color, release_file +from os.path import isfile + +# Get the version from the release file +if isfile(release_file): + with open(release_file, 'r') as rf: + version = rf.read().rstrip() +else: + version = None + +if version: print(f"{color.DC}t2server {version}") print(f"\n{color.DC}The follow commands can be used to manage your Tribes 2 server:") print(f"""\n{color.BW}systemctl t2server{color.DC}: The t2server service can be managed with the -standard Linux systemctl command.""") +standard Linux systemctl command. See 'man systemctl' for more info.""") print(f"""\n{color.BW}t2fixer{color.DC}: This command resets the owner and permissions on all files associated with t2server. It's recommended to run this command any time you make changes From 88cdae6de082e7e3b777b1b078a994693d46ee15 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:54:55 -0500 Subject: [PATCH 19/21] include configuration defaults in case they are not set in /etc/t2config/config.yaml --- usr/local/bin/t2support.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/usr/local/bin/t2support.py b/usr/local/bin/t2support.py index 6afd43e..f133e66 100644 --- a/usr/local/bin/t2support.py +++ b/usr/local/bin/t2support.py @@ -14,6 +14,18 @@ etc_dir = "/etc/t2server" log_dir = "/var/log/t2server" unit_dir = "/etc/systemd/system" bin_dir = "/usr/local/bin" +release_file = f"{etc_dir}/release" + +config_defaults = { + 'ServerPrefs' : 'Classic_CTF.cs', + 'Mod' : 'Classic', + 'Public' : False, + 'OverrideMITM': False, + 'Heartbeat' : False, + 'DSOCleanup' : True, + 'MissionType' : 'CTF', + 'MapList' : False +} class color: X = '\033[m' # Reset @@ -78,7 +90,7 @@ def menu(option_list,header="",footer=""): try: key=search(r'\[([0-9a-zA-Z])\]', option).group(1) except AttributeError: - pass + bail("Error while processing menu option list.") if option.startswith("~~"): default = str(key) keys.append(key.upper()) From e2ab01674ff6d8a0bce067dcf0c125a06ba5881d Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 18:58:06 -0500 Subject: [PATCH 20/21] add support to upgrade pre-existing installs --- setup | 431 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 243 insertions(+), 188 deletions(-) diff --git a/setup b/setup index 72d17a4..17dbb6a 100755 --- a/setup +++ b/setup @@ -62,10 +62,59 @@ def download_file(url, filename): pbar.close() return filename -if __name__ == "__main__": +def version_compare(installed_version, package_version): + if installed_version == package_version: return 0 + installed = installed_version.split(".") + packaged = package_version.split(".") + if installed[0] < packaged[0]: + return 1 + elif installed[0] > packaged[0]: + return -1 + if installed[1] < packaged[1]: + return 1 + elif installed[1] > packaged[1]: + return -1 + if installed[2] < packaged[2]: + return 1 + elif installed[2] > packaged[2]: + return -1 - action=menu(["~~[C]ontinue","[Q]uit"],header="This script will install Tribes 2 for use as a dedicated server.") - if action == "Q": bail() +if __name__ == "__main__": + # Check for an existing install. Read version from the release file if it exists, otherwise assume 0.7.3 + if isfile(release_file): + with open(release_file, 'r') as rf: + installed_version = rf.read().rstrip() + elif isfile(f"{install_dir}/GameData/Tribes2.exe"): + installed_version = "0.7.3" + else: + installed_version = None + + # Get the version in this install package + if isfile(f"{pwd}/etc/t2server/release"): + with open(f"{pwd}/etc/t2server/release", 'r') as rf: + package_version = rf.read().rstrip() + else: + package_version = None + + # Compare versions to see if the install package is newer than the installed version and quit if not + if installed_version and package_version: upgrade = version_compare(installed_version, package_version) + else: upgrade = None + + # Exit if trying to update to an older version or the same version, otherwise display the initial menu and determine the setup mode + if upgrade == -1: + bail(f"This would install t2server {package_version} which is older than what's already installed, version {installed_version}.") + elif upgrade == 0: + bail(f"The existing t2server install is the same version as this install package, {package_version}.") + elif upgrade == 1: + setup_mode=menu([f"[U]pgrade to {package_version}","[Q]uit"],header=f"An existing t2server {installed_version} install was detected. 'Upgrade' will only update the scripts and files that come with t2server and won't reinstall Tribes 2.") + if setup_mode == "Q": bail() + else: + action=menu(["~~[C]ontinue","[Q]uit"],header="This script will install Tribes 2 for use as a dedicated server.") + if action == "Q": bail() + setup_mode="I" + + if setup_mode == "R": + system(f"{pwd}/usr/local/bin/t2remove -Y") # Check if user exists try: @@ -73,226 +122,232 @@ if __name__ == "__main__": except KeyError: user_info = False - # Create or repurpose user - if user_info: - if user_info.pw_dir == install_dir: - pwarn(f"User '{user}' exists and will be reused.") + if setup_mode == "I" or setup_mode == "R": + # Create or repurpose user if installing or reinstalling + if user_info: + if user_info.pw_dir == install_dir: + pwarn(f"User '{user}' exists and will be reused.") + else: + bail(f"ERROR: User '{user}' already exists and may belong to another person or process.") else: - bail(f"ERROR: User '{user}' already exists and may belong to another person or process.") - else: - pinfo(f"Creating {user} user and {install_dir}.") - system(f"useradd -md {install_dir} {user}") + print(f"Creating {user} user and {install_dir}.") + system(f"useradd -md {install_dir} {user}") - if not user_info: user_info = getuser(user) + if not user_info: user_info = getuser(user) - # Create log_dir - pinfo(f"Creating {log_dir}.") - makedirs(log_dir, mode=0o777, exist_ok=True) - chmod(log_dir, 0o777) + # Create log_dir + print(f"Creating {log_dir}.") + makedirs(log_dir, mode=0o777, exist_ok=True) + chmod(log_dir, 0o777) - # Create .wine dir - pinfo(f"Creating {install_dir}/.wine defaults.") - system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'") + # Create .wine dir + print(f"Creating {install_dir}/.wine defaults.") + system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'") - # Map wine I: drive to pwd T: drive to install_dir and L: to log_dir - pinfo(f"Mapping I: in wine for {user}.") - try: - symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:") - except FileExistsError: - pass - - pinfo(f"Mapping L: in wine for {user}.") - try: - symlink(log_dir, f"{install_dir}/.wine/dosdevices/l:") - except FileExistsError: - pass - - pinfo(f"Mapping T: in wine for {user}.") - try: - symlink(install_parent, f"{install_dir}/.wine/dosdevices/t:") - except FileExistsError: - pass - - - # Check for needed exe/zip/dll files in winbin dir - needed_files=[] - if isfile(f"{pwd}/winbin/tribes2gsi.exe"): - pinfo("tribes2gsi.exe found.") - rename(f"{pwd}/winbin/tribes2gsi.exe",f"{pwd}/winbin/tribes2_gsi.exe") - installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" - elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"): - pinfo("tribes2_gsi.exe found.") - installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" - else: - pwarn("Tribes 2 installer not found.") - needed_files.append("tribes2_gsi.exe") - installer_exe = False - - if isfile(f"{pwd}/winbin/TribesNext_rc2a.exe"): - pinfo("TribesNext_rc2a.exe found.") - tnpatch_exe = f"{pwd}/winbin/TribesNext_rc2a.exe" - else: - pwarn("Tribes Next patch not found.") - needed_files.append("TribesNext_rc2a.exe") - tnpatch_exe = False - - ruby_dll = f"{pwd}/winbin/msvcrt-ruby191.dll" - instwrap_exe = f"{pwd}/winbin/install_wrapper.exe" - - # Download files if needed - if not installer_exe or not tnpatch_exe: - action=menu(["~~[D]ownload automatically","[Q]uit"],header="One or more needed files were not found. Download automatically or quit so they can be manually placed in the 'winbin' subdirectory?") - if needed_files == 2: - needed_files=f"{needed_files[0]} and {needed_files[1]}" - if action=="Q": bail(f"Manually place {needed_files} in the 'winbin' subdirectory then rerun setup.") - - if not installer_exe: - for url in installer_mirror_list: - try: - pinfo(f"\nDownloading from {url.split('/')[2]}...") - installer_exe = download_file(url, f"{pwd}/winbin/tribes2_gsi.exe") - if md5sum(installer_exe) == installer_checksum: - pinfo("Checksum validation passed.") - break - else: - perror("Checksum validation failed. Trying next mirror.") - except KeyError: - perror("Download error. Trying next mirror.") - continue - - if not installer_exe: - bail("ERROR: Tribes 2 installer could not be downloaded.") - - if not tnpatch_exe: - for url in tnpatch_mirror_list: - try: - pinfo(f"\nDownloading from {url.split('/')[2]}...") - tnpatch_exe = download_file(url, f"{pwd}/winbin/TribesNext_rc2a.exe") - if md5sum(tnpatch_exe) == tnpatch_checksum: - pinfo("Checksum validation passed.") - break - else: - perror("Checksum validation failed. Trying next mirror." ) - except KeyError: - perror("Download error. Trying next mirror.") - continue - - if not tnpatch_exe: - bail("ERROR: Tribes Next patch could not be downloaded.") - - # Present SLAs before beginning install - sla = None - while not sla: - sla=menu(["[V]iew Tribes 2 and TribesNext License Agreements", "[A]ccept License Agreements", "[Q]uit"], header="Please take a moment to review and accept the Tribes 2 and TribeNext License Agreements before beginning automated install.") - if sla == "V": - print(color.DY) - system(f"/usr/bin/less {pwd}/sla/tribes2.txt") - print(color.DP) - system(f"/usr/bin/less {pwd}/sla/tribesnext.txt") - sla = None - elif sla == "A": - break - elif sla == "Q": - bail("You must accept the License Agreements to install.") - - # Ensure sufficient permissions on winbin and its contents - chmod(f"{pwd}/winbin", 0o777) - chmod(installer_exe, 0o777) - chmod(tnpatch_exe, 0o777) - chmod(instwrap_exe, 0o777) - chmod(ruby_dll, 0o777) - - # Execute install wrapper - pinfo(f"\nInstalling Tribes 2 and the TribesNext patch in wine. Please wait...") - chowner(install_dir, user) - system(f"su - {user} -c'xvfb-run -as " + '"-fbdir /var/tmp"' + " wine I:/install_wrapper.exe > /dev/null 2>&1'") - - # Rudamentary check to see if T2 install succeeded - if not isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): bail(f"ERROR: Tribes 2 installation appears to have failed. Check {log_dir}/install_wrapper.log") - - # Rudamentary check to see if TN install succeeded - if not isfile(f"{install_dir}/GameData/TN_Uninstall.exe"): bail(f"ERROR: Tribes Next installation appears to have failed. Check {log_dir}/install_wrapper.log") - - # Replace msvcrt-ruby190.dll with msvcrt-ruby191.dll - pinfo("Updating msvcrt-ruby190.dll to msvcrt-ruby191.dll.\n") - copyfile(ruby_dll,f"{install_dir}/GameData/msvcrt-ruby191.dll") - unlink(f"{install_dir}/GameData/msvcrt-ruby190.dll") - symlink(f"{install_dir}/GameData/msvcrt-ruby191.dll", f"{install_dir}/GameData/msvcrt-ruby190.dll") - - # Install addons - for addon in iglob(f"{pwd}/addons/*"): - if addon.endswith(".zip"): - pinfo(f"Unpacking {addon} into {install_dir}/GameData.") - system(f"unzip -qqd {install_dir}/GameData {addon}") - elif addon.endswith((".tar",".tgz",".tar.gz",".txz",".tar.xz",".tbz",".tar.bz")): - pinfo(f"Unpacking {addon} into {install_dir}/GameData.") - system(f"tar -C {install_dir}/GameData -xf {addon}") - elif addon.endswith(".vl2"): - pinfo(f"Copying {addon} to {install_dir}/GameData/base.") - copyfile(addon,f"{install_dir}/GameData/base/{addon.split('/')[-1]}") - elif addon.endswith("readme.txt"): + # Map wine I: drive to pwd T: drive to install_dir and L: to log_dir + print(f"Mapping I: in wine for {user}.") + try: + symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:") + except FileExistsError: pass + + print(f"Mapping L: in wine for {user}.") + try: + symlink(log_dir, f"{install_dir}/.wine/dosdevices/l:") + except FileExistsError: + pass + + print(f"Mapping T: in wine for {user}.") + try: + symlink(install_parent, f"{install_dir}/.wine/dosdevices/t:") + except FileExistsError: + pass + + # Check for needed exe/zip/dll files in winbin dir + needed_files=[] + if isfile(f"{pwd}/winbin/tribes2gsi.exe"): + pinfo("tribes2gsi.exe found.") + rename(f"{pwd}/winbin/tribes2gsi.exe",f"{pwd}/winbin/tribes2_gsi.exe") + installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" + elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"): + pinfo("tribes2_gsi.exe found.") + installer_exe = f"{pwd}/winbin/tribes2_gsi.exe" else: - pwarn(f"Ignoring {addon}.") + pwarn("Tribes 2 installer not found.") + needed_files.append("tribes2_gsi.exe") + installer_exe = False + + if isfile(f"{pwd}/winbin/TribesNext_rc2a.exe"): + pinfo("TribesNext_rc2a.exe found.") + tnpatch_exe = f"{pwd}/winbin/TribesNext_rc2a.exe" + else: + pwarn("Tribes Next patch not found.") + needed_files.append("TribesNext_rc2a.exe") + tnpatch_exe = False + + ruby_dll = f"{pwd}/winbin/msvcrt-ruby191.dll" + instwrap_exe = f"{pwd}/winbin/install_wrapper.exe" + + # Download files if needed + if not installer_exe or not tnpatch_exe: + action=menu(["~~[D]ownload automatically","[Q]uit"],header="One or more needed files were not found. Download automatically or quit so they can be manually placed in the 'winbin' subdirectory?") + if needed_files == 2: + needed_files=f"{needed_files[0]} and {needed_files[1]}" + if action=="Q": bail(f"Manually place {needed_files} in the 'winbin' subdirectory then rerun setup.") + + if not installer_exe: + for url in installer_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + installer_exe = download_file(url, f"{pwd}/winbin/tribes2_gsi.exe") + if md5sum(installer_exe) == installer_checksum: + print("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror.") + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not installer_exe: + bail("ERROR: Tribes 2 installer could not be downloaded.") + + if not tnpatch_exe: + for url in tnpatch_mirror_list: + try: + pinfo(f"\nDownloading from {url.split('/')[2]}...") + tnpatch_exe = download_file(url, f"{pwd}/winbin/TribesNext_rc2a.exe") + if md5sum(tnpatch_exe) == tnpatch_checksum: + print("Checksum validation passed.") + break + else: + perror("Checksum validation failed. Trying next mirror." ) + except KeyError: + perror("Download error. Trying next mirror.") + continue + + if not tnpatch_exe: + bail("ERROR: Tribes Next patch could not be downloaded.") + + # Present SLAs before beginning install + sla = None + while not sla: + sla=menu(["[V]iew Tribes 2 and TribesNext License Agreements", "[A]ccept License Agreements", "[Q]uit"], header="Please take a moment to review and accept the Tribes 2 and TribeNext License Agreements before beginning automated install.") + if sla == "V": + print(color.DY) + system(f"/usr/bin/less {pwd}/sla/tribes2.txt") + print(color.DP) + system(f"/usr/bin/less {pwd}/sla/tribesnext.txt") + sla = None + elif sla == "A": + break + elif sla == "Q": + bail("You must accept the License Agreements to install.") + + # Ensure sufficient permissions on winbin and its contents + chmod(f"{pwd}/winbin", 0o777) + chmod(installer_exe, 0o777) + chmod(tnpatch_exe, 0o777) + chmod(instwrap_exe, 0o777) + chmod(ruby_dll, 0o777) + + # Execute install wrapper + pinfo(f"\nInstalling Tribes 2 and the TribesNext patch in wine. Please wait...") + chowner(install_dir, user) + system(f"su - {user} -c'xvfb-run -as " + '"-fbdir /var/tmp"' + " wine I:/install_wrapper.exe > /dev/null 2>&1'") + + # Rudamentary check to see if T2 install succeeded + if not isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): bail(f"ERROR: Tribes 2 installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Rudamentary check to see if TN install succeeded + if not isfile(f"{install_dir}/GameData/TN_Uninstall.exe"): bail(f"ERROR: Tribes Next installation appears to have failed. Check {log_dir}/install_wrapper.log") + + # Replace msvcrt-ruby190.dll with msvcrt-ruby191.dll + print("Updating msvcrt-ruby190.dll to msvcrt-ruby191.dll.\n") + copyfile(ruby_dll,f"{install_dir}/GameData/msvcrt-ruby191.dll") + unlink(f"{install_dir}/GameData/msvcrt-ruby190.dll") + symlink(f"{install_dir}/GameData/msvcrt-ruby191.dll", f"{install_dir}/GameData/msvcrt-ruby190.dll") + + # Install addons + for addon in iglob(f"{pwd}/addons/*"): + if addon.endswith(".zip"): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"unzip -qqd {install_dir}/GameData {addon}") + elif addon.endswith((".tar",".tgz",".tar.gz",".txz",".tar.xz",".tbz",".tar.bz")): + pinfo(f"Unpacking {addon} into {install_dir}/GameData.") + system(f"tar -C {install_dir}/GameData -xf {addon}") + elif addon.endswith(".vl2"): + pinfo(f"Copying {addon} to {install_dir}/GameData/base.") + copyfile(addon,f"{install_dir}/GameData/base/{addon.split('/')[-1]}") + elif addon.endswith("readme.txt"): + pass + else: + pwarn(f"Ignoring {addon}.") # Copy t2server and t2bouncer to /usr/local/bin/ - pinfo("Installing t2server script.") + if setup_mode == "U": print("Updating t2server script.") + else: print("Installing t2server script.") copyfile(f"{pwd}/usr/local/bin/t2server",f"{bin_dir}/t2server") - pinfo("Installing t2bouncer script.") + + if setup_mode == "U": print("Updating t2bouncer script.") + else: print("Installing t2bouncer script.") copyfile(f"{pwd}/usr/local/bin/t2bouncer",f"{bin_dir}/t2bouncer") # Set owner/group on install_dir chowner(install_dir, user) - # Clean up temp dir and some unneeded files - pinfo("A little housekeeping...") - if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk") - if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk") - if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE") - if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt") - if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat") - if isfile(f"{install_dir}/GameData/Classic_dedicated_server.bat"): unlink(f"{install_dir}/GameData/Classic_dedicated_server.bat") - if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat") - if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt") - if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt") - if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual") - if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp") - if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt") - if isfile(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt") - if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt") - if isfile(f"{install_dir}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt") - if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.txt") + if setup_mode == "I" or setup_mode == "R": + # Clean up temp dir and some unneeded files + print("A little housekeeping...") + if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk") + if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk") + if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE") + if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt") + if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat") + if isfile(f"{install_dir}/GameData/Classic_dedicated_server.bat"): unlink(f"{install_dir}/GameData/Classic_dedicated_server.bat") + if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat") + if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt") + if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt") + if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual") + if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp") + if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt") + if isfile(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt") + if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt") + if isfile(f"{install_dir}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt") + if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.txt") # Create config directory and files - pinfo(f"\nCreating {etc_dir}, default config, and installing prefs files.") + print(f"\nCreating {etc_dir}, default config, and installing prefs files.") makedirs(f"{etc_dir}/serverprefs", mode=0o775, exist_ok=True) if isfile(f"{etc_dir}/config.yaml"): timestamp = int(time()) rename(f"{etc_dir}/config.yaml",f"{etc_dir}/config.yaml.{timestamp}") pwarn(f"Existing {etc_dir}/config.yaml renamed to {etc_dir}/config.yaml.{timestamp}. Be sure to compare with and update the new config.yaml file.") - pinfo(f"Writing default {etc_dir}/config.yaml.") + print(f"Writing default {etc_dir}/config.yaml.") copyfile(f"{pwd}/etc/t2server/config.yaml", f"{etc_dir}/config.yaml") + print(f"Writing {etc_dir}/release") + copyfile(f"{pwd}/etc/t2server/release", f"{etc_dir}/release") for pfile in iglob(f"{pwd}/etc/t2server/serverprefs/*"): pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.") copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}") # Create systemd units - pinfo("\nCreating systemd units:") - pinfo("- t2server service") + print("\nCreating systemd units:") + print("- t2server service") copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/t2server.service") - pinfo("- t2bouncer service") + print("- t2bouncer service") copyfile(f"{pwd}/etc/systemd/system/t2bouncer.service",f"{unit_dir}/t2bouncer.service") - pinfo("- t2bouncer timer") + print("- t2bouncer timer") copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer") system("systemctl daemon-reload") # Install utility scripts - pinfo("\nInstalling utilities:") - pinfo("- t2bouncer") + print("\nInstalling utilities:") + print("- t2bouncer") copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer") - pinfo("- t2remove") + print("- t2remove") copyfile(f"{pwd}/usr/local/bin/t2remove",f"{bin_dir}/t2remove") - pinfo("- t2help") + print("- t2help") copyfile(f"{pwd}/usr/local/bin/t2help",f"{bin_dir}/t2help") # Install python module @@ -302,6 +357,6 @@ if __name__ == "__main__": # Show help system(f"{bin_dir}/t2help") - menu(['~~[E]xit'],header="You can run 't2help' at any time to view the info above again.") + pinfo("You can run 't2help' at any time to view the info above again.") print(f"{color.X}\n") \ No newline at end of file From 6592b1b4f1f368f2b13c2f1054f4bf0b47b6c929 Mon Sep 17 00:00:00 2001 From: greenseeker Date: Sun, 18 Feb 2024 19:01:48 -0500 Subject: [PATCH 21/21] add 'apt upgrade' to install instructions --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a2ed6a..4ffaf67 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ t2server has a handful of dependencies which are not automatically handled at th ``` $ sudo dpkg --add-architecture i386 $ sudo apt update -$ sudo apt install unzip xvfb python3-minimal python3-pip wine32 cpulimit +$ sudo apt upgrade +$ sudo apt install unzip xvfb python3-minimal python3-pip wine32 cpulimit less $ sudo pip3 install tqdm requests pyyaml ``` (more to be tested and added) @@ -47,6 +48,6 @@ RHEL/CentOS 7 and 8 no longer include wine32 in their repos, so installing on th t2server depends on systemd, so it definitely won't work on any distro that's not using it. -When wine is run in the background, it spawns 2 `wineconsole --use-event=*n*` processes that will consume all available CPU. I have been unable to find a proper solution to this, so for now t2server will run cpulimit against these processes at startup in order to contain them. I have not seen this result in any performance issues. +When wine is run in the background, it spawns 2 "wineconsole --use-event=*nn*" processes that will consume all available CPU. I have been unable to find a proper solution to this, so for now t2server will run cpulimit against these processes at startup in order to contain them. I have not seen this result in any performance issues. console.log includes ANSI escape sequences to set colors and terminal size, so your shell will get a little funky after tailing this log. Just run `reset` to reinitialize your session and you'll be back to normal.