#!/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 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 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 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: user_info = getuser(user) except KeyError: user_info = False 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: print(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 print(f"Creating {log_dir}.") makedirs(log_dir, mode=0o777, exist_ok=True) chmod(log_dir, 0o777) # 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 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("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/ if setup_mode == "U": print("Updating t2server script.") else: print("Installing t2server script.") copyfile(f"{pwd}/usr/local/bin/t2server",f"{bin_dir}/t2server") 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) 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 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.") 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 print("\nCreating systemd units:") print("- t2server service") copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/t2server.service") print("- t2bouncer service") copyfile(f"{pwd}/etc/systemd/system/t2bouncer.service",f"{unit_dir}/t2bouncer.service") print("- t2bouncer timer") copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer") system("systemctl daemon-reload") # Install utility scripts print("\nInstalling utilities:") print("- t2bouncer") copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer") print("- t2remove") copyfile(f"{pwd}/usr/local/bin/t2remove",f"{bin_dir}/t2remove") print("- 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") pinfo("You can run 't2help' at any time to view the info above again.") print(f"{color.X}\n")