Browse Source

init repo

master
server 4 years ago
commit
3b2c485c40
  1. 2
      .gitignore
  2. 587
      SourceManager.py
  3. 1
      requirements.txt

2
.gitignore

@ -0,0 +1,2 @@
old_version/
servers.txt

587
SourceManager.py

@ -0,0 +1,587 @@
#!/usr/bin/python3
VERSION = 1.0
#SYS IMPORTS
import sys
import os
import types
import hashlib
import shutil
from os.path import isfile as check_file
from os.path import isdir as check_dir
from os import remove as remove_file
from glob import glob
from json import dumps
import argparse
from subprocess import call as SCall
from time import time
FNULL = open(os.devnull,'w')
#VOLVO
import a2s
from valve.rcon import RCON
from valve.rcon import log as RCON_LOGGING
RCON_LOGGING.setLevel(40)
import traceback
DEBUG = 0
ALL_YES = 0
class colors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
class new_o:
def __init__(self, server_name):
self.prefix = "[{:^25}]".format(server_name)
def info(self, text, end = "\n"):
print(self.prefix + "[ {}INFO{}]{}".format(colors.OKBLUE, colors.ENDC, text), end = end)
return True
def error(self, text):
print(self.prefix + "[{}ERROR{}]{}".format(colors.FAIL, colors.ENDC, text))
return True
def warning(self, text):
print(self.prefix + "[ {}WARN{}]{}".format(colors.WARNING, colors.ENDC, text))
return True
def debug(self, text):
if DEBUG:
print(self.prefix + "[{}DEBUG{}]{}".format(colors.HEADER, colors.ENDC, text))
return True
class o:
def info(text):
print("[ {}INFO{}]{}".format(colors.OKBLUE, colors.ENDC, text))
return True
def error(text):
print("[{}ERROR{}]{}".format(colors.FAIL, colors.ENDC, text))
return True
def warning(text):
print("[ {}WARN{}]{}".format(colors.WARNING, colors.ENDC, text))
return True
def debug(text):
if DEBUG:
print("[{}DEBUG{}]{}".format(colors.HEADER, colors.ENDC, text))
return True
def hashfile(filepath, blocksize = 65536):
file_hash = hashlib.sha256()
with open(filepath, "rb") as f:
fb = f.read(blocksize)
while len(fb) > 0:
file_hash.update(fb)
fb = f.read(blocksize)
return file_hash.hexdigest()
def create_symbolic_file(source_file, symbolic_file):
cmd = f"ln -s {source_file} {symbolic_file}"
print(f"Execute system cmd: {cmd}")
SCall(cmd.split())
class Manager:
servers = []
max_lines = 4
current_server = None
server_choices = []
def __init__(self, servers_file = "./servers.txt"):
self.load_servers(servers_file)
def select_server(self, choice):
for server in self.servers:
if server.select(choice):
self.current_server = server
o.debug("Selected: {}".format(self.current_server))
break
if not self.current_server:
o.error("Cannot choice server! Choice is invalid!")
sys.exit(1)
def load_servers(self, server_list_path):
o.debug("Load servers file: {}".format(server_list_path))
################################################################################
if check_file(server_list_path):
with open(server_list_path, "r") as f_servers:
readed_lines = 0
line = f_servers.readline()
while line:
if line and not line[0] == "#":
if readed_lines == self.max_lines:
readed_lines = 0
line = line.split("\n")[0]
if not readed_lines:
name = line
elif readed_lines == 1:
ip, port = line.split(":")
elif readed_lines == 2:
rcon_password = line
elif readed_lines == 3:
root_directory = line
self.servers.append(Server(ip, port, rcon_password, name, root_directory))
################################################################
readed_lines += 1
line = f_servers.readline()
############################################################################
if not self.servers:
o.error("Servers not found! Insert server in {}".format(server_list_path))
sys.exit(2)
else:
o.error("Cannot find server file. App create him. Insert values!")
with open(server_list_path, "w") as f_servers:
f_servers.write("""
#//////////////////////
#//PLACE HERE
#//////////////////////
#//ServerName
#//IP:PORT
#//RCON PASSWORD
#//tf root folder
#...
""")
sys.exit(1)
################################################################################
def execute(self, func, *args):
"""if self.current_server:
getattr(self.current_server, func, None)(*args)
else:
for server in self.servers:
for choice in self.server_choices:
if server.choice(choice):
getattr(server, func, None)(*args)
break
"""
if self.current_server:
getattr(self.current_server, func, None)(*args)
elif self.server_choices:
for server in self.servers:
for choice in self.server_choices:
if server.select(choice):
getattr(server, func, None)(*args)
break
else:
for server in self.servers:
getattr(server, func, None)(*args)
class Server:
obj = None
def __init__(self, ip, port, rcon_pass, name, root_directory):
self.address = (ip, int(port))
self.password = rcon_pass
self.name = name
self.o = new_o(name)
self.root = root_directory
o.debug(self)
def __str__(self):
return "{:^25} on {}".format(self.name, self.address)
def ln_vpk_files(self, vpk_directory):
vpk_files = glob(f"{self.root}/*.vpk")
vpk_directory_files = glob(f"{vpk_directory}/*.vpk")
for server_vpk_file in vpk_files:
for other_vpk_file in vpk_directory_files:
if server_vpk_file.split("/")[-1] == other_vpk_file.split("/")[-1]:
if hashfile(server_vpk_file) == hashfile(other_vpk_file):
print(f"Create symbolic link from {other_vpk_file} to {server_vpk_file}")
if self.wait_input():
remove_file(server_vpk_file)
create_symbolic_file(other_vpk_file, server_vpk_file)
return True
def upgrade_metamod(self):
pass
def remove_plugin(self, plugin_name, need_reloads_plugins = True):
plugins_list = glob(f"{self.root}/addons/sourcemod/plugins/*.smx") + glob(f"{self.root}/addons/sourcemod/plugins/*/*.smx")
for plugin in plugins_list:
if plugin.split("/")[-1] == plugin_name + ".smx":
self.o.info(f"Remove plugin: {plugin}?")
if self.wait_input():
remove_file(plugin)
if need_reloads_plugins:
self.o.info("Send sm plugins refresh command...", end = "\t")
self.rcon("sm plugins refresh", hide_server_name=True)
return True
def upgrade_plugin(self, fresh_path, need_reloads_plugins = True):
new_hash = hashfile(fresh_path)
fresh_plugin_name = fresh_path.split("/")[-1:][0]
plugins_list = glob(f"{self.root}/addons/sourcemod/plugins/*.smx") + glob(f"{self.root}/addons/sourcemod/plugins/*/*.smx")
for plugin_path in plugins_list:
if plugin_path.split("/")[-1:][0] == fresh_plugin_name:
old_hash = hashfile(plugin_path)
if old_hash == new_hash:
self.o.info(f"Plugin {fresh_plugin_name} currenty updated!")
return False
else:
self.o.info(f"Upgrade plugin {fresh_plugin_name}...")
self.o.info(f"copy {fresh_path} to {plugin_path}")
shutil.copyfile(fresh_path, plugin_path)
if need_reloads_plugins:
self.o.info("Send sm plugins refresh command...", end = "\t")
self.rcon("sm plugins refresh", hide_server_name=True)
return True
self.o.info("Upgraded plugin not found in server...")
return False
def wait_input(self, yes = "y", no = "n"):
if ALL_YES:
return True
while True:
print("Enter \"{}\" to accept or \"{}\" to negative response:".format(yes, no) ,end="\t")
response = input()
if response.lower() == yes:
return True
if response.lower() == no:
return False
def select(self, request):
if self.name == request:
return True
elif self.address == request:
return True
elif str(self.address[1]) == request:
return True
elif request in self.root:
return True
else:
return False
def status(self):
player_count, max_count, ping = self.count_players()
if player_count < 0:
print("{} | Not responsed(((".format(self))
else:
print("{} | {}/{} players | {} ms".format(self, player_count, max_count, ping))
def count_players(self):
try:
ping = 0.0
try:
start_time = time()
server = a2s.info(tuple(self.address))
count, max_count = server.player_count, server.max_players
ping = server.ping
except NameError:
ping = time() - start_time
except ValueError:
return -1, -1, -1
return int(count), int(max_count), round(ping, 3) * 1000
except:
#traceback.print_exc()
return -1, -1, -1
def net_status_json(self):
rcon_response = ""
json_result = {}
try:
with RCON(self.address, self.password) as rcon:
rcon_response = rcon.execute("net_status")
response = rcon_response.body.decode("utf8","ignore")
response = response.split("\n")
#####################################################
gamemode, server_type, connections = response[1].split(": ")[1].split(",")
connections = int(connections.split()[0])
#####################################################
client_port, server_port, hltv_port, matchmaking_port, systemlink_port, lan_port = response[2].split(": ")[1].split(",")
client_port = int(client_port.split()[1])
server_port = int(server_port.split()[1])
hltv_port = int(hltv_port.split()[1])
#####################################################
try:
latency_avg_out, latency_avg_in = response[3].split(": ")[1].split(",")
latency_avg_out = float(latency_avg_out.split()[2][:-1])
latency_avg_in = float(latency_avg_in.split()[1][:-1])
except IndexError:
print(dumps({
"config":{
"gamemode":gamemode,
"server_type":server_type,
"connections":connections
},
"ports":{
"client":client_port,
"server":server_port,
"hltv":hltv_port
},
"latency":{
"avg_out":0.0,
"avg_in":0.0
},
"loss":{
"avg_out":0.0,
"avg_in":0.0
},
"packets":{
"total":{
"out":0,
"in":0
},
"client":{
"out":0,
"in":0
}
},
"data":{
"total":{
"out":0.0,
"in":0.0
},
"client":{
"out":0.0,
"in":0.0
}
}
}))
return
#####################################################
loss_avg_out, loss_avg_in = response[4].split(": ")[1].split(",")
loss_avg_out = float(loss_avg_out.split()[2])
loss_avg_in = float(loss_avg_in.split()[1])
#####################################################
packets_total_out, packets_total_in = response[5].split(": ")[1].split(",")
packets_total_out = float(packets_total_out.split()[3][:-2])
packets_total_in = float(packets_total_in.split()[1][:-2])
#####################################################
packets_per_client_out, packets_per_client_in = response[6].split(",")
packets_per_client_out = float(packets_per_client_out.split()[3][:-2])
packets_per_client_in = float(packets_per_client_in.split()[1][:-2])
#####################################################
data_total_out = response[7].split(": ")[1].split(",")[0].split()
if len(data_total_out) > 4:
if data_total_out[4] == "kB/s":
data_total_out = float(data_total_out[3]) * 1024
elif data_total_out[4] == "MB/s":
data_total_out = float(data_total_out[3]) * 1024 * 1024
else:
data_total_out = float(data_total_out[3])
#
data_total_in = response[7].split(": ")[1].split(",")[1].split()
if len(data_total_in) > 2:
if data_total_in[2] == "kB/s":
data_total_in = float(data_total_in[1]) * 1024
elif data_total_in[2] == "MB/s":
data_total_in = float(data_total_in[1]) * 1024 * 1024
else:
data_total_in = float(data_total_in[1])
#####################################################
data_per_client_out = response[8].split(",")[0].split()
if len(data_per_client_out) > 4:
if data_per_client_out[4] == "kB/s":
data_per_client_out = float(data_per_client_out[3]) * 1024
elif data_per_client_out[4] == "MB/s":
data_per_client_out = float(data_per_client_out[3]) * 1024 * 1024
else:
data_per_client_out = float(data_per_client_out[3])
#
data_per_client_in = response[8].split(",")[1].split()
if len(data_per_client_in) > 2:
if data_per_client_in[2] == "kB/s":
data_per_client_in = float(data_per_client_in[1]) * 1024
elif data_per_client_in[2] == "MB/s":
data_per_client_in = float(data_per_client_in[1]) * 1024 * 1024
else:
data_per_client_in = float(data_per_client_in[1])
######################################################
json_result = {
"config":{
"gamemode":gamemode,
"server_type":server_type,
"connections":connections
},
"ports":{
"client":client_port,
"server":server_port,
"hltv":hltv_port
},
"latency":{
"avg_out":latency_avg_out,
"avg_in":latency_avg_in
},
"loss":{
"avg_out":loss_avg_out,
"avg_in":loss_avg_in
},
"packets":{
"total":{
"out":packets_total_out,
"in":packets_total_in
},
"client":{
"out":packets_per_client_out,
"in":packets_per_client_in
}
},
"data":{
"total":{
"out":data_total_out,
"in":data_total_in
},
"client":{
"out":data_per_client_out,
"in":data_per_client_in
}
}
}
####################################################
print(dumps(json_result))
except Exception as rcon_error:
traceback.print_exc()
print({})
def rcon(self, command, result = False, hide_server_name = False):
if not hide_server_name:
self.o.info("{obj.name:^25}: ".format(obj = self), end="\t")
rcon_response = ""
try:
with RCON(self.address, self.password) as rcon:
rcon_response = rcon.execute(command)
response = rcon_response.body.decode("utf8","ignore")
if not result:
print(response if response else "ok")
else:
return response
except Exception as rcon_error:
rcon_response = "Rcon execute error: {}".format(rcon_error)
print(rcon_response)
def show_directory(self, path):
full_path = "{}/{}".format(self.root, path)
print("Current directory: {}".format(full_path))
if check_dir(full_path):
SCall("ls -all {}".format(full_path).split())
else:
print("Directory does not exist")
def copy_file(self, source_file, path):
full_path = "{}/{}".format(self.root, path)
print("Copy in > {}".format(full_path))
new_file = source_file.split("/")[-1:][0]
if check_dir(full_path):
SCall("cp {0} {1}/{2}".format(source_file, full_path, new_file).split())
else:
print("Destonation directory doesn't exists!")
def symbolic_file(self, source_file, path):
full_path = "{}/{}".format(self.root, path)
print("Create link in > {}".format(full_path))
new_file = source_file.split("/")[-1:][0]
if not "/" in source_file:
source_file = os.getcwd() + "/" + source_file
destonation_file = "{}/{}".format(full_path, new_file)
if check_file(destonation_file):
print("Destonation file is current created, override him?")
if self.wait_input():
remove_file("{}/{}".format(full_path, new_file))
else:
print("Abort operation!")
return
if check_dir(full_path):
cmd = "ln -s {0} {1}/{2}".format(source_file, full_path, new_file)
print("Execute: ", cmd)
SCall(cmd.split())
else:
print("Destonation directory doesn't exists!")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--serverslist", help="Path to servers list", default = "./servers.txt", type = str)
parser.add_argument("--rcon", "-r", help = "Command to execute", type = str, nargs="+", default = "")
parser.add_argument("--choice", "-c", help = "Choice server, aka: part name in directory", type = str, default = ["global.choice"], nargs = "+")
parser.add_argument("--status", "-s", help = "Show current number players on server", default = False, action = "store_true")
parser.add_argument("--yes", "-y", help = "Say YES to all response", default = False, action = "store_true")
parser.add_argument("--netstatus","-ns", help = "Show json net_status", default = False, action = "store_true")
################################################################################################################################################
parser.add_argument("--CopyFile", "-cp", help = "Path of file to copy in root directory\nNeed second argument: --DestDir", type = str, default = "")
parser.add_argument("--SymLinkFile", "-ln", help = "Path of file to create symbolic link in root directory\nNeed second argument: --DestDir", type = str, default = "")
parser.add_argument("--DestDir", "-dd", help = "Destonation directory, aka: addons/sourcemod/plugins", type = str, default = "/")
parser.add_argument("--ShowDir", "-ls", help = "Show ls command on dest directory", default = False, action = "store_true")
################################################################################################################################################
parser.add_argument("--RemovePlugin", "-rmv", help = "Name plugin to remove. Names must match.", type = str, default = "")
parser.add_argument("--UpgradePlugin", "-upg", help = "Path of file to uprade. Names must match.", type = str, default = "")
parser.add_argument("--NoReloadPlugins", "-nrp", help = "Upgrade plugins without send sm plugins refresh command", default = False, action = "store_true");
################################################################################################################################################
args = parser.parse_args()
ALL_YES = 1 if args.yes else 0
##################################
manager = Manager(args.serverslist)
#if len(args.choice) > 0 and args.choice[0] == "global.choice":
if not args.choice == ["global.choice"]:
if len(args.choice) == 1:
manager.select_server(args.choice[0])
else:
manager.server_choices = args.choice
##################################
if args.rcon:
command = ""
for word in args.rcon:
command += word + " "
command = command[:-1]
manager.execute("rcon", command)
sys.exit(0)
##################################
if args.netstatus:
manager.execute("net_status_json")
sys.exit(0)
##################################
if args.status:
manager.execute("status")
sys.exit(0)
##################################
if args.ShowDir:
if args.DestDir:
manager.execute("show_directory", args.DestDir)
sys.exit(0)
else:
o.error("Need --DestDir argument!")
sys.exit(1)
##################################
if args.CopyFile and args.DestDir:
if check_file(args.CopyFile):
manager.execute("copy_file", args.CopyFile, args.DestDir)
sys.exit(0)
else:
o.error("Invalid path of source file!")
sys.exit(1)
##################################
if args.SymLinkFile and args.DestDir:
if check_file(args.SymLinkFile):
manager.execute("symbolic_file", args.SymLinkFile, args.DestDir)
sys.exit(0)
else:
o.error("Invalid path of source file!")
sys.exit(1)
###################################
if args.UpgradePlugin:
if check_file(args.UpgradePlugin):
manager.execute("upgrade_plugin", args.UpgradePlugin, not args.NoReloadPlugins)
#manager.execute("rcon", "sm plugins refresh")
sys.exit(0)
else:
o.error("Invalid path of upgraded plugin!")
sys.exit(1)
###################################
if args.RemovePlugin:
if True:
manager.execute("remove_plugin", args.RemovePlugin, not args.NoReloadPlugins)
#manager.execute("rcon", "sm plugins refresh")
sys.exit(0)
else:
o.error("Invalid name of remove plugin!")
sys.exit(1)

1
requirements.txt

@ -0,0 +1 @@
python_valve==0.2.1
Loading…
Cancel
Save