From 8ffa4dacbdae5dd828a8423960b1cb407743f5af Mon Sep 17 00:00:00 2001 From: Carl Manzi Date: Sat, 2 Jan 2021 23:30:39 -0500 Subject: [PATCH] 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