mirror of
https://github.com/greenseeker/t2server.git
synced 2026-01-30 08:31:15 +00:00
Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6592b1b4f1 | ||
|
|
e2ab01674f | ||
|
|
88cdae6de0 | ||
|
|
aca1b4056b | ||
|
|
0ca42d26f9 | ||
|
|
a67dc7bb48 | ||
|
|
d9017910ec | ||
|
|
95374b3d23 | ||
|
|
c410fcb645 |
|
|
@ -11,7 +11,8 @@ t2server has a handful of dependencies which are not automatically handled at th
|
||||||
```
|
```
|
||||||
$ sudo dpkg --add-architecture i386
|
$ sudo dpkg --add-architecture i386
|
||||||
$ sudo apt update
|
$ 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
|
$ sudo pip3 install tqdm requests pyyaml
|
||||||
```
|
```
|
||||||
(more to be tested and added)
|
(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.
|
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
---
|
---
|
||||||
## ServerPrefs indicates the server config file in /etc/t2server/serverprefs
|
## ServerPrefs indicates the server config file in /etc/t2server/serverprefs to
|
||||||
## to use. This is case-sensitive and must match the filename exactly.
|
## use. This is case-sensitive and must match the filename exactly.
|
||||||
ServerPrefs: Classic_CTF.cs
|
ServerPrefs: Classic_CTF.cs
|
||||||
|
|
||||||
## Tribes 2 servers tend to get unstable after a couple weeks of being online.
|
## 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.
|
## Here you can specify a day and hour to automatically bounce the server. Set
|
||||||
## Set RestartTime to the hour of the day, 0-23, to cycle the server
|
## RestartTime to the hour of the day, 0-23, to cycle the server (eg. 4=4:00am,
|
||||||
## (eg. 4=4:00am, 16=4:00pm) or False to disable. Set RestartDay to the
|
## 16=4:00pm) or False to disable. Set RestartDay to the three-letter
|
||||||
## three-letter abbreviation of the day on which the server should be restarted
|
## abbreviation of the day on which the server should be restarted (Sun, Mon,
|
||||||
## (Sun, Mon, Tue, Wed, Thu, Fri, or Sat). This will be ignored if
|
## Tue, Wed, Thu, Fri, or Sat). This will be ignored if RestartTime = False.
|
||||||
## RestartTime = False.
|
|
||||||
RestartTime: False
|
RestartTime: False
|
||||||
RestartDay: Mon
|
RestartDay: Mon
|
||||||
|
|
||||||
## Set Mod to the directory name of the mod to be loaded, or 'base' for
|
## Set Mod to the directory name of the mod to be loaded, or 'base' for vanilla
|
||||||
## vanilla (but why?). This is case-sensitive and must match the subdirectory
|
## (but why?). This is case-sensitive and must match the subdirectory name
|
||||||
## name exactly.
|
## exactly.
|
||||||
Mod: Classic
|
Mod: Classic
|
||||||
|
|
||||||
## Set Public to False to host a LAN-only game, or to True to host a public
|
## 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.
|
## detection. This setting has no effect if Public = False.
|
||||||
OverrideMITM: True
|
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
|
## Configure a custom map rotation list. The standard Mission Types are
|
||||||
## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM"
|
## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM"
|
||||||
## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege",
|
## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege", map
|
||||||
## "TeamHunters", and "TeamRabbit". Your server will always launch with the
|
## "TeamHunters", and "TeamRabbit". If you're running a mod that handles
|
||||||
## MissionType and Map specified in your serverprefs file ($Host::MissionType
|
## rotation, set these to 'False'.
|
||||||
## 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:
|
## Example:
|
||||||
## MissionType: CTF
|
## MissionType: CTF
|
||||||
## MapList: ["Katabatic", "Minotaur", "Tombstone"]
|
## MapList: ["Katabatic", "Minotaur", "Tombstone"]
|
||||||
|
|
|
||||||
1
etc/t2server/release
Normal file
1
etc/t2server/release
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0.8.0
|
||||||
431
setup
431
setup
|
|
@ -62,10 +62,59 @@ def download_file(url, filename):
|
||||||
pbar.close()
|
pbar.close()
|
||||||
return filename
|
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 __name__ == "__main__":
|
||||||
if action == "Q": bail()
|
# 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
|
# Check if user exists
|
||||||
try:
|
try:
|
||||||
|
|
@ -73,226 +122,232 @@ if __name__ == "__main__":
|
||||||
except KeyError:
|
except KeyError:
|
||||||
user_info = False
|
user_info = False
|
||||||
|
|
||||||
# Create or repurpose user
|
if setup_mode == "I" or setup_mode == "R":
|
||||||
if user_info:
|
# Create or repurpose user if installing or reinstalling
|
||||||
if user_info.pw_dir == install_dir:
|
if user_info:
|
||||||
pwarn(f"User '{user}' exists and will be reused.")
|
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:
|
else:
|
||||||
bail(f"ERROR: User '{user}' already exists and may belong to another person or process.")
|
print(f"Creating {user} user and {install_dir}.")
|
||||||
else:
|
system(f"useradd -md {install_dir} {user}")
|
||||||
pinfo(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
|
# Create log_dir
|
||||||
pinfo(f"Creating {log_dir}.")
|
print(f"Creating {log_dir}.")
|
||||||
makedirs(log_dir, mode=0o777, exist_ok=True)
|
makedirs(log_dir, mode=0o777, exist_ok=True)
|
||||||
chmod(log_dir, 0o777)
|
chmod(log_dir, 0o777)
|
||||||
|
|
||||||
# Create .wine dir
|
# Create .wine dir
|
||||||
pinfo(f"Creating {install_dir}/.wine defaults.")
|
print(f"Creating {install_dir}/.wine defaults.")
|
||||||
system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'")
|
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
|
# Map wine I: drive to pwd T: drive to install_dir and L: to log_dir
|
||||||
pinfo(f"Mapping I: in wine for {user}.")
|
print(f"Mapping I: in wine for {user}.")
|
||||||
try:
|
try:
|
||||||
symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:")
|
symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:")
|
||||||
except FileExistsError:
|
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"):
|
|
||||||
pass
|
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:
|
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/
|
# 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")
|
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")
|
copyfile(f"{pwd}/usr/local/bin/t2bouncer",f"{bin_dir}/t2bouncer")
|
||||||
|
|
||||||
# Set owner/group on install_dir
|
# Set owner/group on install_dir
|
||||||
chowner(install_dir, user)
|
chowner(install_dir, user)
|
||||||
|
|
||||||
# Clean up temp dir and some unneeded files
|
if setup_mode == "I" or setup_mode == "R":
|
||||||
pinfo("A little housekeeping...")
|
# Clean up temp dir and some unneeded files
|
||||||
if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk")
|
print("A little housekeeping...")
|
||||||
if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk")
|
if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk")
|
||||||
if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE")
|
if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk")
|
||||||
if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt")
|
if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE")
|
||||||
if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat")
|
if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt")
|
||||||
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_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat")
|
||||||
if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.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/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt")
|
if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat")
|
||||||
if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt")
|
if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt")
|
||||||
if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual")
|
if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt")
|
||||||
if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp")
|
if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual")
|
||||||
if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt")
|
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}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt")
|
if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt")
|
||||||
if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.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}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt")
|
if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt")
|
||||||
if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.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
|
# 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)
|
makedirs(f"{etc_dir}/serverprefs", mode=0o775, exist_ok=True)
|
||||||
if isfile(f"{etc_dir}/config.yaml"):
|
if isfile(f"{etc_dir}/config.yaml"):
|
||||||
timestamp = int(time())
|
timestamp = int(time())
|
||||||
rename(f"{etc_dir}/config.yaml",f"{etc_dir}/config.yaml.{timestamp}")
|
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.")
|
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")
|
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/*"):
|
for pfile in iglob(f"{pwd}/etc/t2server/serverprefs/*"):
|
||||||
pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.")
|
pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.")
|
||||||
copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}")
|
copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}")
|
||||||
|
|
||||||
# Create systemd units
|
# Create systemd units
|
||||||
pinfo("\nCreating systemd units:")
|
print("\nCreating systemd units:")
|
||||||
pinfo("- t2server service")
|
print("- t2server service")
|
||||||
copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/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")
|
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")
|
copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer")
|
||||||
system("systemctl daemon-reload")
|
system("systemctl daemon-reload")
|
||||||
|
|
||||||
# Install utility scripts
|
# Install utility scripts
|
||||||
pinfo("\nInstalling utilities:")
|
print("\nInstalling utilities:")
|
||||||
pinfo("- t2bouncer")
|
print("- t2bouncer")
|
||||||
copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer")
|
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")
|
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")
|
copyfile(f"{pwd}/usr/local/bin/t2help",f"{bin_dir}/t2help")
|
||||||
|
|
||||||
# Install python module
|
# Install python module
|
||||||
|
|
@ -302,6 +357,6 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# Show help
|
# Show help
|
||||||
system(f"{bin_dir}/t2help")
|
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")
|
print(f"{color.X}\n")
|
||||||
|
|
@ -47,6 +47,15 @@ def setperm(file):
|
||||||
pwarn(f"Failed to set permissions on {file}")
|
pwarn(f"Failed to set permissions on {file}")
|
||||||
log.write(f"... FAILED!\n")
|
log.write(f"... FAILED!\n")
|
||||||
pass
|
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:
|
else:
|
||||||
log.write(f"Setting mode 664 on file {file}")
|
log.write(f"Setting mode 664 on file {file}")
|
||||||
try:
|
try:
|
||||||
|
|
@ -101,3 +110,4 @@ with open(f'{log_dir}/t2fixer.log', 'w') as log:
|
||||||
setperm(f"{bin_dir}/t2fixer")
|
setperm(f"{bin_dir}/t2fixer")
|
||||||
setperm(f"{bin_dir}/t2help")
|
setperm(f"{bin_dir}/t2help")
|
||||||
setperm(f"{bin_dir}/t2support.py")
|
setperm(f"{bin_dir}/t2support.py")
|
||||||
|
setperm(f"{etc_dir}/release")
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
#!/usr/bin/env -S python3 -B
|
#!/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.DC}The follow commands can be used to manage your Tribes 2 server:")
|
||||||
|
|
||||||
print(f"""\n{color.BW}systemctl <action> t2server{color.DC}: The t2server service can be managed with the
|
print(f"""\n{color.BW}systemctl <action> 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
|
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
|
with t2server. It's recommended to run this command any time you make changes
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,26 @@ from requests import get
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from t2support import *
|
from t2support import *
|
||||||
|
|
||||||
winecmd=["/usr/bin/wineconsole", "--backend=curses"]
|
winecmd = ["/usr/bin/wine"]
|
||||||
argbase=["Tribes2.exe","-dedicated"]
|
argbase = ["Tribes2.exe","-dedicated"]
|
||||||
basecmd=winecmd+argbase
|
basecmd = winecmd + argbase
|
||||||
parent=getppid()
|
parent = getppid()
|
||||||
|
|
||||||
def dso_cleanup():
|
def dso_cleanup():
|
||||||
"""
|
"""
|
||||||
This function finds all .dso files (compiled .cs scripts) and deletes them
|
This function finds all .dso files (compiled .cs scripts) and deletes them
|
||||||
if they are older than their associated .cs script so that Tribes 2 will
|
so that Tribes 2 will recompile them at startup.
|
||||||
recompile them at startup.
|
|
||||||
"""
|
"""
|
||||||
for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True):
|
for dso_file in iglob(f"{install_dir}/**/*.dso", recursive = True):
|
||||||
cs_file=dso_file[:-4]
|
print(f"Deleting {dso_file} so it can be rebuilt.")
|
||||||
if isfile(cs_file):
|
unlink(dso_file)
|
||||||
cs_mtime=getmtime(cs_file)
|
|
||||||
dso_mtime=getmtime(dso_file)
|
|
||||||
if cs_mtime > dso_mtime:
|
|
||||||
print(f"Deleting {dso_file} so it can be rebuilt.")
|
|
||||||
unlink(dso_file)
|
|
||||||
|
|
||||||
def build_args(basecmd,config):
|
def build_args(basecmd,config):
|
||||||
"""
|
"""
|
||||||
This function assembles the command line to launch the server based on the
|
This function assembles the command line to launch the server based on the
|
||||||
config.yaml file.
|
config.yaml file.
|
||||||
"""
|
"""
|
||||||
server_command=basecmd
|
server_command = basecmd
|
||||||
if config['Public']:
|
if config['Public']:
|
||||||
server_command.append("-online")
|
server_command.append("-online")
|
||||||
else:
|
else:
|
||||||
|
|
@ -63,12 +57,12 @@ def server_files(config):
|
||||||
print(f"Deleting {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
print(f"Deleting {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
||||||
unlink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
unlink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
||||||
print(f"Linking {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs -> {etc_dir}/serverprefs/{config['ServerPrefs']}")
|
print(f"Linking {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs -> {etc_dir}/serverprefs/{config['ServerPrefs']}")
|
||||||
makedirs(f"{install_dir}/GameData/{config['Mod']}/prefs", mode=0o755, exist_ok=True)
|
makedirs(f"{install_dir}/GameData/{config['Mod']}/prefs", mode = 0o755, exist_ok = True)
|
||||||
symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
|
||||||
|
|
||||||
if config["MapList"] and config["MissionType"]:
|
if config["MapList"] and config["MissionType"]:
|
||||||
print(f"Writing {install_dir}/GameData/base/prefs/missions.txt")
|
print(f"Writing {install_dir}/GameData/base/prefs/missions.txt")
|
||||||
makedirs(f"{install_dir}/GameData/base/prefs", mode=0o755, exist_ok=True)
|
makedirs(f"{install_dir}/GameData/base/prefs", mode = 0o755, exist_ok = True)
|
||||||
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
|
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
|
||||||
for mission in config["MapList"]:
|
for mission in config["MapList"]:
|
||||||
mlist.write(f"{config['MissionType']} {mission}\n")
|
mlist.write(f"{config['MissionType']} {mission}\n")
|
||||||
|
|
@ -78,7 +72,6 @@ def server_files(config):
|
||||||
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
|
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
|
||||||
mlist.write("")
|
mlist.write("")
|
||||||
|
|
||||||
|
|
||||||
def runaway_control(runaway_proc_cmd):
|
def runaway_control(runaway_proc_cmd):
|
||||||
"""
|
"""
|
||||||
When run in the background, wine will spawn two 'wineconsole'
|
When run in the background, wine will spawn two 'wineconsole'
|
||||||
|
|
@ -88,9 +81,9 @@ def runaway_control(runaway_proc_cmd):
|
||||||
for x in range(20):
|
for x in range(20):
|
||||||
sleep(15)
|
sleep(15)
|
||||||
print(f"Checking for runaway '{runaway_proc_cmd}' process...")
|
print(f"Checking for runaway '{runaway_proc_cmd}' process...")
|
||||||
runaway_pid=run(["/usr/bin/pgrep","-f", runaway_proc_cmd],stdout=PIPE).stdout
|
runaway_pid = run(["/usr/bin/pgrep","-f", runaway_proc_cmd],stdout = PIPE).stdout
|
||||||
if runaway_pid:
|
if runaway_pid:
|
||||||
runaway_pid=str(int(runaway_pid))
|
runaway_pid = str(int(runaway_pid))
|
||||||
print(f"Limiting runaway wineconsole process: {runaway_pid}")
|
print(f"Limiting runaway wineconsole process: {runaway_pid}")
|
||||||
run(["/usr/bin/cpulimit","-bp", runaway_pid,"-l2"])
|
run(["/usr/bin/cpulimit","-bp", runaway_pid,"-l2"])
|
||||||
break
|
break
|
||||||
|
|
@ -99,7 +92,7 @@ def master_heartbeat():
|
||||||
"""
|
"""
|
||||||
A public Tribes 2 server should send a regular heartbeat to the TribexNext
|
A public Tribes 2 server should send a regular heartbeat to the TribexNext
|
||||||
master server so that it appears in the list, however this seems
|
master server so that it appears in the list, however this seems
|
||||||
inconsistent, so this function takes over that responsibility.
|
inconsistent, so this function takes over that responsibility if enabled.
|
||||||
"""
|
"""
|
||||||
print("Starting TribesNext heartbeat thread...")
|
print("Starting TribesNext heartbeat thread...")
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -110,43 +103,24 @@ def is_valid_ip(ip):
|
||||||
"""Check if an ip looks like a valid IP address."""
|
"""Check if an ip looks like a valid IP address."""
|
||||||
return bool(match(r"^(\d{1,3}\.){3}\d{1,3}$", ip))
|
return bool(match(r"^(\d{1,3}\.){3}\d{1,3}$", ip))
|
||||||
|
|
||||||
def override_mitm():
|
|
||||||
"""
|
|
||||||
Tribes 2 servers try to detect descrepencies between their own IP and the IP
|
|
||||||
that the client believes it's connecting to as possible man-in-the-middle
|
|
||||||
attacks, however this often interfers with connections when the server is
|
|
||||||
NATed or multi-homed. This function gets the public-facing IP of the host
|
|
||||||
and writes an autoexec script to effectively disable this detection.
|
|
||||||
"""
|
|
||||||
for ip_service in ["http://api.ipify.org","http://ifconfig.me","http://ipinfo.io/ip"]:
|
|
||||||
r=get(ip_service)
|
|
||||||
if r.status_code == 200 and is_valid_ip(r.text):
|
|
||||||
print(f"Got public IP address {r.text}")
|
|
||||||
break
|
|
||||||
if r.status_code != 200: bail("Could not get this server's public IP address.")
|
|
||||||
print(f"Overriding Man-in-the-Middle attack detection.")
|
|
||||||
with open(f'{install_dir}/GameData/base/scripts/autoexec/noMITM.cs', 'w') as nomitm_script:
|
|
||||||
nomitm_script.write(f'$IPv4::InetAddress = "{r.text}";\n')
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# Get the version from the release file and print it.
|
||||||
|
if isfile(release_file):
|
||||||
|
with open(release_file, 'r') as rf:
|
||||||
|
version = rf.read().rstrip()
|
||||||
|
else:
|
||||||
|
version = None
|
||||||
|
|
||||||
|
if version: print(f"t2server version {version}")
|
||||||
|
|
||||||
# If run interactively, warn user that they probably don't want to do this unless troubleshooting.
|
# If run interactively, warn user that they probably don't want to do this unless troubleshooting.
|
||||||
if parent == 1:
|
if parent == 1:
|
||||||
print(f"Started by init")
|
print(f"Started by init")
|
||||||
else:
|
else:
|
||||||
interactive_run=menu(['[Y]es, run t2server interactively.',"~~[N]o, abort."],header="Running t2server directly can be helpful for troubleshooting but generally it's best to manage your server with 'systemctl'. Do you still want to run t2server?",footer="Choose [N] and run 't2help' if you're unsure of what to do.")
|
interactive_run = menu([f'[Y]es, run t2server interactively.',"~~[N]o, abort."], header = "Running t2server directly can be helpful for troubleshooting but generally it's best to manage your server with 'systemctl'. Do you still want to run t2server?", footer = "Choose [N] and run 't2help' if you're unsure of what to do.")
|
||||||
if interactive_run == 'N': bail()
|
if interactive_run == 'N': bail()
|
||||||
chdir(f"{install_dir}/GameData")
|
chdir(f"{install_dir}/GameData")
|
||||||
|
|
||||||
# Set default configuration
|
|
||||||
config_defaults = {
|
|
||||||
'ServerPrefs' : 'Classic_CTF.cs',
|
|
||||||
'Mod' : 'Classic',
|
|
||||||
'Public' : False,
|
|
||||||
'OverrideMITM': True,
|
|
||||||
'MissionType' : 'CTF',
|
|
||||||
'MapList' : False
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read configuration from config.yaml
|
# Read configuration from config.yaml
|
||||||
with open(f'{etc_dir}/config.yaml', 'r') as f:
|
with open(f'{etc_dir}/config.yaml', 'r') as f:
|
||||||
loaded_config = yaml.full_load(f)
|
loaded_config = yaml.full_load(f)
|
||||||
|
|
@ -161,33 +135,55 @@ if __name__ == "__main__":
|
||||||
if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"):
|
if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"):
|
||||||
bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}")
|
bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}")
|
||||||
|
|
||||||
# Delete any pre-existing noMITM script/dso. It will be recreated below, if needed.
|
# Delete any pre-existing 00_t2server_opts script. It will be recreated below, if needed.
|
||||||
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs")
|
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs")
|
||||||
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso")
|
|
||||||
|
|
||||||
# Create serverprefs symlink and missions.txt (if appropriate), clean out stale dso files, then assemble the command line arguments to launch the server
|
# Create serverprefs symlink and missions.txt (if appropriate), clean out dso files, then assemble the command line arguments to launch the server
|
||||||
server_files(config)
|
server_files(config)
|
||||||
dso_cleanup()
|
if config['DSOCleanup']: dso_cleanup()
|
||||||
server_command=build_args(basecmd,config)
|
server_command = build_args(basecmd,config)
|
||||||
|
|
||||||
# If this is a public server, start a hearbeat thread. Also write the MITM override file if configured.
|
# Start the opts script with a comment at the top indicating this is auto-generated and shouldn't be edited, and also disable PureServer.
|
||||||
|
opts_script_content = "// This file is updated automatically by t2server based on /etc/t2server/config.yaml.\n// Do not edit this file directly.\n$Host::PureServer = 0;\n"
|
||||||
|
|
||||||
|
# If this is a public server...
|
||||||
if config['Public']:
|
if config['Public']:
|
||||||
print("Starting heartbeat...")
|
# Start a heartbeat thread, if configured.
|
||||||
if config['OverrideMITM']: override_mitm()
|
if config['Heartbeat']:
|
||||||
heartbeat=Thread(target=master_heartbeat)
|
heartbeat = Thread(target = master_heartbeat)
|
||||||
heartbeat.daemon=True
|
heartbeat.daemon = True
|
||||||
heartbeat.start()
|
heartbeat.start()
|
||||||
|
# Capture the public IP for MITM override, if configured.
|
||||||
|
if config['OverrideMITM']:
|
||||||
|
for ip_service in ["http://api.ipify.org","http://ifconfig.me","http://ipinfo.io/ip"]:
|
||||||
|
r = get(ip_service)
|
||||||
|
if r.status_code == 200 and is_valid_ip(r.text):
|
||||||
|
print(f"Got public IP address {r.text}")
|
||||||
|
break
|
||||||
|
if r.status_code != 200: bail("Could not get this server's public IP address.")
|
||||||
|
opts_script_content += f'$IPv4::InetAddress = "{r.text}";\n'
|
||||||
|
print(f"Overriding Man-in-the-Middle attack detection.")
|
||||||
|
|
||||||
|
# If MissionType and MapList are defined, write the first map into 00_t2server_opts.cs
|
||||||
|
if config['MissionType'] and config['MapList']:
|
||||||
|
opts_script_content += f'$Host::MissionType = "' + config['MissionType'] + '";\n$Host::Map = "' + config['MapList'][0] + '";\n'
|
||||||
|
if config['MissionType'] and config['MissionType'].lower() != 'teamrabbit':
|
||||||
|
opts_script_content += "$Host::LoadTR2Gametype = 0;\n"
|
||||||
|
|
||||||
|
# Write all options to the 00_t2server_opts file
|
||||||
|
with open(f'{install_dir}/GameData/base/scripts/autoexec/00_t2server_opts.cs', 'w') as opts_script:
|
||||||
|
opts_script.write(opts_script_content)
|
||||||
|
|
||||||
# Cap the CPU of the runaway wineconsole processes
|
# Cap the CPU of the runaway wineconsole processes
|
||||||
wcpid1_limit=Thread(target=runaway_control, args=("wineconsole --use-event=52",))
|
wcpid1_limit = Thread(target = runaway_control, args = ("wineconsole --use-event=52",))
|
||||||
wcpid1_limit.start()
|
wcpid1_limit.start()
|
||||||
wcpid2_limit=Thread(target=runaway_control, args=("wineconsole --use-event=188",))
|
wcpid2_limit = Thread(target = runaway_control, args = ("wineconsole --use-event=188",))
|
||||||
wcpid2_limit.start()
|
wcpid2_limit.start()
|
||||||
|
|
||||||
# Open the console log file if running as service and start the Tribes 2 server
|
# Open the console log file if running as service and start the Tribes 2 server
|
||||||
print(f"Starting Tribes 2 server: " + " ".join(server_command))
|
print(f"Starting Tribes 2 server: " + " ".join(server_command))
|
||||||
if parent == 1:
|
if parent == 1:
|
||||||
with open(f"{log_dir}/console.log", 'w') as consolelog:
|
with open(f"{log_dir}/console.log", 'w') as consolelog:
|
||||||
run(server_command,stdout=consolelog)
|
run(server_command,stdout = consolelog)
|
||||||
else:
|
else:
|
||||||
run(server_command)
|
run(server_command)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,18 @@ etc_dir = "/etc/t2server"
|
||||||
log_dir = "/var/log/t2server"
|
log_dir = "/var/log/t2server"
|
||||||
unit_dir = "/etc/systemd/system"
|
unit_dir = "/etc/systemd/system"
|
||||||
bin_dir = "/usr/local/bin"
|
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:
|
class color:
|
||||||
X = '\033[m' # Reset
|
X = '\033[m' # Reset
|
||||||
|
|
@ -78,7 +90,7 @@ def menu(option_list,header="",footer=""):
|
||||||
try:
|
try:
|
||||||
key=search(r'\[([0-9a-zA-Z])\]', option).group(1)
|
key=search(r'\[([0-9a-zA-Z])\]', option).group(1)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
bail("Error while processing menu option list.")
|
||||||
if option.startswith("~~"):
|
if option.startswith("~~"):
|
||||||
default = str(key)
|
default = str(key)
|
||||||
keys.append(key.upper())
|
keys.append(key.upper())
|
||||||
|
|
|
||||||
|
|
@ -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 >"
|
WriteLog("Click [Next]: " & ControlClick("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 5)) ; click "Next >"
|
||||||
|
|
||||||
; Welcome 2
|
; 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 >"
|
WriteLog("Click [Next]: " & ControlClick("Welcome", "Welcome to the Tribes 2 Setup program", 3)) ; click "Next >"
|
||||||
|
|
||||||
; Credits
|
; 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 >"
|
WriteLog("Click [Next]: " & ControlClick("Start Installation", "You are now ready to install Tribes 2.", 3)) ; click "Next >"
|
||||||
|
|
||||||
; Register
|
; 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("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 >"
|
WriteLog("Click [Next]: " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 3)) ; click "Next >"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue