#!/usr/bin/env python3 import os import re import sys import zlib import time import struct import logging import datetime import requests import subprocess import yaml import click import zipfile import colorama from pathlib import Path from PyQt6 import QtWidgets from rcon.source import Client import hlnaui home_dir = Path.home() logging.basicConfig(stream=sys.stderr, level=logging.CRITICAL) def find_file(path): """Находим все конфиги в зависимости от пути""" arr = next(os.walk(path), (None, None, []))[2] # [] if no file if '.directory' in arr: arr.remove('.directory') 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 create_dir(directory): """Проверка и создание директории""" if not os.path.exists(directory): os.makedirs(directory) create_dir(dir_config) def path_server(): """Получение пути для хранения файлов серверов, записываем путь в yaml файл""" dir_server = input(f"""Укажите путь до каталога, где будут храниться файлы сервера. По-умолчанию {home_dir}/Servers/ :""") if not dir_server: dir_server = f"{home_dir}/Servers/" yaml_create("path_server", dir_server) return dir_server @click.group() def hlna(): pass @hlna.command(help='Восстановление бэкапов серверов в ') @click.argument('g', nargs=1) @click.option('-d', required=True, help="Путь до zip архива") def restore(g, d): """Получение пути к файлам внутри архива""" with zipfile.ZipFile(d, 'r') as zip_file: files = zip_file.namelist() """Извлечение файлов""" for i in files: with zipfile.ZipFile(d, 'r') as zip_file: path_extarct = "./" if g == 'test' else "/" zip_file.extract(i, path_extarct) print(i, colorama.Fore.GREEN + "- перемещен" + colorama.Style.RESET_ALL) print_line(f"Бэкап {d} восстановлен", flag="GREEN") @hlna.command(help='Бэкап серверов выбранной игры (.+)', modname) modname = modname and modname.group(1) if os.path.isfile(f"{dir_mod_ark}.mod"): os.remove(f"{dir_mod_ark}.mod") with open(f"{dir_extract}/mod.info", "rb") as modinfo: data = modinfo.read() mapnamelen = struct.unpack_from("> {dir_logs}{date} 2>&1") with open(f"{dir_logs}{date}.log", "a") as f: f.write(f"[{t}] Сервер {i} перемещён из {state_msg}\n")# переписать эту залупу else: 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"[{t}] Сервер {i} перемещён из {state_msg}\n") if x == 0: print_line(f"Готов - {i}", flag="GREEN") #start = "start" if e else "stop" enable = "enable" if e else "disable" os.system(f"systemctl --user {enable} ark_{i}") else: print_line(f"Ошибка перемещения {i}", flag="RED") except: print_line("ошибка операции", flag="RED") @hlna.command(help='Выводит статус настроеных серверов') def status(list_config=list_config): ext_ip = get_external_ip() if list_config == [] and delist_config == []: print_line("Сервера не сконфигурированы", flag="RED") else: for i in list_config: data = read_yaml(i, g="ark") x = os.system(f"lsof -w -i :{data['Port']}") if x == 0: print_line("Сервер запущен", flag="GREEN") else: print_line("Сервер не запущен", flag="RED") # этот принт надо отдельной функцией сделать, чтобы убрать дублирование текста print_line(f""" Имя сервера: {i} Карта: {data['map']} Моды: {data['ModsId']} Пароль: {data['ServerPassword']}S Кластер: {data['Cluster']} Кластер id: {data['clusterid']} Query порт: {data['QueryPort']} Порт сервера: {data['Port']} Rcon включен: {data['RCONEnabled']} Rcon порт : {data['RCONPort']} Максимальное кол-во игроков: {data['MaxPlayers']} steam://connect/{ext_ip}:{data['QueryPort']}""", flag="CYAN") if delist_config != []: x = input("Есть неактивные сервера, показать Y/n: ") if x != "n": for i in delist_config: data = read_yaml(i, False) print_line(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']}""", flag="YELLOW") @hlna.command(help='Запуск, сконфигурированного сервера или кластера ') @click.option('-g', required=True, help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def start(g, m): """Запускает сервер выбранной игры""" # добавить проверку на ввод аргумента ark/7days если else: давать подсказку # если нет конфигов, то выводим что серверов нет g = g.lower() if g == "ark": modupdateall(g, m) start_stop("start", g, m) @hlna.command(help='Остановка, сконфигурированного сервера или кластера ') @click.option('-g', required=True, help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def stop(g, m): g = g.lower() if g == "ark": modupdateall(g, m) start_stop("stop", g, m) @hlna.command(help='Перезапуск, сконфигурированного сервера или кластера ') @click.option('-g', required=True, help="Название игры для запуска. (ark, 7days") @click.option('-m', default='all', help="Название карты для запуска или all для запуска все карт") def restart(g, m): g = g.lower() if g == "ark": modupdateall(g, m) start_stop("restart", g, m) def check_exist_servers(g): """Проверяет наличие конфигов для активных карт""" g = g.lower() if g == "ark" and not list_config: print_line("Нет сконфигурированных серверов", flag="RED") # добавить отсюда вилку на вопрос с конфигурацией elif g == "7days": print_line("7Days", flag="CYAN") else: return 1 def start_stop(action, g, m): """Функция изменения статусов сервера""" g = g.lower() if g == "ark": x = check_exist_servers(g) if x: name_servers = choose_map(g,m) if m !='all' else list_config for i in name_servers: data = read_yaml(i, g="ark", flag=True) if action == "stop" or action == "restart": rcon_local(i, "SaveWorld") x = os.system(f"systemctl --user {action} ark_{data['SessionName'].lower()}.service") if x == 0: print_line(f"Готово {action} для {g} {i}", flag="GREEN") elif g == "7days": x = os.system(f"systemctl --user {action} 7days.service") if x == 0: print_line(f"Готово {action} для {m}", flag="GREEN") else: print_line("доступные игры: ark и 7days") def read_yaml(list_config=list_config, flag=True, m="", g=""): """Читает конфиги активных или неактивных карт в зависимости от флага и отдаёт данные туда где их запросили""" g = g.lower() if g == "ark": if m == "all": list_config=list_config path_yaml = f"{dir_maps_ark}{list_config}" if flag else f"{dir_deactivated}{list_config}" elif g == "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(g, m, list_config=list_config): """Функция выбора карт""" g = g.lower() new_arr = [] if g == "ark": dict_mapname = {} dict_allmapname = [] for i in list_config: data = read_yaml(i, g="ark") dict_mapname[data['SessionName']] = data['map'] dict_allmapname.append(data['SessionName']) name_servers = [] for ns, v in dict_mapname.items(): if v in m: name_servers.append(ns) if list_config != []: # Перенести выше для проверки наличия конфигов if m == "all": new_arr = dict_allmapname print_line(f"Выполняется для карт(-ы) {name_servers}", flag="CYAN") else: if name_servers: name_servers = sorted(name_servers) print_line('Найдены сервера с этой картой', flag="CYAN") for i, map in enumerate(name_servers): print_line(f"{i + 1}) {map}", flag="CYAN") while True: try: x = input("Выберите сервер из списка, либо несколько через запятую: ").split(',') x = [int(i) for i in x] break except: print_line("Неправильный ввод", flag="RED") print(name_servers) for i in x: new_arr.append(name_servers[i - 1]) print_line(f"Выбранные сервера: {name_servers}", flag="CYAN") else: print_line("Не найдено серверов с картой") return new_arr @hlna.command(help='Отправка команд на игровой сервер через rcon ') @click.argument('c', nargs=1) @click.option('-m', required=True, help="Название карты для применения rcon команды") def rcon(m, c): rcon_local(m, c) def rcon_local(m, c): try: dict_mapname = {} dict_adminpwd = {} if list_config: rcon_ports = [] for i in list_config: data = read_yaml(i, g="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(): if name_map in m: rcon_ports.append(rcon_p) for port in rcon_ports: passwd = dict_adminpwd[port] with Client('127.0.0.1', port, passwd=str(passwd)) as client: response = client.run(c) print_line(f"Rcon выполнен {response} {dict_mapname[port]}", flag="GREEN") else: pass except: print_line(f"Ошибка отправки команды {c} в {m}", flag="RED") def zero(x=""): """Потом пригодится (нет)""" return "" if not os.path.exists(dir_config + "config"): dir_server = path_server() else: data = read_yaml(g="path_server") if data['path_server'] == "": path_server() else: 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_workshop_ark = f"{home_dir}/.local/share/Steam/steamapps/workshop" dir_shooter = "ShooterGame" 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") t = now.strftime("%H:%M:%S") create_dir(dir_server) create_dir(dir_unit) create_dir(dir_logs) class HlnaApp(QtWidgets.QMainWindow, hlnaui.Ui_mainWindow): def __init__(self): super().__init__() self.setupUi(self) def hlnag(): if len(sys.argv) > 1: hlna() else: app = QtWidgets.QApplication(sys.argv) window = HlnaApp() window.show() sys.exit(app.exec()) if __name__ == '__main__': hlnag()