#!/usr/bin/env python3 import os import re import struct import shutil import datetime import subprocess import yaml import click import colorama from pathlib import Path from rcon.source import Client home_dir = Path.home() def find_file(path): """Находим все конфиги в зависимости от пути""" arr = next(os.walk(path), (None, None, []))[2] # [] if no file x = arr.count('.directory') y = arr.count('logs') if x > 0: arr.remove('.directory') if y > 0: arr.remove('logs') return arr dir_config = f"{home_dir}/.config/hlna/" dir_maps_ark = f"{dir_config}/ARK/" dir_deactivated = f"{dir_maps_ark}deactivated/" list_config = find_file(dir_maps_ark) delist_config = find_file(dir_deactivated) def path_server(): dir_server = input(f"""Укажите путь до каталога, где будут храниться файлы сервера. По-умолчанию {home_dir}/Servers/ :""") if dir_server == "": dir_server = f"{home_dir}/Servers/" yaml_create(game := "path_server", dir_server) return dir_server @click.group() def hlna(): pass def create_dir(directory): """Проверка и создание директории""" if not os.path.exists(directory): os.makedirs(directory) def print_line(text): """Добавление тире вокруг текста, покраска""" print(colorama.Fore.YELLOW + "-" * 30) print(f"{colorama.Fore.GREEN} + {text}") print(colorama.Fore.YELLOW + "-" * 30 + colorama.Style.RESET_ALL) def check_int(number=""): """Проверка на ввод числа""" while True: try: x = input(number) if x == "": return 0 x = int(x) return x except ValueError: print_line("Введите число") @hlna.command(help='Сбор настроек для сервера или кластера') def config(): while True: count_game = check_int("""Выберите игру для конфигурирования 1. ARK Survival Evolved 2. 7 Days to Die : """) if count_game == 1: config_ark() break elif count_game == 2: config_7daystodie() break else: print_line("Пока есть только ARK и 7Days xD") def config_ark(list_config=list_config): create_dir(dir_server_ark) create_dir(dir_maps_ark) """Сбор данных для конфига""" data = {} port_s = [] rcon_p = [] query_p = [] cluster_id = "" cluster_dir_override = "" count_cluster = check_int("""Укажите требуется ли кластер? default: Нет 1. Да 2. Нет : """) if count_cluster == 1: cluster_server = True cluster_id = input("Укажите id для кластера, любое сочетание символов: \n") create_dir(dir_server_ark + cluster_id) cluster_dir_override = (dir_server_ark + cluster_id) else: cluster_server = False if list_config: print("Уже установленные карты: ") for i in list_config: data = read_yaml(i, game="ARK") print(f"{i} : {data['map']}") count_maps = check_int("Укажите количество карт: \n") if count_maps == 0: # 0 возвращает check_int когда, ничего не было введено count_maps = 1 for i in range(count_maps): while True: "Проверка на выбор карты из списка" amount_map = check_int("""Выберите карту из списка указав номер 1. The Island 2. The Center 3. Scorched Earth 4. Ragnarok 5. Aberration 6. Extinction 7. Valguero 8. Genesis: Part 1 9. Crystal Isles 10. Genesis: Part 2 11. Lost Island 12. Fjordur : """) if amount_map == 0: # 0 возвращает check_int когда, ничего не было введено amount_map = i + 1 if 0 < amount_map <= 12: break if list_config: for i in list_config: data = read_yaml(i, game="ARK") port_s.append(data['Port']) rcon_p.append(data['RCONPort']) query_p.append(data['QueryPort']) if amount_map == 1: map_s = "TheIsland" elif amount_map == 2: map_s = "TheCenter" elif amount_map == 3: map_s = "ScorchedEarth_P" elif amount_map == 4: map_s = "Ragnarok" elif amount_map == 5: map_s = "Aberration_P" elif amount_map == 6: map_s = "Extinction" elif amount_map == 7: map_s = "Valguero_P" elif amount_map == 8: map_s = "Genesis" elif amount_map == 9: map_s = "CrystalIsles" elif amount_map == 10: map_s = "Gen2" elif amount_map == 11: map_s = "LostIsland" elif amount_map == 12: map_s = "Fjordur" else: # Если вдруг каким-то боком проверка не отработает и не будет нужной цифры map_s = 'TheIsland' def ports(ports_arr): while True: port = check_int("") if not port: if not ports_arr: return 0 else: port = max(ports_arr) + 2 if port in ports_arr: print("Порт уже занят") else: return port if list_config: data = read_yaml(list_config[-1], game="ARK") while True: name_server = input("Укажите название Сервера: \n") if name_server == "": if map_s in list_config: count = 1 new_name = map_s while new_name in list_config: new_name = f"{map_s}{str(count)}" count += 1 list_config.append(new_name) print(list_config) break else: list_config.append(map_s) break else: if name_server in list_config: print_line("Имя занято") else: list_config.append(name_server) # если enter, то ставим последним элементом карту break print("Укажите порт сервера: ") port_server = ports(port_s) if port_server == 0: port_server = 7777 print("Порт Сервера=", port_server) print("Укажите query порт сервера: ") query_port = ports(query_p) if query_port == 0: query_port = 27015 print("Query Port=", query_port) rcon_enabled = True if rcon_p == []: rcon_port = 27020 else: rcon_port = max(rcon_p) + 1 password_server = input("Укажите пароль Сервера: \n") adminpassword_server = 123 max_players = check_int("Укажите максимальное количество игроков: \n") if max_players == 0: max_players = 70 print("Передавать сервер в глобальный список серверов steam?") listen_server_amount = check_int("""\n 1. Да 2. Нет :""") if listen_server_amount == 1: listen_server = True elif listen_server_amount == 2: listen_server = False else: listen_server = True yaml_create(game := "ARK", dir_server := "", cluster_server, map_s, list_config[-1], port_server, query_port, rcon_enabled, rcon_port, adminpassword_server, password_server, max_players, cluster_id, cluster_dir_override, listen_server) def config_7daystodie(): list_simvols = "$@-.%{}+/".split() create_dir(dir_server_7days) print("Введите имя конфига (serverconfig):\n") config_7days = input() if config_7days == "": config_7days = "serverconfig" elif config_7days == "serverconfig": config_7days = "serverconfig" elif config_7days in list_simvols: print_line("Запрещённые символы") else: xml_parser() systemd_unit_create(game := "7Days", config_7days) def xml_parser(): print("Я не умею парсить))") def yaml_create(game, dir_server="", cluster_server="", map_s="", name_server="", port_server="", query_port="", rcon_enabled="", rcon_port="", adminpassword_server="", password_server="", max_players="", id_mods_ark="", cluster_id="", cluster_dir_override="", listen_server=""): if game == "ARK": print_line(dir_maps_ark) print_line(name_server) path_yaml = dir_maps_ark + name_server settings = [ { 'map': map_s, 'Cluster': cluster_server, 'SessionName': name_server, 'Port': port_server, 'QueryPort': query_port, 'RCONEnabled': rcon_enabled, 'RCONPort': rcon_port, 'ServerAdminPassword': adminpassword_server, 'ServerPassword': password_server, 'MaxPlayers': max_players, 'ModsId': id_mods_ark, 'Listen': listen_server, 'ServerPath': dir_server_ark, 'clusterid': cluster_id, 'clusterdir': cluster_dir_override } ] elif game == "path_server": path_yaml = dir_config + "config" settings = [ { 'path_server': dir_server } ] elif game != "": systemd_unit_create(game) with open(path_yaml, 'w') as yamlfile: yaml.dump(settings, yamlfile) print(colorama.Fore.GREEN + "Конфиг создан" + colorama.Style.RESET_ALL) def systemd_unit_create(game, config_7days="", name_server=list_config): if game == "ARK": id_game = "376030" for i in name_server: data = read_yaml(i, game="ARK") ntff = "" if not data['Cluster'] else "-NoTransferFromFiltering" unit_dir_server = dir_server_ark systemd_unit_exec = f"{dir_server_exec}ShooterGameServer {data['map']}?listen={data['Listen']}?GameModIds=2967069515?SessionName={data['SessionName']}?Port={data['Port']}?QueryPort={data['QueryPort']}?RCONEnabled={data['RCONEnabled']}?RCONPort={data['RCONPort']}?ServerAdminPassword={data['ServerAdminPassword']}??MaxPlayers={data['MaxPlayers']} -clusterid={data['clusterid']} -ClusterDirOverride={data['clusterdir']} {ntff}" unit_file = f"{dir_unit}ark_{data['SessionName']}.service".lower() elif game == "7Days": id_game = "294420" # сюда дописать обращение к xml для получения уникального имени сервера unit_dir_server = dir_server_7days systemd_unit_exec = f"{dir_server_7days}startserver.sh -configfile={config_7days}.xml" unit_file = f"{dir_unit}7days.service".lower() unit_text = f'''[Unit] Description={game}: Server Wants=network-online.target After=syslog.target network.target nss-lookup.target network-online.target [Service] ExecStartPre=/usr/bin/steamcmd +force_install_dir {unit_dir_server} +login anonymous +app_update {id_game} +quit TimeoutStartSec=99999 ExecStart={systemd_unit_exec} LimitNOFILE=100000 ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s INT $MAINPID [Install] WantedBy=default.target ''' with open(unit_file, 'w') as systemd_unit: systemd_unit.write(unit_text) unit_name = unit_file.split("/")[-1] os.system(f"systemctl --user unmask {unit_name}") os.system('systemctl --user daemon-reload') os.system(f"systemctl --user enable {unit_name}") @hlna.command(help='Для скачивания и установки модов') @click.option('-g', help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def modinstall(g, m): if g == "ark": id_game_workshop = "346110" if not os.path.isdir(dir_workshop_ark): create_dir(dir_workshop_ark) id_mods_ark = input("""Укажите id модов через запятую :""") os.environ.get('mod_branch', 'Windows') os.system(f"steamcmd +login anonymous +workshop_download_item {id_game_workshop} {id_mods_ark} +quit") modextract(id_mods_ark, id_game_workshop) def modextract(id_mods_ark, id_game_workshop): mod_steam_workshop = "{}/content/{}/{}".format(dir_workshop_ark, id_game_workshop, id_mods_ark) mod_ark_mods = "{}{}".format(dir_mods_ark, id_mods_ark) modextractdir = mod_ark_mods if id_mods_ark == "111111111": return if os.path.isfile("{}/WindowsNoEditor/mod.info".format(mod_steam_workshop)): mod_steam_workshop = "{}/WindowsNoEditor".format(mod_steam_workshop) for dirpath, dirnames, filenames in os.walk(mod_steam_workshop): for dname in dirnames: os.makedirs(os.path.join(modextractdir, os.path.relpath(os.path.join(dirpath, dname), mod_steam_workshop)), exist_ok=True) for fname in filenames: if not os.path.isfile(os.path.join(mod_steam_workshop, fname)) and not os.path.isfile(os.path.join(mod_steam_workshop, fname + ".z")): os.unlink(os.path.join(modextractdir, fname)) for dname in dirnames: if not os.path.isdir(os.path.join(mod_steam_workshop, dname)): shutil.rmtree(os.path.join(modextractdir, os.path.relpath(os.path.join(dirpath, dname), mod_steam_workshop))) for root, dirs, files in os.walk(mod_steam_workshop): for name in files: if not (name.endswith(".z") or name.endswith(".z.uncompressed_size")): srcfile = os.path.join(root, name) dstfile = os.path.join(modextractdir, os.path.relpath(srcfile, mod_steam_workshop)) if not os.path.isfile(dstfile) or os.path.getmtime(srcfile) > os.path.getmtime(dstfile): shutil.copy2(srcfile, dstfile) modname = subprocess.check_output(['curl', '-s', 'https://steamcommunity.com/sharedfiles/filedetails/?id={}'.format(id_mods_ark)]).decode('utf-8') print_line(f"{modname} mod_name") modname = re.search(r'
(.+)
', modname) print_line(f"{modname} mod_name") modname = modname and modname.group(1) print_line(f"{modname} mod_name") if os.path.isfile("{}/.mod".format(modextractdir)): os.remove("{}/.mod".format(modextractdir)) modfile_bytes = b'' with open(os.path.join(modextractdir, 'mod.info'), 'rb') as f: data = f.read() mapnamelen = struct.unpack_from('> {dir_logs}{date} 2>&1") # Добавить текущее время with open(f"{dir_logs}{date}.log", "a") as f: f.write(f"[{time}] File {i} has been moved to {dir_maps_ark}\n") if x == 0: print(f"Карта активирована - {i}") else: print(f"{i} либо уже активирована, либо такой карты нет") finally: pass # except: # print("ошибка при активации карты") # pass else: for i in m: try: if i in delist_config: print(f"Карта {i} уже есть в деактивированных") continue x = os.system( f"mv {dir_maps_ark}{i} {dir_deactivated} >> {dir_logs}{date} 2>&1") # Добавить текущее время with open(f"{dir_logs}{date}.log", "a") as f: f.write(f"[{time}] File {i} has been moved to {dir_deactivated}\n") if x == 0: print(f"Карта деактивирована - {i}") else: print(f"{i} либо деактивирована, либо такой карты нет") except: pass @hlna.command() def status(list_config=list_config): # Добавить сортивку по кластерам и вывод несколько столбиков if list_config == [] and delist_config == []: print("Сервера не сконфигурированы") else: for i in list_config: data = read_yaml(i, game="ARK") x = os.system(f"lsof -w -i :{data['Port']}") if x == 0: print(colorama.Fore.GREEN + "Сервер запущен" + colorama.Style.RESET_ALL) else: print(colorama.Fore.RED + "Сервер не запущен" + colorama.Style.RESET_ALL) print(f""" Имя сервера: {i} Карта: {data['map']} Моды: {data['ModsId']} Пароль: {data['ServerPassword']} Кластер: {data['Cluster']} Кластер id: {data['clusterid']} Query порт: {data['QueryPort']} Порт сервера: {data['Port']} Rcon включен: {data['RCONEnabled']} Rcon порт : {data['RCONPort']} Максимальное кол-во игроков: {data['MaxPlayers']}""") print("-" * 40) if delist_config != []: x = input("Есть неактивные сервера, показать Y/n: ") if x != "n": for i in delist_config: data = read_yaml(i, False) print(f""" Имя сервера: {i} Карта: {data['map']} Моды: {data['ModsId']} Пароль: {data['ServerPassword']} Кластер: {data['Cluster']} Кластер id: {data['clusterid']} Query порт: {data['QueryPort']} Порт сервера: {data['Port']} Rcon включен: {data['RCONEnabled']} Rcon порт : {data['RCONPort']} Максимальное кол-во игроков: {data['MaxPlayers']}""") print("-" * 40) @hlna.command(help='Для запуска, сконфигурированного сервера или кластера') @click.option('-g', help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def start(g, m): start_stop("start", g, m) @hlna.command(help='Для остановки, сконфигурированного сервера или кластера') @click.option('-g', help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def stop(g, m): start_stop("stop", g, m) @hlna.command(help='Для перезапуска, сконфигурированного сервера или кластера') @click.option('-g', help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def restart(g, m): start_stop("restart", g, m) def start_stop(action, g, m, list_config=list_config): if g == "ark": dict_mapname = {} dict_allmapname = [] for i in list_config: data = read_yaml(i, game="ARK") dict_mapname[data['SessionName']] = data['map'] dict_allmapname.append(data['SessionName']) names_serverstart = [] for ns, v in dict_mapname.items(): if v in m: names_serverstart.append(ns) if list_config != []: #Перенести выше для проверки наличия конфигов if m == "all": names_serverstart = dict_allmapname print(f"Выполняется для карт(-ы) {names_serverstart}") else: names_serverstart = choose_map(names_serverstart) for i in names_serverstart: data = read_yaml(i, game="ARK") y = os.system(f"~/git/hln-a/hlna.py rcon SaveWorld -m {i}") if action == "restart" or action == "stop" else "" x = os.system(f"systemctl --user {action} ark_{data['SessionName'].lower()}.service") print_line(y) if x == 0: print_line("Готово") else: print("Ни одной карты не установлено") elif g == "7days": x = os.system(f"systemctl --user {action} 7days.service") if x == 0: print_line("Готово") def read_yaml(list_config=list_config, flag=True, game=""): # Читаем конфиги активных или неактивных карт в зависимости от флага if game == "ARK": path_yaml = f"{dir_maps_ark}{list_config}" if flag else f"{dir_deactivated}{list_config}" elif game == "path_server": path_yaml = dir_config + "config" with open(path_yaml, "r") as yamlfile: data = yaml.load(yamlfile, Loader=yaml.FullLoader) return data[0] # возвращаем словарь со всеми значениями def choose_map(arr): new_arr = [] arr = sorted(arr) print('Найдены сервера с этой картой') for i, map in enumerate(arr): print(f"{i + 1}) {map}") while True: try: x = input("Выберите сервер из списка, либо несколько через запятую: ").split(',') x = [int(i) for i in x] break except: print("Неправильный ввод") for i in x: new_arr.append(arr[i - 1]) print('Выбранные сервера:', new_arr) return new_arr @hlna.command() @click.argument('c', nargs=1) @click.option('-m', required=True, help="Название карты для применения rcon команды") def rcon(m, c): print_line(f"Команда: , {c}") print_line(f"Карты: , {m}") dict_mapname = {} dict_adminpwd = {} if list_config: rcon_ports = [] for i in list_config: data = read_yaml(i, game="ARK") dict_mapname[data['RCONPort']] = data['map'] dict_adminpwd[data['RCONPort']] = data['ServerAdminPassword'] if m == "all": for rcon_p in dict_mapname: rcon_ports.append(rcon_p) else: for rcon_p, name_map in dict_mapname.items(): print_line(f"переменные name_map и m {name_map} & {m}") # обьединить с таким же блоком в start() if name_map in m: rcon_ports.append(rcon_p) print_line(f"Карта которая запускается {name_map}") for port in rcon_ports: print(f"Rcon выполнен для {port}") passwd = dict_adminpwd[port] with Client('127.0.0.1', port, passwd=str(passwd)) as client: response = client.run(c) print(response) else: pass def zero(x=""): return "" if not os.path.exists(dir_config + "config"): dir_server = path_server() else: print_line("else") data = read_yaml(game="path_server") if data['path_server'] == "": path_server() else: print_line(data['path_server']) dir_server = data['path_server'] dir_unit = f"{home_dir}/.config/systemd/user/" dir_logs = f"{dir_config}logs/" dir_server_ark = f"{dir_server}ARK/" dir_server_exec = f"{dir_server_ark}ShooterGame/Binaries/Linux/" dir_workshop_ark = f"{home_dir}/.local/share/Steam/steamapps/workshop/" dir_mods_ark = f"{dir_server_ark}ShooterGame/Content/Mods/" dir_server_7days = f"{dir_server}/7Days/" now = datetime.datetime.now() date = now.strftime("%Y-%m-%d") time = now.strftime("%H:%M:%S") create_dir(dir_server) create_dir(dir_unit) create_dir(dir_config) create_dir(dir_logs) if __name__ == '__main__': hlna()